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 f402fda9f2..79e4932dd1 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
@@ -100,7 +100,23 @@
      }
  
      void forEachSpawnCandidateChunk(Consumer<ChunkHolder> callback) {
-@@ -1424,7 +1458,7 @@
+@@ -1215,6 +1249,7 @@
+     }
+ 
+     public void addEntity(Entity entity) {
++        org.spigotmc.AsyncCatcher.catchOp("entity track"); // Spigot
+         if (!(entity instanceof EnderDragonPart)) {
+             EntityType<?> entitytypes = entity.getType();
+             int i = entitytypes.clientTrackingRange() * 16;
+@@ -1250,6 +1285,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();
+@@ -1424,7 +1460,7 @@
          public final Set<ServerPlayerConnection> seenBy = Sets.newIdentityHashSet();
  
          public TrackedEntity(final Entity entity, final int i, final int j, final boolean flag) {
@@ -109,7 +125,23 @@
              this.entity = entity;
              this.range = i;
              this.lastSectionPos = SectionPos.of((EntityAccess) entity);
-@@ -1484,6 +1518,11 @@
+@@ -1469,6 +1505,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,6 +1513,7 @@
+         }
+ 
+         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());
+                 int i = ChunkMap.this.getPlayerViewDistance(player);
+@@ -1484,6 +1522,11 @@
                  double d2 = d0 * d0;
                  boolean flag = d1 <= d2 && 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/ServerLevel.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch
index 88d306b6fb..aa947ffaa4 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
@@ -78,7 +78,7 @@
 +    // CraftBukkit start
 +    public final LevelStorageSource.LevelStorageAccess convertable;
 +    public final UUID uuid;
-+
+ 
 +    public LevelChunk getChunkIfLoaded(int x, int z) {
 +        return this.chunkSource.getChunk(x, z, false);
 +    }
@@ -102,7 +102,7 @@
 +        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));
 +            if (chunkgenerator instanceof NoiseBasedChunkGenerator cga) {
@@ -284,14 +284,14 @@
 -            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) {
@@ -299,14 +299,14 @@
 +                    ((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
@@ -421,13 +421,14 @@
          }
  
      }
-@@ -939,24 +1061,37 @@
+@@ -939,24 +1061,38 @@
          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
          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
@@ -463,7 +464,7 @@
              return true;
          }
      }
-@@ -967,13 +1102,35 @@
+@@ -967,13 +1103,35 @@
      }
  
      public void removePlayerImmediately(ServerPlayer player, Entity.RemovalReason reason) {
@@ -500,7 +501,7 @@
          while (iterator.hasNext()) {
              ServerPlayer entityplayer = (ServerPlayer) iterator.next();
  
-@@ -982,6 +1139,12 @@
+@@ -982,6 +1140,12 @@
                  double d1 = (double) pos.getY() - entityplayer.getY();
                  double d2 = (double) pos.getZ() - entityplayer.getZ();
  
@@ -513,7 +514,7 @@
                  if (d0 * d0 + d1 * d1 + d2 * d2 < 1024.0D) {
                      entityplayer.connection.send(new ClientboundBlockDestructionPacket(entityId, pos, progress));
                  }
-@@ -1060,7 +1223,18 @@
+@@ -1060,7 +1224,18 @@
              Iterator iterator = this.navigatingMobs.iterator();
  
              while (iterator.hasNext()) {
@@ -533,7 +534,7 @@
                  PathNavigation navigationabstract = entityinsentient.getNavigation();
  
                  if (navigationabstract.shouldRecomputePath(pos)) {
-@@ -1126,9 +1300,15 @@
+@@ -1126,9 +1301,15 @@
  
      @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) {
@@ -550,7 +551,7 @@
              case NONE:
                  explosion_effect = Explosion.BlockInteraction.KEEP;
                  break;
-@@ -1144,16 +1324,26 @@
+@@ -1144,16 +1325,26 @@
              case TRIGGER:
                  explosion_effect = Explosion.BlockInteraction.TRIGGER_BLOCK;
                  break;
@@ -580,7 +581,7 @@
          Iterator iterator = this.players.iterator();
  
          while (iterator.hasNext()) {
-@@ -1162,10 +1352,11 @@
+@@ -1162,10 +1353,11 @@
              if (entityplayer.distanceToSqr(vec3d) < 4096.0D) {
                  Optional<Vec3> optional = Optional.ofNullable((Vec3) serverexplosion.getHitPlayers().get(entityplayer));
  
@@ -593,7 +594,7 @@
      }
  
      private Explosion.BlockInteraction getDestroyType(GameRules.Key<GameRules.BooleanValue> decayRule) {
-@@ -1226,17 +1417,24 @@
+@@ -1226,17 +1418,24 @@
      }
  
      public <T extends ParticleOptions> int sendParticles(T parameters, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double speed) {
@@ -621,7 +622,7 @@
                  ++j;
              }
          }
-@@ -1292,7 +1490,7 @@
+@@ -1292,7 +1491,7 @@
  
      @Nullable
      public BlockPos findNearestMapStructure(TagKey<Structure> structureTag, BlockPos pos, int radius, boolean skipReferencedStructures) {
@@ -630,7 +631,7 @@
              return null;
          } else {
              Optional<HolderSet.Named<Structure>> optional = this.registryAccess().lookupOrThrow(Registries.STRUCTURE).get(structureTag);
-@@ -1334,11 +1532,22 @@
+@@ -1334,11 +1533,22 @@
      @Nullable
      @Override
      public MapItemSavedData getMapData(MapId id) {
@@ -654,7 +655,7 @@
          this.getServer().overworld().getDataStorage().set(id.key(), state);
      }
  
-@@ -1649,6 +1858,11 @@
+@@ -1649,6 +1859,11 @@
      @Override
      public void blockUpdated(BlockPos pos, Block block) {
          if (!this.isDebug()) {
@@ -666,7 +667,7 @@
              this.updateNeighborsAt(pos, block);
          }
  
-@@ -1668,12 +1882,12 @@
+@@ -1668,12 +1883,12 @@
      }
  
      public boolean isFlat() {
@@ -681,7 +682,7 @@
      }
  
      @Nullable
-@@ -1696,7 +1910,7 @@
+@@ -1696,7 +1911,7 @@
      private static <T> String getTypeCount(Iterable<T> items, Function<T, String> classifier) {
          try {
              Object2IntOpenHashMap<String> object2intopenhashmap = new Object2IntOpenHashMap();
@@ -690,7 +691,7 @@
  
              while (iterator.hasNext()) {
                  T t0 = iterator.next();
-@@ -1705,7 +1919,7 @@
+@@ -1705,7 +1920,7 @@
                  object2intopenhashmap.addTo(s, 1);
              }
  
@@ -699,7 +700,23 @@
                  String s1 = (String) entry.getKey();
  
                  return s1 + ":" + entry.getIntValue();
-@@ -1864,6 +2078,8 @@
+@@ -1717,6 +1932,7 @@
+ 
+     @Override
+     public LevelEntityGetter<Entity> getEntities() {
++        org.spigotmc.AsyncCatcher.catchOp("Chunk getEntities call"); // Spigot
+         return this.entityManager.getEntityGetter();
+     }
+ 
+@@ -1836,6 +2052,7 @@
+         }
+ 
+         public void onTrackingStart(Entity entity) {
++            org.spigotmc.AsyncCatcher.catchOp("entity register"); // Spigot
+             ServerLevel.this.getChunkSource().addEntity(entity);
+             if (entity instanceof ServerPlayer entityplayer) {
+                 ServerLevel.this.players.add(entityplayer);
+@@ -1864,9 +2081,12 @@
              }
  
              entity.updateDynamicGameEventListener(DynamicGameEventListener::add);
@@ -708,7 +725,11 @@
          }
  
          public void onTrackingEnd(Entity entity) {
-@@ -1895,6 +2111,14 @@
++            org.spigotmc.AsyncCatcher.catchOp("entity unregister"); // Spigot
+             ServerLevel.this.getChunkSource().removeEntity(entity);
+             if (entity instanceof ServerPlayer entityplayer) {
+                 ServerLevel.this.players.remove(entityplayer);
+@@ -1895,6 +2115,14 @@
              }
  
              entity.updateDynamicGameEventListener(DynamicGameEventListener::remove);
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 fdda023f17..2312b7078b 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
@@ -216,10 +216,13 @@
          this.activeEffects.clear();
      }
  
-@@ -781,6 +841,17 @@
-             }
-         }
- 
+@@ -778,8 +838,19 @@
+                 if (mobeffect != null) {
+                     this.activeEffects.put(mobeffect.getEffect(), mobeffect);
+                 }
++            }
++        }
++
 +        // CraftBukkit start
 +        if (nbt.contains("Bukkit.MaxHealth")) {
 +            Tag nbtbase = nbt.get("Bukkit.MaxHealth");
@@ -227,13 +230,12 @@
 +                this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(((FloatTag) nbtbase).getAsDouble());
 +            } else if (nbtbase.getId() == 3) {
 +                this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(((IntTag) nbtbase).getAsDouble());
-+            }
-+        }
+             }
+         }
 +        // CraftBukkit end
-+
+ 
          if (nbt.contains("Health", 99)) {
              this.setHealth(nbt.getFloat("Health"));
-         }
 @@ -819,9 +890,32 @@
  
      }
@@ -348,7 +350,7 @@
          }
      }
  
-@@ -987,24 +1117,54 @@
+@@ -987,24 +1117,55 @@
          return this.addEffect(effect, (Entity) null);
      }
  
@@ -363,6 +365,7 @@
 +    }
 +
 +    public boolean addEffect(MobEffectInstance mobeffect, @Nullable Entity entity, EntityPotionEffectEvent.Cause cause) {
++        org.spigotmc.AsyncCatcher.catchOp("effect add"); // Spigot
 +        if (this.isTickingEffects) {
 +            this.effectsToProcess.add(new ProcessableEffect(mobeffect, cause));
 +            return true;
@@ -412,7 +415,7 @@
              return flag;
          }
      }
-@@ -1031,14 +1191,40 @@
+@@ -1031,14 +1192,40 @@
          return this.getType().is(EntityTypeTags.INVERTED_HEALING_AND_HARM);
      }
  
@@ -455,7 +458,7 @@
          if (mobeffect != null) {
              this.onEffectsRemoved(List.of(mobeffect));
              return true;
-@@ -1142,20 +1328,55 @@
+@@ -1142,20 +1329,55 @@
  
      }
  
@@ -512,7 +515,7 @@
          this.entityData.set(LivingEntity.DATA_HEALTH_ID, Mth.clamp(health, 0.0F, this.getMaxHealth()));
      }
  
-@@ -1167,7 +1388,7 @@
+@@ -1167,7 +1389,7 @@
      public boolean hurtServer(ServerLevel world, DamageSource source, float amount) {
          if (this.isInvulnerableTo(world, source)) {
              return false;
@@ -521,7 +524,7 @@
              return false;
          } else if (source.is(DamageTypeTags.IS_FIRE) && this.hasEffect(MobEffects.FIRE_RESISTANCE)) {
              return false;
-@@ -1182,10 +1403,11 @@
+@@ -1182,10 +1404,11 @@
              }
  
              float f1 = amount;
@@ -535,7 +538,7 @@
                  this.hurtCurrentlyUsedShield(amount);
                  f2 = amount;
                  amount = 0.0F;
-@@ -1202,15 +1424,26 @@
+@@ -1202,15 +1425,26 @@
                  flag = true;
              }
  
@@ -564,7 +567,7 @@
              this.walkAnimation.setSpeed(1.5F);
              if (Float.isNaN(amount) || Float.isInfinite(amount)) {
                  amount = Float.MAX_VALUE;
-@@ -1218,18 +1451,27 @@
+@@ -1218,18 +1452,27 @@
  
              boolean flag1 = true;
  
@@ -596,7 +599,7 @@
                  this.hurtDuration = 10;
                  this.hurtTime = this.hurtDuration;
              }
-@@ -1243,7 +1485,7 @@
+@@ -1243,7 +1486,7 @@
                      world.broadcastDamageEvent(this, source);
                  }
  
@@ -605,7 +608,7 @@
                      this.markHurt();
                  }
  
-@@ -1263,7 +1505,7 @@
+@@ -1263,7 +1506,7 @@
                          d1 = source.getSourcePosition().z() - this.getZ();
                      }
  
@@ -614,7 +617,7 @@
                      if (!flag) {
                          this.indicateDamage(d0, d1);
                      }
-@@ -1282,7 +1524,7 @@
+@@ -1282,7 +1525,7 @@
                  this.playHurtSound(source);
              }
  
@@ -623,7 +626,7 @@
  
              if (flag2) {
                  this.lastDamageSource = source;
-@@ -1329,10 +1571,10 @@
+@@ -1329,10 +1572,10 @@
      }
  
      @Nullable
@@ -636,7 +639,7 @@
              this.lastHurtByPlayerTime = 100;
              this.lastHurtByPlayer = entityhuman;
              return entityhuman;
-@@ -1342,8 +1584,8 @@
+@@ -1342,8 +1585,8 @@
                      this.lastHurtByPlayerTime = 100;
                      LivingEntity entityliving = entitywolf.getOwner();
  
@@ -647,7 +650,7 @@
  
                          this.lastHurtByPlayer = entityhuman1;
                      } else {
-@@ -1363,7 +1605,7 @@
+@@ -1363,7 +1606,7 @@
      }
  
      protected void blockedByShield(LivingEntity target) {
@@ -656,7 +659,7 @@
      }
  
      private boolean checkTotemDeathProtection(DamageSource source) {
-@@ -1375,20 +1617,33 @@
+@@ -1375,20 +1618,33 @@
              InteractionHand[] aenumhand = InteractionHand.values();
              int i = aenumhand.length;
  
@@ -694,7 +697,7 @@
                      ServerPlayer entityplayer = (ServerPlayer) this;
  
                      entityplayer.awardStat(Stats.ITEM_USED.get(itemstack.getItem()));
-@@ -1512,14 +1767,22 @@
+@@ -1512,14 +1768,22 @@
                      BlockState iblockdata = Blocks.WITHER_ROSE.defaultBlockState();
  
                      if (this.level().getBlockState(blockposition).isAir() && iblockdata.canSurvive(this.level(), blockposition)) {
@@ -719,7 +722,7 @@
                      this.level().addFreshEntity(entityitem);
                  }
              }
-@@ -1530,24 +1793,39 @@
+@@ -1530,22 +1794,37 @@
      protected void dropAllDeathLoot(ServerLevel world, DamageSource damageSource) {
          boolean flag = this.lastHurtByPlayerTime > 0;
  
@@ -749,8 +752,8 @@
          }
  
 +        return 0; // CraftBukkit
-     }
- 
++    }
++
 +    protected void dropExperience(ServerLevel world, @Nullable Entity attacker) {
 +        // 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
@@ -758,12 +761,10 @@
 +            this.expToDrop = 0;
 +        }
 +        // CraftBukkit end
-+    }
-+
-     protected void dropCustomDeathLoot(ServerLevel world, DamageSource source, boolean causedByPlayer) {}
+     }
  
-     public long getLootTableSeed() {
-@@ -1612,19 +1890,31 @@
+     protected void dropCustomDeathLoot(ServerLevel world, DamageSource source, boolean causedByPlayer) {}
+@@ -1612,19 +1891,31 @@
      }
  
      public void knockback(double strength, double x, double z) {
@@ -802,7 +803,7 @@
          }
      }
  
-@@ -1683,6 +1973,20 @@
+@@ -1683,6 +1974,20 @@
          return new LivingEntity.Fallsounds(SoundEvents.GENERIC_SMALL_FALL, SoundEvents.GENERIC_BIG_FALL);
      }
  
@@ -823,7 +824,7 @@
      public Optional<BlockPos> getLastClimbablePos() {
          return this.lastClimbablePos;
      }
-@@ -1757,9 +2061,14 @@
+@@ -1757,9 +2062,14 @@
          int i = this.calculateFallDamage(fallDistance, damageMultiplier);
  
          if (i > 0) {
@@ -839,7 +840,7 @@
              return true;
          } else {
              return flag;
-@@ -1830,7 +2139,7 @@
+@@ -1830,7 +2140,7 @@
  
      protected float getDamageAfterArmorAbsorb(DamageSource source, float amount) {
          if (!source.is(DamageTypeTags.BYPASSES_ARMOR)) {
@@ -848,7 +849,7 @@
              amount = CombatRules.getDamageAfterAbsorb(this, amount, source, (float) this.getArmorValue(), (float) this.getAttributeValue(Attributes.ARMOR_TOUGHNESS));
          }
  
-@@ -1841,7 +2150,8 @@
+@@ -1841,7 +2151,8 @@
          if (source.is(DamageTypeTags.BYPASSES_EFFECTS)) {
              return amount;
          } else {
@@ -858,7 +859,7 @@
                  int i = (this.getEffect(MobEffects.DAMAGE_RESISTANCE).getAmplifier() + 1) * 5;
                  int j = 25 - i;
                  float f1 = amount * (float) j;
-@@ -1884,18 +2194,144 @@
+@@ -1884,18 +2195,144 @@
          }
      }
  
@@ -1012,7 +1013,7 @@
  
                  if (entity instanceof ServerPlayer) {
                      ServerPlayer entityplayer = (ServerPlayer) entity;
-@@ -1904,13 +2340,48 @@
+@@ -1904,13 +2341,48 @@
                  }
              }
  
@@ -1065,7 +1066,7 @@
      }
  
      public CombatTracker getCombatTracker() {
-@@ -1935,9 +2406,19 @@
+@@ -1935,9 +2407,19 @@
      }
  
      public final void setArrowCount(int stuckArrowCount) {
@@ -1086,7 +1087,7 @@
      public final int getStingerCount() {
          return (Integer) this.entityData.get(LivingEntity.DATA_STINGER_COUNT_ID);
      }
-@@ -1999,7 +2480,7 @@
+@@ -1999,7 +2481,7 @@
                      this.playSound(soundeffect, this.getSoundVolume(), (this.random.nextFloat() - this.random.nextFloat()) * 0.2F + 1.0F);
                  }
  
@@ -1095,7 +1096,7 @@
                      this.setHealth(0.0F);
                      this.die(this.damageSources().generic());
                  }
-@@ -2182,6 +2663,12 @@
+@@ -2182,6 +2664,12 @@
  
      public abstract ItemStack getItemBySlot(EquipmentSlot slot);
  
@@ -1108,7 +1109,7 @@
      public abstract void setItemSlot(EquipmentSlot slot, ItemStack stack);
  
      public Iterable<ItemStack> getHandSlots() {
-@@ -2494,7 +2981,7 @@
+@@ -2494,7 +2982,7 @@
  
      }
  
@@ -1117,7 +1118,7 @@
          Vec3 vec3d1 = this.getRiddenInput(controllingPlayer, movementInput);
  
          this.tickRidden(controllingPlayer, vec3d1);
-@@ -2507,13 +2994,13 @@
+@@ -2507,13 +2995,13 @@
  
      }
  
@@ -1134,7 +1135,7 @@
          return this.getSpeed();
      }
  
-@@ -2571,7 +3058,7 @@
+@@ -2571,7 +3059,7 @@
              double d1 = Mth.clamp(motion.z, -0.15000000596046448D, 0.15000000596046448D);
              double d2 = Math.max(motion.y, -0.15000000596046448D);
  
@@ -1143,7 +1144,7 @@
                  d2 = 0.0D;
              }
  
-@@ -2586,7 +3073,7 @@
+@@ -2586,7 +3074,7 @@
      }
  
      protected float getFlyingSpeed() {
@@ -1152,7 +1153,7 @@
      }
  
      public float getSpeed() {
-@@ -2634,7 +3121,7 @@
+@@ -2634,7 +3122,7 @@
                  }
              }
  
@@ -1161,7 +1162,7 @@
              if (this.tickCount % 20 == 0) {
                  this.getCombatTracker().recheckStatus();
              }
-@@ -2741,7 +3228,7 @@
+@@ -2741,7 +3229,7 @@
          this.elytraAnimationState.tick();
      }
  
@@ -1170,7 +1171,7 @@
          Map<EquipmentSlot, ItemStack> map = this.collectEquipmentChanges();
  
          if (map != null) {
-@@ -3000,7 +3487,7 @@
+@@ -3000,7 +3488,7 @@
          {
              LivingEntity entityliving = this.getControllingPassenger();
  
@@ -1179,7 +1180,7 @@
                  if (this.isAlive()) {
                      this.travelRidden(entityhuman, vec3d1);
                      break label112;
-@@ -3063,6 +3550,7 @@
+@@ -3063,6 +3551,7 @@
          this.checkSlowFallDistance();
          if (!this.level().isClientSide) {
              if (!this.canGlide()) {
@@ -1187,7 +1188,7 @@
                  this.setSharedFlag(7, false);
                  return;
              }
-@@ -3113,7 +3601,7 @@
+@@ -3113,7 +3602,7 @@
          Level world = this.level();
  
          if (!(world instanceof ServerLevel worldserver)) {
@@ -1196,7 +1197,7 @@
          } else {
              List list = this.level().getEntities((Entity) this, this.getBoundingBox(), EntitySelector.pushableBy(this));
  
-@@ -3305,15 +3793,22 @@
+@@ -3305,15 +3794,22 @@
  
      @Override
      public boolean isPickable() {
@@ -1221,7 +1222,7 @@
      public float getYHeadRot() {
          return this.yHeadRot;
      }
-@@ -3483,8 +3978,31 @@
+@@ -3483,8 +3979,31 @@
                  this.releaseUsingItem();
              } else {
                  if (!this.useItem.isEmpty() && this.isUsingItem()) {
@@ -1254,7 +1255,7 @@
                      if (itemstack != this.useItem) {
                          this.setItemInHand(enumhand, itemstack);
                      }
-@@ -3568,12 +4086,18 @@
+@@ -3568,12 +4087,18 @@
      }
  
      public boolean randomTeleport(double x, double y, double z, boolean particleEffects) {
@@ -1275,7 +1276,7 @@
          Level world = this.level();
  
          if (world.hasChunkAt(blockposition)) {
-@@ -3592,18 +4116,43 @@
+@@ -3592,18 +4117,43 @@
              }
  
              if (flag2) {
@@ -1323,7 +1324,7 @@
                  world.broadcastEntityEvent(this, (byte) 46);
              }
  
-@@ -3613,7 +4162,7 @@
+@@ -3613,7 +4163,7 @@
                  entitycreature.getNavigation().stop();
              }
  
@@ -1332,7 +1333,7 @@
          }
      }
  
-@@ -3706,7 +4255,7 @@
+@@ -3706,7 +4256,7 @@
      }
  
      public void stopSleeping() {
@@ -1341,7 +1342,7 @@
          Level world = this.level();
  
          java.util.Objects.requireNonNull(world);
-@@ -3718,9 +4267,9 @@
+@@ -3718,9 +4268,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(() -> {
@@ -1353,7 +1354,7 @@
                  });
                  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 +4289,7 @@
+@@ -3740,7 +4290,7 @@
  
      @Nullable
      public Direction getBedOrientation() {
@@ -1362,7 +1363,7 @@
  
          return blockposition != null ? BedBlock.getBedOrientation(this.level(), blockposition) : null;
      }
-@@ -3905,7 +4454,7 @@
+@@ -3905,7 +4455,7 @@
      public float maxUpStep() {
          float f = (float) this.getAttributeValue(Attributes.STEP_HEIGHT);
  
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 946cf370ee..805865ea9e 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
@@ -29,14 +29,13 @@
  public class RecipeManager extends SimplePreparableReloadListener<RecipeMap> implements RecipeAccess {
  
      private static final Logger LOGGER = LogUtils.getLogger();
-@@ -109,9 +114,27 @@
-     protected void apply(RecipeMap prepared, ResourceManager manager, ProfilerFiller profiler) {
-         this.recipes = prepared;
+@@ -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();
 +    }
@@ -49,15 +48,15 @@
 +
 +            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 +153,7 @@
+@@ -130,7 +154,7 @@
                      StonecutterRecipe recipestonecutting = (StonecutterRecipe) irecipe;
  
                      if (RecipeManager.isIngredientEnabled(features, recipestonecutting.input()) && recipestonecutting.resultDisplay().isEnabled(features)) {
@@ -66,7 +65,7 @@
                      }
                  }
  
-@@ -172,7 +195,10 @@
+@@ -172,7 +196,10 @@
      }
  
      public <I extends RecipeInput, T extends Recipe<I>> Optional<RecipeHolder<T>> getRecipeFor(RecipeType<T> type, I input, Level world) {
@@ -78,7 +77,7 @@
      }
  
      public Optional<RecipeHolder<?>> byKey(ResourceKey<Recipe<?>> key) {
-@@ -183,7 +209,7 @@
+@@ -183,7 +210,7 @@
      private <T extends Recipe<?>> RecipeHolder<T> byKeyTyped(RecipeType<T> type, ResourceKey<Recipe<?>> key) {
          RecipeHolder<?> recipeholder = this.recipes.byKey(key);
  
@@ -87,7 +86,7 @@
      }
  
      public Map<ResourceKey<RecipePropertySet>, RecipePropertySet> getSynchronizedItemProperties() {
-@@ -231,6 +257,22 @@
+@@ -231,6 +258,22 @@
          return new RecipeHolder<>(key, irecipe);
      }
  
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 ec2972a8ed..cbf17a547b 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
@@ -17,9 +17,14 @@
  
  public abstract class BlockBehaviour implements FeatureElement {
  
-@@ -158,6 +161,12 @@
+@@ -156,9 +159,18 @@
  
-     protected void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) {}
+     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) {
++        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) {
@@ -28,9 +33,11 @@
 +    // CraftBukkit end
 +
      protected void onRemove(BlockState state, Level world, BlockPos pos, BlockState newState, boolean moved) {
++        org.spigotmc.AsyncCatcher.catchOp("block remove"); // Spigot
          if (state.hasBlockEntity() && !state.is(newState.getBlock())) {
              world.removeBlockEntity(pos);
-@@ -174,8 +183,10 @@
+         }
+@@ -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());
  
@@ -43,7 +50,7 @@
                  }
  
                  state.spawnAfterBreak(world, pos, ItemStack.EMPTY, flag);
-@@ -1125,9 +1136,15 @@
+@@ -1125,9 +1139,15 @@
          }
  
          public void onPlace(Level world, BlockPos pos, BlockState state, boolean notify) {
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 41590d8d87..493c31f046 100644
--- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -898,6 +898,7 @@ public final class CraftServer implements Server {
     public boolean dispatchCommand(CommandSender sender, String commandLine) {
         Preconditions.checkArgument(sender != null, "sender cannot be null");
         Preconditions.checkArgument(commandLine != null, "commandLine cannot be null");
+        org.spigotmc.AsyncCatcher.catchOp("command dispatch"); // Spigot
 
         if (this.commandMap.dispatch(sender, commandLine)) {
             return true;
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 0870eefe60..085bb29a15 100644
--- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -283,6 +283,7 @@ public class CraftWorld extends CraftRegionAccessor implements World {
 
     @Override
     public boolean unloadChunkRequest(int x, int z) {
+        org.spigotmc.AsyncCatcher.catchOp("chunk unload"); // Spigot
         if (this.isChunkLoaded(x, z)) {
             this.world.getChunkSource().removeRegionTicket(TicketType.PLUGIN, new ChunkPos(x, z), 1, Unit.INSTANCE);
         }
@@ -291,6 +292,7 @@ public class CraftWorld extends CraftRegionAccessor implements World {
     }
 
     private boolean unloadChunk0(int x, int z, boolean save) {
+        org.spigotmc.AsyncCatcher.catchOp("chunk unload"); // Spigot
         if (!this.isChunkLoaded(x, z)) {
             return true;
         }
@@ -307,6 +309,7 @@ public class CraftWorld extends CraftRegionAccessor implements World {
 
     @Override
     public boolean regenerateChunk(int x, int z) {
+        org.spigotmc.AsyncCatcher.catchOp("chunk regenerate"); // Spigot
         throw new UnsupportedOperationException("Not supported in this Minecraft version! Unless you can fix it, this is not a bug :)");
         /*
         if (!unloadChunk0(x, z, false)) {
@@ -384,6 +387,7 @@ public class CraftWorld extends CraftRegionAccessor implements World {
 
     @Override
     public boolean loadChunk(int x, int z, boolean generate) {
+        org.spigotmc.AsyncCatcher.catchOp("chunk load"); // Spigot
         ChunkAccess chunk = this.world.getChunkSource().getChunk(x, z, generate ? ChunkStatus.FULL : ChunkStatus.EMPTY, true);
 
         // If generate = false, but the chunk already exists, we will get this back.
@@ -919,6 +923,7 @@ public class CraftWorld extends CraftRegionAccessor implements World {
 
     @Override
     public Collection<Entity> getNearbyEntities(BoundingBox boundingBox, Predicate<? super Entity> filter) {
+        org.spigotmc.AsyncCatcher.catchOp("getNearbyEntities"); // Spigot
         Preconditions.checkArgument(boundingBox != null, "BoundingBox cannot be null");
 
         AABB bb = new AABB(boundingBox.getMinX(), boundingBox.getMinY(), boundingBox.getMinZ(), boundingBox.getMaxX(), boundingBox.getMaxY(), boundingBox.getMaxZ());
@@ -1073,6 +1078,7 @@ public class CraftWorld extends CraftRegionAccessor implements World {
 
     @Override
     public void save() {
+        org.spigotmc.AsyncCatcher.catchOp("world save"); // Spigot
         this.server.checkSaveState();
         boolean oldSave = this.world.noSave;
 
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 e8fba1c702..daa49fdcb7 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
@@ -228,6 +228,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity {
     @Override
     public List<org.bukkit.entity.Entity> getNearbyEntities(double x, double y, double z) {
         Preconditions.checkState(!this.entity.generation, "Cannot get nearby entities during world generation");
+        org.spigotmc.AsyncCatcher.catchOp("getNearbyEntities"); // Spigot
 
         List<Entity> notchEntityList = this.entity.level().getEntities(this.entity, this.entity.getBoundingBox().inflate(x, y, z), Predicates.alwaysTrue());
         List<org.bukkit.entity.Entity> bukkitEntityList = new java.util.ArrayList<org.bukkit.entity.Entity>(notchEntityList.size());
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 b77e2990e3..624e8b948f 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
@@ -492,6 +492,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
 
     @Override
     public void kickPlayer(String message) {
+        org.spigotmc.AsyncCatcher.catchOp("player kick"); // Spigot
         this.getHandle().transferCookieConnection.kickPlayer(CraftChatMessage.fromStringOrEmpty(message, true));
     }
 
diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java b/paper-server/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java
index ffd5ed8c42..40e348cae0 100644
--- a/paper-server/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java
+++ b/paper-server/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java
@@ -41,6 +41,7 @@ public final class CraftScoreboardManager implements ScoreboardManager {
 
     @Override
     public CraftScoreboard getNewScoreboard() {
+        org.spigotmc.AsyncCatcher.catchOp("scoreboard creation"); // Spigot
         CraftScoreboard scoreboard = new CraftScoreboard(new ServerScoreboard(this.server));
         this.scoreboards.add(scoreboard);
         return scoreboard;
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 2c65f3da1e..8390f5b5b9 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,6 +12,7 @@ public class ServerShutdownThread extends Thread {
     @Override
     public void run() {
         try {
+            org.spigotmc.AsyncCatcher.enabled = false; // Spigot
             this.server.close();
         } finally {
             try {
diff --git a/paper-server/src/main/java/org/spigotmc/AsyncCatcher.java b/paper-server/src/main/java/org/spigotmc/AsyncCatcher.java
new file mode 100644
index 0000000000..bbf0d9d9c4
--- /dev/null
+++ b/paper-server/src/main/java/org/spigotmc/AsyncCatcher.java
@@ -0,0 +1,17 @@
+package org.spigotmc;
+
+import net.minecraft.server.MinecraftServer;
+
+public class AsyncCatcher
+{
+
+    public static boolean enabled = true;
+
+    public static void catchOp(String reason)
+    {
+        if ( AsyncCatcher.enabled && Thread.currentThread() != MinecraftServer.getServer().serverThread )
+        {
+            throw new IllegalStateException( "Asynchronous " + reason + "!" );
+        }
+    }
+}