diff --git a/patches/api/Add-Ban-Methods-to-Player-Objects.patch b/patches/api/Add-Ban-Methods-to-Player-Objects.patch
index c770bfca60..80a67b5d1d 100644
--- a/patches/api/Add-Ban-Methods-to-Player-Objects.patch
+++ b/patches/api/Add-Ban-Methods-to-Player-Objects.patch
@@ -77,17 +77,6 @@ diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/buk
 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 --- a/src/main/java/org/bukkit/entity/Player.java
 +++ b/src/main/java/org/bukkit/entity/Player.java
-@@ -0,0 +0,0 @@ import java.util.UUID;
- import com.destroystokyo.paper.Title; // Paper
- import net.kyori.adventure.text.Component;
- import com.destroystokyo.paper.profile.PlayerProfile; // Paper
-+import java.util.Date; // Paper
-+import org.bukkit.BanEntry; // Paper
-+import org.bukkit.BanList; // Paper
-+import org.bukkit.Bukkit; // Paper
- import org.bukkit.DyeColor;
- import org.bukkit.Effect;
- import org.bukkit.GameMode;
 @@ -0,0 +0,0 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
      public void sendMap(@NotNull MapView map);
  
@@ -100,7 +89,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +     */
 +    // For reference, Bukkit defines this as nullable, while they impl isn't, we'll follow API.
 +    @Nullable
-+    public default BanEntry banPlayerFull(@Nullable String reason) {
++    public default org.bukkit.BanEntry banPlayerFull(@Nullable String reason) {
 +        return banPlayerFull(reason, null, null);
 +    }
 +
@@ -112,7 +101,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +     * @return Ban Entry
 +     */
 +    @Nullable
-+    public default BanEntry banPlayerFull(@Nullable String reason, @Nullable String source) {
++    public default org.bukkit.BanEntry banPlayerFull(@Nullable String reason, @Nullable String source) {
 +        return banPlayerFull(reason, null, source);
 +    }
 +
@@ -124,7 +113,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +     * @return Ban Entry
 +     */
 +    @Nullable
-+    public default BanEntry banPlayerFull(@Nullable String reason, @Nullable Date expires) {
++    public default org.bukkit.BanEntry banPlayerFull(@Nullable String reason, @Nullable java.util.Date expires) {
 +        return banPlayerFull(reason, expires, null);
 +    }
 +
@@ -137,102 +126,102 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +     * @return Ban Entry
 +     */
 +    @Nullable
-+    public default BanEntry banPlayerFull(@Nullable String reason, @Nullable Date expires, @Nullable String source) {
++    public default org.bukkit.BanEntry banPlayerFull(@Nullable String reason, @Nullable java.util.Date expires, @Nullable String source) {
 +        banPlayer(reason, expires, source);
 +        return banPlayerIP(reason, expires, source, true);
 +    }
 +
 +    /**
 +     * Permanently Bans the IP address currently used by the player.
-+     * Does not ban the Profile, use {@link #banPlayerFull(String, Date, String)}
++     * Does not ban the Profile, use {@link #banPlayerFull(String, java.util.Date, String)}
 +     *
 +     * @param reason Reason for ban
 +     * @param kickPlayer Whether or not to kick the player afterwards
 +     * @return Ban Entry
 +     */
 +    @Nullable
-+    public default BanEntry banPlayerIP(@Nullable String reason, boolean kickPlayer) {
++    public default org.bukkit.BanEntry banPlayerIP(@Nullable String reason, boolean kickPlayer) {
 +        return banPlayerIP(reason, null, null, kickPlayer);
 +    }
 +
 +    /**
 +     * Permanently Bans the IP address currently used by the player.
-+     * Does not ban the Profile, use {@link #banPlayerFull(String, Date, String)}
++     * Does not ban the Profile, use {@link #banPlayerFull(String, java.util.Date, String)}
 +     * @param reason Reason for ban
 +     * @param source Source of ban, or null for default
 +     * @param kickPlayer Whether or not to kick the player afterwards
 +     * @return Ban Entry
 +     */
 +    @Nullable
-+    public default BanEntry banPlayerIP(@Nullable String reason, @Nullable String source, boolean kickPlayer) {
++    public default org.bukkit.BanEntry banPlayerIP(@Nullable String reason, @Nullable String source, boolean kickPlayer) {
 +        return banPlayerIP(reason, null, source, kickPlayer);
 +    }
 +
 +    /**
 +     * Bans the IP address currently used by the player.
-+     * Does not ban the Profile, use {@link #banPlayerFull(String, Date, String)}
++     * Does not ban the Profile, use {@link #banPlayerFull(String, java.util.Date, String)}
 +     * @param reason Reason for Ban
 +     * @param expires When to expire the ban
 +     * @param kickPlayer Whether or not to kick the player afterwards
 +     * @return Ban Entry
 +     */
 +    @Nullable
-+    public default BanEntry banPlayerIP(@Nullable String reason, @Nullable Date expires, boolean kickPlayer) {
++    public default org.bukkit.BanEntry banPlayerIP(@Nullable String reason, @Nullable java.util.Date expires, boolean kickPlayer) {
 +        return banPlayerIP(reason, expires, null, kickPlayer);
 +    }
 +
 +    /**
 +     * Permanently Bans the IP address currently used by the player.
-+     * Does not ban the Profile, use {@link #banPlayerFull(String, Date, String)}
++     * Does not ban the Profile, use {@link #banPlayerFull(String, java.util.Date, String)}
 +     *
 +     * @param reason Reason for ban
 +     * @return Ban Entry
 +     */
 +    @Nullable
-+    public default BanEntry banPlayerIP(@Nullable String reason) {
++    public default org.bukkit.BanEntry banPlayerIP(@Nullable String reason) {
 +        return banPlayerIP(reason, null, null);
 +    }
 +
 +    /**
 +     * Permanently Bans the IP address currently used by the player.
-+     * Does not ban the Profile, use {@link #banPlayerFull(String, Date, String)}
++     * Does not ban the Profile, use {@link #banPlayerFull(String, java.util.Date, String)}
 +     * @param reason Reason for ban
 +     * @param source Source of ban, or null for default
 +     * @return Ban Entry
 +     */
 +    @Nullable
-+    public default BanEntry banPlayerIP(@Nullable String reason, @Nullable String source) {
++    public default org.bukkit.BanEntry banPlayerIP(@Nullable String reason, @Nullable String source) {
 +        return banPlayerIP(reason, null, source);
 +    }
 +
 +    /**
 +     * Bans the IP address currently used by the player.
-+     * Does not ban the Profile, use {@link #banPlayerFull(String, Date, String)}
++     * Does not ban the Profile, use {@link #banPlayerFull(String, java.util.Date, String)}
 +     * @param reason Reason for Ban
 +     * @param expires When to expire the ban
 +     * @return Ban Entry
 +     */
 +    @Nullable
-+    public default BanEntry banPlayerIP(@Nullable String reason, @Nullable Date expires) {
++    public default org.bukkit.BanEntry banPlayerIP(@Nullable String reason, @Nullable java.util.Date expires) {
 +        return banPlayerIP(reason, expires, null);
 +    }
 +
 +    /**
 +     * Bans the IP address currently used by the player.
-+     * Does not ban the Profile, use {@link #banPlayerFull(String, Date, String)}
++     * Does not ban the Profile, use {@link #banPlayerFull(String, java.util.Date, String)}
 +     * @param reason Reason for Ban
 +     * @param expires When to expire the ban
 +     * @param source Source of the banm or null for default
 +     * @return Ban Entry
 +     */
 +    @Nullable
-+    public default BanEntry banPlayerIP(@Nullable String reason, @Nullable Date expires, @Nullable String source) {
++    public default org.bukkit.BanEntry banPlayerIP(@Nullable String reason, @Nullable java.util.Date expires, @Nullable String source) {
 +        return banPlayerIP(reason, expires, source, true);
 +    }
 +
 +    /**
 +     * Bans the IP address currently used by the player.
-+     * Does not ban the Profile, use {@link #banPlayerFull(String, Date, String)}
++     * Does not ban the Profile, use {@link #banPlayerFull(String, java.util.Date, String)}
 +     * @param reason Reason for Ban
 +     * @param expires When to expire the ban
 +     * @param source Source of the banm or null for default
@@ -240,8 +229,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +     * @return Ban Entry
 +     */
 +    @Nullable
-+    public default BanEntry banPlayerIP(@Nullable String reason, @Nullable Date expires, @Nullable String source, boolean kickPlayer) {
-+        BanEntry banEntry = Bukkit.getServer().getBanList(BanList.Type.IP).addBan(getAddress().getAddress().getHostAddress(), reason, expires, source);
++    public default org.bukkit.BanEntry banPlayerIP(@Nullable String reason, @Nullable java.util.Date expires, @Nullable String source, boolean kickPlayer) {
++        org.bukkit.BanEntry banEntry = org.bukkit.Bukkit.getServer().getBanList(org.bukkit.BanList.Type.IP).addBan(getAddress().getAddress().getHostAddress(), reason, expires, source);
 +        if (kickPlayer && isOnline()) {
 +            getPlayer().kickPlayer(reason);
 +        }
diff --git a/patches/api/Add-Player-Client-Options-API.patch b/patches/api/Add-Player-Client-Options-API.patch
index 837ec2b7ba..5c6fa9fd3a 100644
--- a/patches/api/Add-Player-Client-Options-API.patch
+++ b/patches/api/Add-Player-Client-Options-API.patch
@@ -203,7 +203,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +import com.destroystokyo.paper.ClientOption; // Paper
  import com.destroystokyo.paper.Title; // Paper
  import net.kyori.adventure.text.Component;
- import com.destroystokyo.paper.profile.PlayerProfile; // Paper
+ import org.bukkit.DyeColor;
 @@ -0,0 +0,0 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
       * Reset the cooldown counter to 0, effectively starting the cooldown period.
       */
diff --git a/patches/api/Add-setPlayerProfile-API-for-Skulls.patch b/patches/api/Add-setPlayerProfile-API-for-Skulls.patch
index dd76f7eba4..715a1cfb32 100644
--- a/patches/api/Add-setPlayerProfile-API-for-Skulls.patch
+++ b/patches/api/Add-setPlayerProfile-API-for-Skulls.patch
@@ -10,14 +10,6 @@ diff --git a/src/main/java/org/bukkit/block/Skull.java b/src/main/java/org/bukki
 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 --- a/src/main/java/org/bukkit/block/Skull.java
 +++ b/src/main/java/org/bukkit/block/Skull.java
-@@ -0,0 +0,0 @@ import org.bukkit.block.data.BlockData;
- import org.jetbrains.annotations.Contract;
- import org.jetbrains.annotations.NotNull;
- import org.jetbrains.annotations.Nullable;
-+import com.destroystokyo.paper.profile.PlayerProfile; // Paper
- 
- /**
-  * Represents a captured state of a skull block.
 @@ -0,0 +0,0 @@ public interface Skull extends TileState {
       */
      public void setOwningPlayer(@NotNull OfflinePlayer player);
@@ -27,34 +19,38 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +     * Sets this skull to use the supplied Player Profile, which can include textures already prefilled.
 +     * @param profile The profile to set this Skull to use, may not be null
 +     */
-+    void setPlayerProfile(@NotNull PlayerProfile profile);
++    void setPlayerProfile(@NotNull com.destroystokyo.paper.profile.PlayerProfile profile);
 +
 +    /**
-+     * If the skull has an owner, per {@link #hasOwner()}, return the owners {@link PlayerProfile}
++     * If the skull has an owner, per {@link #hasOwner()}, return the owners {@link com.destroystokyo.paper.profile.PlayerProfile}
 +     * @return The profile of the owner, if set
 +     */
-+    @Nullable PlayerProfile getPlayerProfile();
++    @Nullable com.destroystokyo.paper.profile.PlayerProfile getPlayerProfile();
 +    // Paper end
 +
      /**
-      * Gets the rotation of the skull in the world (or facing direction if this
-      * is a wall mounted skull).
+      * Gets the profile of the player who owns the skull. This player profile
+      * may appear as the texture depending on skull type.
+@@ -0,0 +0,0 @@ public interface Skull extends TileState {
+      * @return the profile of the owning player
+      */
+     @Nullable
++    @Deprecated // Paper
+     PlayerProfile getOwnerProfile();
+ 
+     /**
+@@ -0,0 +0,0 @@ public interface Skull extends TileState {
+      * @throws IllegalArgumentException if the profile does not contain the
+      * necessary information
+      */
++    @Deprecated // Paper
+     void setOwnerProfile(@Nullable PlayerProfile profile);
+ 
+     /**
 diff --git a/src/main/java/org/bukkit/inventory/meta/SkullMeta.java b/src/main/java/org/bukkit/inventory/meta/SkullMeta.java
 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 --- a/src/main/java/org/bukkit/inventory/meta/SkullMeta.java
 +++ b/src/main/java/org/bukkit/inventory/meta/SkullMeta.java
-@@ -0,0 +0,0 @@
- package org.bukkit.inventory.meta;
- 
-+import com.destroystokyo.paper.profile.PlayerProfile;
- import org.bukkit.OfflinePlayer;
- import org.jetbrains.annotations.NotNull;
- import org.jetbrains.annotations.Nullable;
- 
-+
- /**
-  * Represents a skull that can have an owner.
-  */
 @@ -0,0 +0,0 @@ public interface SkullMeta extends ItemMeta {
      @Deprecated
      boolean setOwner(@Nullable String owner);
@@ -64,15 +60,31 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +     * Sets this skull to use the supplied Player Profile, which can include textures already prefilled.
 +     * @param profile The profile to set this Skull to use, or null to clear owner
 +     */
-+    void setPlayerProfile(@Nullable PlayerProfile profile);
++    void setPlayerProfile(@Nullable com.destroystokyo.paper.profile.PlayerProfile profile);
 +
 +    /**
-+     * If the skull has an owner, per {@link #hasOwner()}, return the owners {@link PlayerProfile}
++     * If the skull has an owner, per {@link #hasOwner()}, return the owners {@link com.destroystokyo.paper.profile.PlayerProfile}
 +     * @return The profile of the owner, if set
 +     */
-+    @Nullable PlayerProfile getPlayerProfile();
++    @Nullable com.destroystokyo.paper.profile.PlayerProfile getPlayerProfile();
 +    // Paper end
 +
      /**
       * Gets the owner of the skull.
       *
+@@ -0,0 +0,0 @@ public interface SkullMeta extends ItemMeta {
+      * @return the profile of the owning player
+      */
+     @Nullable
++    @Deprecated // Paper
+     PlayerProfile getOwnerProfile();
+ 
+     /**
+@@ -0,0 +0,0 @@ public interface SkullMeta extends ItemMeta {
+      * @throws IllegalArgumentException if the profile does not contain the
+      * necessary information
+      */
++    @Deprecated // Paper
+     void setOwnerProfile(@Nullable PlayerProfile profile);
+ 
+     @Override
diff --git a/patches/api/Basic-PlayerProfile-API.patch b/patches/api/Basic-PlayerProfile-API.patch
index bd984c2922..e977b2b6d9 100644
--- a/patches/api/Basic-PlayerProfile-API.patch
+++ b/patches/api/Basic-PlayerProfile-API.patch
@@ -16,13 +16,15 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +import java.util.Collection;
 +import java.util.Set;
 +import java.util.UUID;
++
++import org.bukkit.profile.PlayerTextures;
 +import org.jetbrains.annotations.NotNull;
 +import org.jetbrains.annotations.Nullable;
 +
 +/**
 + * Represents a players profile for the game, such as UUID, Name, and textures.
 + */
-+public interface PlayerProfile {
++public interface PlayerProfile extends org.bukkit.profile.PlayerProfile {
 +
 +    /**
 +     * @return The players name, if set
@@ -54,6 +56,24 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    UUID setId(@Nullable UUID uuid);
 +
 +    /**
++     * Gets the {@link PlayerTextures} of this profile.
++     * This will build a snapshot of the current texture data once
++     * requested inside PlayerTextures.
++     *
++     * @return the textures, not <code>null</code>
++     */
++    @NotNull
++    PlayerTextures getTextures();
++
++    /**
++     * Copies the given textures.
++     *
++     * @param textures the textures to copy, or <code>null</code> to clear the
++     * textures
++     */
++    void setTextures(@Nullable PlayerTextures textures);
++
++    /**
 +     * @return A Mutable set of this players properties, such as textures.
 +     * Values specified here are subject to implementation details.
 +     */
diff --git a/patches/api/Expose-attack-cooldown-methods-for-Player.patch b/patches/api/Expose-attack-cooldown-methods-for-Player.patch
index 37089406c3..de20390c3f 100644
--- a/patches/api/Expose-attack-cooldown-methods-for-Player.patch
+++ b/patches/api/Expose-attack-cooldown-methods-for-Player.patch
@@ -11,7 +11,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 @@ -0,0 +0,0 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
       * @param profile The new profile to use
       */
-     void setPlayerProfile(@NotNull PlayerProfile profile);
+     void setPlayerProfile(@NotNull com.destroystokyo.paper.profile.PlayerProfile profile);
 +
 +    /**
 +     * Returns the amount of ticks the current cooldown lasts
diff --git a/patches/api/Player.setPlayerProfile-API.patch b/patches/api/Player.setPlayerProfile-API.patch
index a99e290d5e..bf7fae788a 100644
--- a/patches/api/Player.setPlayerProfile-API.patch
+++ b/patches/api/Player.setPlayerProfile-API.patch
@@ -5,18 +5,66 @@ Subject: [PATCH] Player.setPlayerProfile API
 
 This can be useful for changing name or skins after a player has logged in.
 
+diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/org/bukkit/Bukkit.java
++++ b/src/main/java/org/bukkit/Bukkit.java
+@@ -0,0 +0,0 @@ public final class Bukkit {
+      * <code>null</code> and the name is <code>null</code> or blank
+      */
+     @NotNull
++    @Deprecated // Paper
+     public static PlayerProfile createPlayerProfile(@Nullable UUID uniqueId, @Nullable String name) {
+         return server.createPlayerProfile(uniqueId, name);
+     }
+@@ -0,0 +0,0 @@ public final class Bukkit {
+      * @throws IllegalArgumentException if the unique id is <code>null</code>
+      */
+     @NotNull
++    @Deprecated // Paper
+     public static PlayerProfile createPlayerProfile(@NotNull UUID uniqueId) {
+         return server.createPlayerProfile(uniqueId);
+     }
+@@ -0,0 +0,0 @@ public final class Bukkit {
+      * blank
+      */
+     @NotNull
++    @Deprecated // Paper
+     public static PlayerProfile createPlayerProfile(@NotNull String name) {
+         return server.createPlayerProfile(name);
+     }
+diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/org/bukkit/Server.java
++++ b/src/main/java/org/bukkit/Server.java
+@@ -0,0 +0,0 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi
+      * <code>null</code> and the name is <code>null</code> or blank
+      */
+     @NotNull
++    @Deprecated // Paper
+     PlayerProfile createPlayerProfile(@Nullable UUID uniqueId, @Nullable String name);
+ 
+     /**
+@@ -0,0 +0,0 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi
+      * @throws IllegalArgumentException if the unique id is <code>null</code>
+      */
+     @NotNull
++    @Deprecated // Paper
+     PlayerProfile createPlayerProfile(@NotNull UUID uniqueId);
+ 
+     /**
+@@ -0,0 +0,0 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi
+      * blank
+      */
+     @NotNull
++    @Deprecated
+     PlayerProfile createPlayerProfile(@NotNull String name);
+ 
+     /**
 diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java
 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 --- a/src/main/java/org/bukkit/entity/Player.java
 +++ b/src/main/java/org/bukkit/entity/Player.java
-@@ -0,0 +0,0 @@ import java.net.InetSocketAddress;
- import java.util.UUID;
- import com.destroystokyo.paper.Title; // Paper
- import net.kyori.adventure.text.Component;
-+import com.destroystokyo.paper.profile.PlayerProfile; // Paper
- import org.bukkit.DyeColor;
- import org.bukkit.Effect;
- import org.bukkit.GameMode;
 @@ -0,0 +0,0 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
       *         was {@link org.bukkit.event.player.PlayerResourcePackStatusEvent.Status#SUCCESSFULLY_LOADED}
       */
@@ -27,14 +75,36 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +     * @return The players profile object
 +     */
 +    @NotNull
-+    PlayerProfile getPlayerProfile();
++    com.destroystokyo.paper.profile.PlayerProfile getPlayerProfile();
 +
 +    /**
 +     * Changes the PlayerProfile for this player. This will cause this player
 +     * to be reregistered to all clients that can currently see this player
 +     * @param profile The new profile to use
 +     */
-+    void setPlayerProfile(@NotNull PlayerProfile profile);
++    void setPlayerProfile(@NotNull com.destroystokyo.paper.profile.PlayerProfile profile);
      // Paper end
  
      // Spigot start
+diff --git a/src/main/java/org/bukkit/profile/PlayerProfile.java b/src/main/java/org/bukkit/profile/PlayerProfile.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/org/bukkit/profile/PlayerProfile.java
++++ b/src/main/java/org/bukkit/profile/PlayerProfile.java
+@@ -0,0 +0,0 @@ import org.jetbrains.annotations.Nullable;
+  * <p>
+  * New profiles can be created via
+  * {@link Server#createPlayerProfile(UUID, String)}.
++ * @deprecated see {@link com.destroystokyo.paper.profile.PlayerProfile}
+  */
++@Deprecated // Paper
+ public interface PlayerProfile extends Cloneable, ConfigurationSerializable {
+ 
+     /**
+@@ -0,0 +0,0 @@ public interface PlayerProfile extends Cloneable, ConfigurationSerializable {
+      * @return the player's unique id, or <code>null</code> if not available
+      */
+     @Nullable
++    @Deprecated // Paper
+     UUID getUniqueId();
+ 
+     /**
diff --git a/patches/server/Add-Plugin-Tickets-to-API-Chunk-Methods.patch b/patches/server/Add-Plugin-Tickets-to-API-Chunk-Methods.patch
index 29efeb8e31..6a2a6617cd 100644
--- a/patches/server/Add-Plugin-Tickets-to-API-Chunk-Methods.patch
+++ b/patches/server/Add-Plugin-Tickets-to-API-Chunk-Methods.patch
@@ -26,7 +26,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 --- 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 {
-         this.ambientSpawn = this.configuration.getInt("spawn-limits.ambient");
+         this.overrideSpawnLimits();
          console.autosavePeriod = this.configuration.getInt("ticks-per.autosave");
          this.warningState = WarningState.value(this.configuration.getString("settings.deprecated-verbose"));
 -        TicketType.PLUGIN.timeout = this.configuration.getInt("chunk-gc.period-in-ticks");
@@ -35,8 +35,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          this.loadIcon();
      }
 @@ -0,0 +0,0 @@ public final class CraftServer implements Server {
-         this.waterUndergroundCreatureSpawn = this.configuration.getInt("spawn-limits.water-underground-creature");
-         this.ambientSpawn = this.configuration.getInt("spawn-limits.ambient");
+         this.console.setMotd(config.motd);
+         this.overrideSpawnLimits();
          this.warningState = WarningState.value(this.configuration.getString("settings.deprecated-verbose"));
 -        TicketType.PLUGIN.timeout = this.configuration.getInt("chunk-gc.period-in-ticks");
 +        TicketType.PLUGIN.timeout = Math.min(20, configuration.getInt("chunk-gc.period-in-ticks")); // Paper - cap plugin loads to 1 second
diff --git a/patches/server/Add-System.out-err-catcher.patch b/patches/server/Add-System.out-err-catcher.patch
index e9cdbb0598..2b9bcbbbe0 100644
--- a/patches/server/Add-System.out-err-catcher.patch
+++ b/patches/server/Add-System.out-err-catcher.patch
@@ -108,19 +108,11 @@ diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/ja
 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
 +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
-@@ -0,0 +0,0 @@ import com.mojang.serialization.Lifecycle;
- import io.netty.buffer.ByteBuf;
- import io.netty.buffer.ByteBufOutputStream;
- import io.netty.buffer.Unpooled;
-+import io.papermc.paper.logging.SysoutCatcher;
- import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
- import java.awt.image.BufferedImage;
- import java.io.File;
 @@ -0,0 +0,0 @@ public final class CraftServer implements Server {
      public int reloadCount;
      private final io.papermc.paper.datapack.PaperDatapackManager datapackManager; // Paper
      public static Exception excessiveVelEx; // Paper - Velocity warnings
-+    private final SysoutCatcher sysoutCatcher = new SysoutCatcher(); // Paper
++    private final io.papermc.paper.logging.SysoutCatcher sysoutCatcher = new io.papermc.paper.logging.SysoutCatcher(); // Paper
  
      static {
          ConfigurationSerialization.registerClass(CraftOfflinePlayer.class);
diff --git a/patches/server/Add-back-EntityPortalExitEvent.patch b/patches/server/Add-back-EntityPortalExitEvent.patch
index b41726cb12..827e2175eb 100644
--- a/patches/server/Add-back-EntityPortalExitEvent.patch
+++ b/patches/server/Add-back-EntityPortalExitEvent.patch
@@ -12,7 +12,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
              } else {
                  // CraftBukkit start
                  worldserver = shapedetectorshape.world;
-+
 +                // Paper start - Call EntityPortalExitEvent
 +                CraftEntity bukkitEntity = this.getBukkitEntity();
 +                Vec3 position = shapedetectorshape.pos;
@@ -30,10 +29,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +                    velocity = org.bukkit.craftbukkit.util.CraftVector.toNMS(event.getAfter());
 +                }
 +                // Paper end
-+
-                 this.unRide();
-                 // CraftBukkit end
- 
+                 if (worldserver == this.level) {
+                     // SPIGOT-6782: Just move the entity if a plugin changed the world to the one the entity is already in
+                     this.moveTo(shapedetectorshape.pos.x, shapedetectorshape.pos.y, shapedetectorshape.pos.z, shapedetectorshape.yRot, shapedetectorshape.xRot);
 @@ -0,0 +0,0 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, i
  
                  if (entity != null) {
diff --git a/patches/server/Add-command-line-option-to-load-extra-plugin-jars-no.patch b/patches/server/Add-command-line-option-to-load-extra-plugin-jars-no.patch
index a07db7b59c..a847b68040 100644
--- a/patches/server/Add-command-line-option-to-load-extra-plugin-jars-no.patch
+++ b/patches/server/Add-command-line-option-to-load-extra-plugin-jars-no.patch
@@ -45,15 +45,15 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        final List<File> list = new ArrayList<>();
 +        for (final File file : jars) {
 +            if (!file.exists()) {
-+                MinecraftServer.LOGGER.warn("File '{}' specified through 'add-plugin' argument does not exist, cannot load a plugin from it!", file.getAbsolutePath());
++                net.minecraft.server.MinecraftServer.LOGGER.warn("File '{}' specified through 'add-plugin' argument does not exist, cannot load a plugin from it!", file.getAbsolutePath());
 +                continue;
 +            }
 +            if (!file.isFile()) {
-+                MinecraftServer.LOGGER.warn("File '{}' specified through 'add-plugin' argument is not a file, cannot load a plugin from it!", file.getAbsolutePath());
++                net.minecraft.server.MinecraftServer.LOGGER.warn("File '{}' specified through 'add-plugin' argument is not a file, cannot load a plugin from it!", file.getAbsolutePath());
 +                continue;
 +            }
 +            if (!file.getName().endsWith(".jar")) {
-+                MinecraftServer.LOGGER.warn("File '{}' specified through 'add-plugin' argument is not a jar file, cannot load a plugin from it!", file.getAbsolutePath());
++                net.minecraft.server.MinecraftServer.LOGGER.warn("File '{}' specified through 'add-plugin' argument is not a jar file, cannot load a plugin from it!", file.getAbsolutePath());
 +                continue;
 +            }
 +            list.add(file);
diff --git a/patches/server/Add-paper-mobcaps-and-paper-playermobcaps.patch b/patches/server/Add-paper-mobcaps-and-paper-playermobcaps.patch
index 859e67a6de..77ed7d1765 100644
--- a/patches/server/Add-paper-mobcaps-and-paper-playermobcaps.patch
+++ b/patches/server/Add-paper-mobcaps-and-paper-playermobcaps.patch
@@ -213,7 +213,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        sender.sendMessage(Component.join(JoinConfiguration.noSeparators(), Component.text("Mobcaps for player: "), Component.text(player.getName(), NamedTextColor.GREEN)));
 +        sender.sendMessage(this.buildMobcapsComponent(
 +            category -> level.chunkSource.chunkMap.getMobCountNear(serverPlayer, category),
-+            category -> NaturalSpawner.limitForCategory(level, category)
++            category -> level.getWorld().getSpawnLimit(org.bukkit.craftbukkit.util.CraftSpawnCategory.toBukkit(category))
 +        ));
 +    }
 +
@@ -275,67 +275,13 @@ diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/m
 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 {
-             MobCategory enumcreaturetype = aenumcreaturetype[j];
-             // CraftBukkit start - Use per-world spawn limits
-             boolean spawnThisTick = true;
--            int limit = enumcreaturetype.getMaxInstancesPerChunk();
-+            final int limit = limitForCategory(world, enumcreaturetype); // Paper
-             switch (enumcreaturetype) {
--                case MONSTER:
--                    spawnThisTick = spawnMonsterThisTick;
--                    limit = world.getWorld().getMonsterSpawnLimit();
--                    break;
--                case CREATURE:
--                    spawnThisTick = spawnAnimalThisTick;
--                    limit = world.getWorld().getAnimalSpawnLimit();
--                    break;
--                case WATER_CREATURE:
--                    spawnThisTick = spawnWaterThisTick;
--                    limit = world.getWorld().getWaterAnimalSpawnLimit();
--                    break;
--                case UNDERGROUND_WATER_CREATURE:
--                    spawnThisTick = spawnWaterUndergroundCreatureThisTick;
--                    limit = world.getWorld().getWaterUndergroundCreatureSpawnLimit();
--                    break;
--                case AMBIENT:
--                    spawnThisTick = spawnAmbientThisTick;
--                    limit = world.getWorld().getAmbientSpawnLimit();
--                    break;
--                case WATER_AMBIENT:
--                    spawnThisTick = spawnWaterAmbientThisTick;
--                    limit = world.getWorld().getWaterAmbientSpawnLimit();
--                    break;
-+                // Paper start - not mindiff so we get conflict on change
-+                case MONSTER -> spawnThisTick = spawnMonsterThisTick;
-+                case CREATURE -> spawnThisTick = spawnAnimalThisTick;
-+                case WATER_CREATURE -> spawnThisTick = spawnWaterThisTick;
-+                case UNDERGROUND_WATER_CREATURE -> spawnThisTick = spawnWaterUndergroundCreatureThisTick;
-+                case AMBIENT -> spawnThisTick = spawnAmbientThisTick;
-+                case WATER_AMBIENT -> spawnThisTick = spawnWaterAmbientThisTick;
-+                // Paper end
-             }
- 
-             if (!spawnThisTick || limit == 0) {
 @@ -0,0 +0,0 @@ public final class NaturalSpawner {
          world.getProfiler().pop();
      }
  
 +    // Paper start
-+    public static int limitForCategory(final ServerLevel world, final MobCategory enumcreaturetype) {
-+        return switch (enumcreaturetype) {
-+            case MONSTER -> world.getWorld().getMonsterSpawnLimit();
-+            case CREATURE -> world.getWorld().getAnimalSpawnLimit();
-+            case WATER_CREATURE -> world.getWorld().getWaterAnimalSpawnLimit();
-+            case UNDERGROUND_WATER_CREATURE -> world.getWorld().getWaterUndergroundCreatureSpawnLimit();
-+            case AMBIENT -> world.getWorld().getAmbientSpawnLimit();
-+            case WATER_AMBIENT -> world.getWorld().getWaterAmbientSpawnLimit();
-+            default -> enumcreaturetype.getMaxInstancesPerChunk();
-+        };
-+    }
-+
 +    public static int globalLimitForCategory(final ServerLevel level, final MobCategory category, final int spawnableChunkCount) {
-+        final int categoryLimit = limitForCategory(level, category);
++        final int categoryLimit = level.getWorld().getSpawnLimit(CraftSpawnCategory.toBukkit(category));
 +        if (categoryLimit < 1) {
 +            return categoryLimit;
 +        }
diff --git a/patches/server/Add-setPlayerProfile-API-for-Skulls.patch b/patches/server/Add-setPlayerProfile-API-for-Skulls.patch
index f0fbc0242a..194c91b198 100644
--- a/patches/server/Add-setPlayerProfile-API-for-Skulls.patch
+++ b/patches/server/Add-setPlayerProfile-API-for-Skulls.patch
@@ -10,35 +10,43 @@ diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftSkull.java b/src/ma
 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 --- a/src/main/java/org/bukkit/craftbukkit/block/CraftSkull.java
 +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftSkull.java
-@@ -0,0 +0,0 @@
- package org.bukkit.craftbukkit.block;
- 
-+import com.destroystokyo.paper.profile.CraftPlayerProfile;
-+import com.destroystokyo.paper.profile.PlayerProfile;
- import com.google.common.base.Preconditions;
- import com.mojang.authlib.GameProfile;
- import net.minecraft.server.MinecraftServer;
 @@ -0,0 +0,0 @@ public class CraftSkull extends CraftBlockEntityState<SkullBlockEntity> implemen
          }
      }
  
 +    // Paper start
-+    @Override
-+    public void setPlayerProfile(PlayerProfile profile) {
+     @Override
++    public void setPlayerProfile(com.destroystokyo.paper.profile.PlayerProfile profile) {
 +        Preconditions.checkNotNull(profile, "profile");
-+        this.profile = CraftPlayerProfile.asAuthlibCopy(profile);
++        this.profile = com.destroystokyo.paper.profile.CraftPlayerProfile.asAuthlibCopy(profile);
 +    }
 +
 +    @javax.annotation.Nullable
 +    @Override
-+    public PlayerProfile getPlayerProfile() {
-+        return profile != null ? CraftPlayerProfile.asBukkitCopy(profile) : null;
++    public com.destroystokyo.paper.profile.PlayerProfile getPlayerProfile() {
++        return profile != null ? com.destroystokyo.paper.profile.CraftPlayerProfile.asBukkitCopy(profile) : null;
 +    }
 +    // Paper end
 +
++    @Override
++    @Deprecated // Paper
+     public PlayerProfile getOwnerProfile() {
+         if (!this.hasOwner()) {
+             return null;
+@@ -0,0 +0,0 @@ public class CraftSkull extends CraftBlockEntityState<SkullBlockEntity> implemen
+     }
+ 
      @Override
-     public BlockFace getRotation() {
-         BlockData blockData = getBlockData();
++    @Deprecated // Paper
+     public void setOwnerProfile(PlayerProfile profile) {
+         if (profile == null) {
+             this.profile = null;
+         } else {
+-            this.profile = CraftPlayerProfile.validateSkullProfile(((CraftPlayerProfile) profile).buildGameProfile());
++            this.profile = CraftPlayerProfile.validateSkullProfile(((com.destroystokyo.paper.profile.SharedPlayerProfile) profile).buildGameProfile()); // Paper
+         }
+     }
+ 
 diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java
 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 --- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java
@@ -51,14 +59,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 -import net.minecraft.nbt.NbtUtils;
 -import net.minecraft.nbt.Tag;
 -import net.minecraft.world.level.block.entity.SkullBlockEntity;
-+import com.destroystokyo.paper.profile.CraftPlayerProfile;
-+import com.destroystokyo.paper.profile.PlayerProfile;
  import org.bukkit.Bukkit;
  import org.bukkit.Material;
  import org.bukkit.OfflinePlayer;
-@@ -0,0 +0,0 @@ import org.bukkit.craftbukkit.inventory.CraftMetaItem.SerializableMeta;
- import org.bukkit.craftbukkit.util.CraftMagicNumbers;
+@@ -0,0 +0,0 @@ import org.bukkit.craftbukkit.util.CraftMagicNumbers;
  import org.bukkit.inventory.meta.SkullMeta;
+ import org.bukkit.profile.PlayerProfile;
  
 +import javax.annotation.Nullable;
 +import net.minecraft.nbt.CompoundTag;
@@ -74,17 +80,48 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
 +    // Paper start
 +    @Override
-+    public void setPlayerProfile(@Nullable PlayerProfile profile) {
-+        setProfile((profile == null) ? null : CraftPlayerProfile.asAuthlibCopy(profile));
++    public void setPlayerProfile(@Nullable com.destroystokyo.paper.profile.PlayerProfile profile) {
++        setProfile((profile == null) ? null : com.destroystokyo.paper.profile.CraftPlayerProfile.asAuthlibCopy(profile));
 +    }
 +
 +    @Nullable
 +    @Override
-+    public PlayerProfile getPlayerProfile() {
-+        return profile != null ? CraftPlayerProfile.asBukkitCopy(profile) : null;
++    public com.destroystokyo.paper.profile.PlayerProfile getPlayerProfile() {
++        return profile != null ? com.destroystokyo.paper.profile.CraftPlayerProfile.asBukkitCopy(profile) : null;
 +    }
 +    // Paper end
 +
      @Override
      public OfflinePlayer getOwningPlayer() {
          if (this.hasOwner()) {
+@@ -0,0 +0,0 @@ class CraftMetaSkull extends CraftMetaItem implements SkullMeta {
+     }
+ 
+     @Override
++    @Deprecated // Paper
+     public PlayerProfile getOwnerProfile() {
+         if (!this.hasOwner()) {
+             return null;
+@@ -0,0 +0,0 @@ class CraftMetaSkull extends CraftMetaItem implements SkullMeta {
+     }
+ 
+     @Override
++    @Deprecated // Paper
+     public void setOwnerProfile(PlayerProfile profile) {
+         if (profile == null) {
+             this.setProfile(null);
+         } else {
+-            this.setProfile(CraftPlayerProfile.validateSkullProfile(((CraftPlayerProfile) profile).buildGameProfile()));
++            this.setProfile(CraftPlayerProfile.validateSkullProfile(((com.destroystokyo.paper.profile.SharedPlayerProfile) profile).buildGameProfile())); // Paper
+         }
+     }
+ 
+@@ -0,0 +0,0 @@ class CraftMetaSkull extends CraftMetaItem implements SkullMeta {
+     Builder<String, Object> serialize(Builder<String, Object> builder) {
+         super.serialize(builder);
+         if (this.hasOwner()) {
+-            return builder.put(SKULL_OWNER.BUKKIT, new CraftPlayerProfile(this.profile));
++            return builder.put(SKULL_OWNER.BUKKIT, new com.destroystokyo.paper.profile.CraftPlayerProfile(this.profile)); // Paper
+         }
+         return builder;
+     }
diff --git a/patches/server/Adventure.patch b/patches/server/Adventure.patch
index 1696944046..1b518310a7 100644
--- a/patches/server/Adventure.patch
+++ b/patches/server/Adventure.patch
@@ -1844,9 +1844,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 --- 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 {
-     private int waterAmbientSpawn = -1;
-     private int waterUndergroundCreatureSpawn = -1;
-     private int ambientSpawn = -1;
+     private final List<BlockPopulator> populators = new ArrayList<BlockPopulator>();
+     private final BlockMetadataStore blockMetadata = new BlockMetadataStore(this);
+     private final Object2IntOpenHashMap<SpawnCategory> spawnCategoryLimit = new Object2IntOpenHashMap<>();
 +    private net.kyori.adventure.pointer.Pointers adventure$pointers; // Paper - implement pointers
  
      private static final Random rand = new Random();
diff --git a/patches/server/Allow-using-signs-inside-spawn-protection.patch b/patches/server/Allow-using-signs-inside-spawn-protection.patch
index 53f58af1fb..fe8df8bd1f 100644
--- a/patches/server/Allow-using-signs-inside-spawn-protection.patch
+++ b/patches/server/Allow-using-signs-inside-spawn-protection.patch
@@ -23,11 +23,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 --- 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 implements ServerPlayerConnection, Ser
-         int i = this.player.level.getMaxBuildHeight();
+         // Paper end
  
          if (blockposition.getY() < i) {
--            if (this.awaitingPositionFromClient == null && this.player.distanceToSqr((double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D) < 64.0D && worldserver.mayInteract(this.player, blockposition)) {
-+            if (this.awaitingPositionFromClient == null && this.player.distanceToSqr((double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D) < 64.0D && (worldserver.mayInteract(this.player, blockposition) || (worldserver.paperConfig.allowUsingSignsInsideSpawnProtection && worldserver.getBlockState(blockposition).getBlock() instanceof net.minecraft.world.level.block.SignBlock))) { // Paper
-                 // CraftBukkit start - Check if we can actually do something over this large a distance
-                 // Paper - move check up
-                 this.player.stopUsingItem(); // SPIGOT-4706
+-            if (this.awaitingPositionFromClient == null && this.player.distanceToSqr((double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D) < 64.0D && worldserver.mayInteract(this.player, blockposition)) { // CraftBukkit - reuse value // Paper - revert CB change
++            if (this.awaitingPositionFromClient == null && this.player.distanceToSqr((double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D) < 64.0D && (worldserver.mayInteract(this.player, blockposition) || (worldserver.paperConfig.allowUsingSignsInsideSpawnProtection && worldserver.getBlockState(blockposition).getBlock() instanceof net.minecraft.world.level.block.SignBlock))) { // CraftBukkit - reuse value // Paper - revert CB change // Paper - sign check
+                 this.player.stopUsingItem(); // CraftBukkit - SPIGOT-4706
+                 InteractionResult enuminteractionresult = this.player.gameMode.useItemOn(this.player, worldserver, itemstack, enumhand, movingobjectpositionblock);
+ 
diff --git a/patches/server/Basic-PlayerProfile-API.patch b/patches/server/Basic-PlayerProfile-API.patch
index 7107407af2..16d131aeee 100644
--- a/patches/server/Basic-PlayerProfile-API.patch
+++ b/patches/server/Basic-PlayerProfile-API.patch
@@ -15,25 +15,26 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +import com.destroystokyo.paper.PaperConfig;
 +import com.google.common.base.Charsets;
++import com.google.common.collect.Iterables;
 +import com.mojang.authlib.GameProfile;
 +import com.mojang.authlib.properties.Property;
 +import com.mojang.authlib.properties.PropertyMap;
++import net.minecraft.Util;
 +import net.minecraft.server.MinecraftServer;
 +import net.minecraft.server.players.GameProfileCache;
 +import org.apache.commons.lang3.Validate;
 +import org.bukkit.craftbukkit.entity.CraftPlayer;
++import org.bukkit.craftbukkit.profile.CraftPlayerTextures;
++import org.bukkit.craftbukkit.profile.CraftProfileProperty;
++import org.bukkit.profile.PlayerTextures;
++import org.jetbrains.annotations.NotNull;
 +
 +import javax.annotation.Nonnull;
 +import javax.annotation.Nullable;
-+import java.util.AbstractSet;
-+import java.util.Collection;
-+import java.util.Iterator;
-+import java.util.Objects;
-+import java.util.Optional;
-+import java.util.Set;
-+import java.util.UUID;
++import java.util.*;
++import java.util.concurrent.CompletableFuture;
 +
-+public class CraftPlayerProfile implements PlayerProfile {
++public class CraftPlayerProfile implements PlayerProfile, SharedPlayerProfile {
 +
 +    private GameProfile profile;
 +    private final PropertySet properties = new PropertySet();
@@ -64,6 +65,22 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        properties.put(name, new Property(name, property.getValue(), property.getSignature()));
 +    }
 +
++    @Override
++    public CraftPlayerTextures getTextures() {
++        return new CraftPlayerTextures(this);
++    }
++
++    @Override
++    public void setTextures(@Nullable PlayerTextures textures) {
++        if (textures == null) {
++            this.removeProperty("textures");
++        } else {
++            CraftPlayerTextures craftPlayerTextures = new CraftPlayerTextures(this);
++            craftPlayerTextures.copyFrom(textures);
++            craftPlayerTextures.rebuildPropertyIfDirty();
++        }
++    }
++
 +    public GameProfile getGameProfile() {
 +        return profile;
 +    }
@@ -82,6 +99,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        return prev.getId();
 +    }
 +
++    @Override
++    public UUID getUniqueId() {
++        return getId();
++    }
++
 +    @Nullable
 +    @Override
 +    public String getName() {
@@ -117,6 +139,29 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        return !profile.getProperties().removeAll(property).isEmpty();
 +    }
 +
++    @Nullable
++    @Override
++    public Property getProperty(String property) {
++        return Iterables.getFirst(this.profile.getProperties().get(property), null);
++    }
++
++    @Nullable
++    @Override
++    public void setProperty(@NotNull String propertyName, @Nullable Property property) {
++        PropertyMap properties = profile.getProperties();
++        properties.removeAll(propertyName);
++        if (property != null) {
++            properties.put(propertyName, property);
++        }
++    }
++
++    @Override
++    public @NotNull GameProfile buildGameProfile() {
++        GameProfile profile = new GameProfile(this.profile.getId(), this.profile.getName());
++        profile.getProperties().putAll(this.profile.getProperties());
++        return profile;
++    }
++
 +    @Override
 +    public boolean equals(Object o) {
 +        if (this == o) return true;
@@ -148,8 +193,16 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +
 +    @Override
++    public @NotNull CompletableFuture<org.bukkit.profile.PlayerProfile> update() {
++        return CompletableFuture.supplyAsync(() -> {
++            final CraftPlayerProfile clone = clone();
++            clone.complete(true);
++            return clone;
++        }, Util.backgroundExecutor());
++    }
++
++    @Override
 +    public boolean completeFromCache() {
-+        MinecraftServer server = MinecraftServer.getServer();
 +        return completeFromCache(false, PaperConfig.isProxyOnlineMode());
 +    }
 +
@@ -193,12 +246,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +
 +    public boolean complete(boolean textures) {
-+        MinecraftServer server = MinecraftServer.getServer();
 +        return complete(textures, PaperConfig.isProxyOnlineMode());
 +    }
 +    public boolean complete(boolean textures, boolean onlineMode) {
 +        MinecraftServer server = MinecraftServer.getServer();
-+
 +        boolean isCompleteFromCache = this.completeFromCache(true, onlineMode);
 +        if (onlineMode && (!isCompleteFromCache || textures && !hasTextures())) {
 +            GameProfile result = server.getSessionService().fillProfileProperties(profile, true);
@@ -258,6 +309,25 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        return craft.getGameProfile();
 +    }
 +
++    @Override
++    public @NotNull Map<String, Object> serialize() {
++        Map<String, Object> map = new LinkedHashMap<>();
++        if (this.getId() != null) {
++            map.put("uniqueId", this.getId().toString());
++        }
++        if (this.getName() != null) {
++            map.put("name", getName());
++        }
++        if (!this.properties.isEmpty()) {
++            List<Object> propertiesData = new ArrayList<>();
++            for (ProfileProperty property : properties) {
++                propertiesData.add(CraftProfileProperty.serialize(new Property(property.getName(), property.getValue(), property.getSignature())));
++            }
++            map.put("properties", propertiesData);
++        }
++        return map;
++    }
++
 +    private class PropertySet extends AbstractSet<ProfileProperty> {
 +
 +        @Override
@@ -428,6 +498,35 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        super(authenticationService, UUID.randomUUID().toString(), agent);
 +    }
 +}
+diff --git a/src/main/java/com/destroystokyo/paper/profile/SharedPlayerProfile.java b/src/main/java/com/destroystokyo/paper/profile/SharedPlayerProfile.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/java/com/destroystokyo/paper/profile/SharedPlayerProfile.java
+@@ -0,0 +0,0 @@
++package com.destroystokyo.paper.profile;
++
++import com.mojang.authlib.GameProfile;
++import com.mojang.authlib.properties.Property;
++import org.jetbrains.annotations.NotNull;
++import org.jetbrains.annotations.Nullable;
++
++import java.util.UUID;
++
++public interface SharedPlayerProfile {
++
++    @Nullable UUID getUniqueId();
++
++    @Nullable String getName();
++
++    boolean removeProperty(@NotNull String property);
++
++    @Nullable Property getProperty(@NotNull String propertyName);
++
++    @Nullable void setProperty(@NotNull String propertyName, @Nullable Property property);
++
++    @NotNull GameProfile buildGameProfile();
++}
 diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java
 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 --- a/src/main/java/net/minecraft/server/MCUtil.java
@@ -529,21 +628,101 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
      // Paper end
  }
-diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java
+diff --git a/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerProfile.java b/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerProfile.java
 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java
-+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java
-@@ -0,0 +0,0 @@ class CraftMetaSkull extends CraftMetaItem implements SkullMeta {
+--- a/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerProfile.java
++++ b/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerProfile.java
+@@ -0,0 +0,0 @@ import org.bukkit.profile.PlayerProfile;
+ import org.bukkit.profile.PlayerTextures;
+ 
+ @SerializableAs("PlayerProfile")
+-public final class CraftPlayerProfile implements PlayerProfile {
++public final class CraftPlayerProfile implements PlayerProfile, com.destroystokyo.paper.profile.SharedPlayerProfile { // Paper
+ 
+     @Nonnull
+     public static GameProfile validateSkullProfile(@Nonnull GameProfile gameProfile) {
+@@ -0,0 +0,0 @@ public final class CraftPlayerProfile implements PlayerProfile {
      }
  
-     private void setProfile(GameProfile profile) {
-+        // Paper start
-+        if (profile != null) {
-+            com.destroystokyo.paper.profile.CraftPlayerProfile paperProfile = new com.destroystokyo.paper.profile.CraftPlayerProfile(profile);
-+            paperProfile.completeFromCache(false, true);
-+            profile = paperProfile.getGameProfile();
-+        }
-+        // Paper end
-         this.profile = profile;
-         this.serializedProfile = (profile == null) ? null : NbtUtils.writeGameProfile(new CompoundTag(), profile);
+     @Nullable
+-    Property getProperty(String propertyName) {
++    public Property getProperty(String propertyName) { // Paper - public
+         return Iterables.getFirst(this.properties.get(propertyName), null);
      }
+ 
+-    void setProperty(String propertyName, @Nullable Property property) {
++    public void setProperty(String propertyName, @Nullable Property property) { // Paper - public
+         // Assert: (property == null) || property.getName().equals(propertyName)
+         this.removeProperty(propertyName);
+         if (property != null) {
+@@ -0,0 +0,0 @@ public final class CraftPlayerProfile implements PlayerProfile {
+         }
+     }
+ 
+-    void removeProperty(String propertyName) {
+-        this.properties.removeAll(propertyName);
++    // Paper start - change return value for shared interface
++    public boolean removeProperty(String propertyName) {
++        return !this.properties.removeAll(propertyName).isEmpty();
++        // Paper end
+     }
+ 
+     void rebuildDirtyProperties() {
+diff --git a/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerTextures.java b/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerTextures.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerTextures.java
++++ b/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerTextures.java
+@@ -0,0 +0,0 @@ import javax.annotation.Nullable;
+ import org.bukkit.craftbukkit.util.JsonHelper;
+ import org.bukkit.profile.PlayerTextures;
+ 
+-final class CraftPlayerTextures implements PlayerTextures {
++public final class CraftPlayerTextures implements PlayerTextures { // Paper - public
+ 
+     static final String PROPERTY_NAME = "textures";
+     private static final String MINECRAFT_HOST = "textures.minecraft.net";
+@@ -0,0 +0,0 @@ final class CraftPlayerTextures implements PlayerTextures {
+         }
+     }
+ 
+-    private final CraftPlayerProfile profile;
++    private final com.destroystokyo.paper.profile.SharedPlayerProfile profile; // Paper
+ 
+     // The textures data is loaded lazily:
+     private boolean loaded = false;
+@@ -0,0 +0,0 @@ final class CraftPlayerTextures implements PlayerTextures {
+     // GameProfiles (even if these modifications are later reverted).
+     private boolean dirty = false;
+ 
+-    CraftPlayerTextures(@Nonnull CraftPlayerProfile profile) {
++    public CraftPlayerTextures(@Nonnull com.destroystokyo.paper.profile.SharedPlayerProfile profile) { // Paper
+         this.profile = profile;
+     }
+ 
+-    void copyFrom(@Nonnull PlayerTextures other) {
++    public void copyFrom(@Nonnull PlayerTextures other) { // Paper - public
+         if (other == this) return;
+         Preconditions.checkArgument(other instanceof CraftPlayerTextures, "Expecting CraftPlayerTextures, got %s", other.getClass().getName());
+         CraftPlayerTextures otherTextures = (CraftPlayerTextures) other;
+@@ -0,0 +0,0 @@ final class CraftPlayerTextures implements PlayerTextures {
+         return this.profile.getProperty(PROPERTY_NAME);
+     }
+ 
+-    void rebuildPropertyIfDirty() {
++    public void rebuildPropertyIfDirty() { // Paper - public
+         if (!this.dirty) return;
+         // Assert: loaded
+         this.dirty = false;
+diff --git a/src/main/java/org/bukkit/craftbukkit/profile/CraftProfileProperty.java b/src/main/java/org/bukkit/craftbukkit/profile/CraftProfileProperty.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/org/bukkit/craftbukkit/profile/CraftProfileProperty.java
++++ b/src/main/java/org/bukkit/craftbukkit/profile/CraftProfileProperty.java
+@@ -0,0 +0,0 @@ import javax.annotation.Nullable;
+ import org.apache.commons.io.IOUtils;
+ import org.bukkit.craftbukkit.configuration.ConfigSerializationUtil;
+ 
+-final class CraftProfileProperty {
++public final class CraftProfileProperty { // Paper - public
+ 
+     /**
+      * Different JSON formatting styles to use for encoded property values.
diff --git a/patches/server/Build-system-changes.patch b/patches/server/Build-system-changes.patch
index 7bd30d6e21..09ada170fb 100644
--- a/patches/server/Build-system-changes.patch
+++ b/patches/server/Build-system-changes.patch
@@ -50,7 +50,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +                    Date buildDate = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z").parse(Main.class.getPackage().getImplementationVendor()); // Paper
  
                      Calendar deadline = Calendar.getInstance();
-                     deadline.add(Calendar.DAY_OF_YEAR, -14);
+                     deadline.add(Calendar.DAY_OF_YEAR, -28);
 diff --git a/src/main/java/org/bukkit/craftbukkit/util/Versioning.java b/src/main/java/org/bukkit/craftbukkit/util/Versioning.java
 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 --- a/src/main/java/org/bukkit/craftbukkit/util/Versioning.java
diff --git a/patches/server/Complete-resource-pack-API.patch b/patches/server/Complete-resource-pack-API.patch
index 0d986f72d3..6c416ac936 100644
--- a/patches/server/Complete-resource-pack-API.patch
+++ b/patches/server/Complete-resource-pack-API.patch
@@ -26,9 +26,9 @@ diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/
 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 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 @@ import org.bukkit.metadata.MetadataValue;
- import org.bukkit.plugin.Plugin;
+@@ -0,0 +0,0 @@ import org.bukkit.plugin.Plugin;
  import org.bukkit.plugin.messaging.StandardMessenger;
+ import org.bukkit.profile.PlayerProfile;
  import org.bukkit.scoreboard.Scoreboard;
 +import org.jetbrains.annotations.NotNull;
  
diff --git a/patches/server/Entity-Activation-Range-2.0.patch b/patches/server/Entity-Activation-Range-2.0.patch
index f8c1338322..f9c6d933f6 100644
--- a/patches/server/Entity-Activation-Range-2.0.patch
+++ b/patches/server/Entity-Activation-Range-2.0.patch
@@ -307,9 +307,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 --- 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 {
-     public long ticksPerWaterAmbientSpawns;
-     public long ticksPerWaterUndergroundCreatureSpawns;
-     public long ticksPerAmbientSpawns;
+     public Map<BlockPos, BlockEntity> capturedTileEntities = new HashMap<>();
+     public List<ItemEntity> captureDrops;
+     public final it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap<SpawnCategory> ticksPerSpawnCategory = new it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap<>();
 +    // Paper start
 +    public int wakeupInactiveRemainingAnimals;
 +    public int wakeupInactiveRemainingFlying;
diff --git a/patches/server/Fix-Per-World-Difficulty-Remembering-Difficulty.patch b/patches/server/Fix-Per-World-Difficulty-Remembering-Difficulty.patch
index 45763fd2d9..7413d1c777 100644
--- a/patches/server/Fix-Per-World-Difficulty-Remembering-Difficulty.patch
+++ b/patches/server/Fix-Per-World-Difficulty-Remembering-Difficulty.patch
@@ -118,6 +118,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 -            world.setSpawnSettings(config.spawnMonsters, config.spawnAnimals);
 +            // world.serverLevelData.setDifficulty(config.difficulty); // Paper - per level difficulty
 +            world.setSpawnSettings(world.serverLevelData.getDifficulty() != Difficulty.PEACEFUL && config.spawnMonsters, config.spawnAnimals); // Paper - per level difficulty (from MinecraftServer#setDifficulty(ServerLevel, Difficulty, boolean))
-             if (this.getTicksPerAnimalSpawns() < 0) {
-                 world.ticksPerAnimalSpawns = 400;
-             } else {
+ 
+             for (SpawnCategory spawnCategory : SpawnCategory.values()) {
+                 if (CraftSpawnCategory.isValidForLimits(spawnCategory)) {
diff --git a/patches/server/Fix-this-stupid-bullshit.patch b/patches/server/Fix-this-stupid-bullshit.patch
index 08c03dba17..0172efd719 100644
--- a/patches/server/Fix-this-stupid-bullshit.patch
+++ b/patches/server/Fix-this-stupid-bullshit.patch
@@ -36,7 +36,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +++ b/src/main/java/org/bukkit/craftbukkit/Main.java
 @@ -0,0 +0,0 @@ public class Main {
                      Calendar deadline = Calendar.getInstance();
-                     deadline.add(Calendar.DAY_OF_YEAR, -14);
+                     deadline.add(Calendar.DAY_OF_YEAR, -28);
                      if (buildDate.before(deadline.getTime())) {
 -                        System.err.println("*** Error, this build is outdated ***");
 +                        // Paper start - This is some stupid bullshit
diff --git a/patches/server/Implement-regenerateChunk.patch b/patches/server/Implement-regenerateChunk.patch
index 2313a91952..0706ea5033 100644
--- a/patches/server/Implement-regenerateChunk.patch
+++ b/patches/server/Implement-regenerateChunk.patch
@@ -25,10 +25,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 -        /*
 -        if (!unloadChunk0(x, z, false)) {
 -            return false;
--        }
--
--        final long chunkKey = ChunkCoordIntPair.pair(x, z);
--        world.getChunkProvider().unloadQueue.remove(chunkKey);
 +        // Paper start - implement regenerateChunk method
 +        final ServerLevel serverLevel = this.world;
 +        final net.minecraft.server.level.ServerChunkCache serverChunkCache = serverLevel.getChunkSource();
@@ -36,9 +32,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        final net.minecraft.world.level.chunk.LevelChunk levelChunk = serverChunkCache.getChunk(chunkPos.x, chunkPos.z, true);
 +        for (final BlockPos blockPos : BlockPos.betweenClosed(chunkPos.getMinBlockX(), serverLevel.getMinBuildHeight(), chunkPos.getMinBlockZ(), chunkPos.getMaxBlockX(), serverLevel.getMaxBuildHeight() - 1, chunkPos.getMaxBlockZ())) {
 +            levelChunk.removeBlockEntity(blockPos);
-+            serverLevel.setBlock(blockPos, Blocks.AIR.defaultBlockState(), 16);
-+        }
-+
++            serverLevel.setBlock(blockPos, net.minecraft.world.level.block.Blocks.AIR.defaultBlockState(), 16);
+         }
+ 
+-        final long chunkKey = ChunkCoordIntPair.pair(x, z);
+-        world.getChunkProvider().unloadQueue.remove(chunkKey);
 +        for (final ChunkStatus chunkStatus : REGEN_CHUNK_STATUSES) {
 +            final List<ChunkAccess> list = new ArrayList<>();
 +            final int range = Math.max(1, chunkStatus.getRange());
diff --git a/patches/server/Lag-compensate-block-breaking.patch b/patches/server/Lag-compensate-block-breaking.patch
index e9ab8e692c..1d217fdaf8 100644
--- a/patches/server/Lag-compensate-block-breaking.patch
+++ b/patches/server/Lag-compensate-block-breaking.patch
@@ -136,9 +136,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
                  this.level.destroyBlockProgress(this.player.getId(), pos, -1);
 -                this.player.connection.send(new ClientboundBlockBreakAckPacket(pos, this.level.getBlockState(pos), action, true, "aborted destroying"));
 +                if (!com.destroystokyo.paper.PaperConfig.lagCompensateBlockBreaking) this.player.connection.send(new ClientboundBlockBreakAckPacket(pos, this.level.getBlockState(pos), action, true, "aborted destroying")); // Paper - this can cause clients on a lagging server to think they stopped destroying a block they're currently destroying
-             }
  
-         }
+                 CraftEventFactory.callBlockDamageAbortEvent(this.player, pos, this.player.getInventory().getSelected()); // CraftBukkit
+             }
 @@ -0,0 +0,0 @@ public class ServerPlayerGameMode {
  
      public void destroyAndAck(BlockPos pos, ServerboundPlayerActionPacket.Action action, String reason) {
diff --git a/patches/server/MC-Utils.patch b/patches/server/MC-Utils.patch
index 422f588641..3836446968 100644
--- a/patches/server/MC-Utils.patch
+++ b/patches/server/MC-Utils.patch
@@ -6303,8 +6303,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  import org.bukkit.craftbukkit.block.CapturedBlockState;
 +import org.bukkit.craftbukkit.block.CraftBlockState;
  import org.bukkit.craftbukkit.block.data.CraftBlockData;
+ import org.bukkit.craftbukkit.util.CraftSpawnCategory;
  import org.bukkit.craftbukkit.util.CraftNamespacedKey;
- import org.bukkit.event.block.BlockPhysicsEvent;
 @@ -0,0 +0,0 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
          return y < -20000000 || y >= 20000000;
      }
diff --git a/patches/server/Move-range-check-for-block-placing-up.patch b/patches/server/Move-range-check-for-block-placing-up.patch
index 74213c4fbe..0f9adae598 100644
--- a/patches/server/Move-range-check-for-block-placing-up.patch
+++ b/patches/server/Move-range-check-for-block-placing-up.patch
@@ -13,9 +13,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      // Spigot end
  
 +    // Paper start
++    private static final int SURVIVAL_PLACE_DISTANCE_SQUARED = 6 * 6;
++    private static final int CREATIVE_PLACE_DISTANCE_SQUARED = 7 * 7;
 +    private boolean isOutsideOfReach(double x, double y, double z) {
 +        Location eyeLoc = this.getCraftPlayer().getEyeLocation();
-+        double reachDistance = NumberConversions.square(eyeLoc.getX() - x) + NumberConversions.square(eyeLoc.getY() - y) + NumberConversions.square(eyeLoc.getZ() - z);
++        double reachDistance = org.bukkit.util.NumberConversions.square(eyeLoc.getX() - x) + org.bukkit.util.NumberConversions.square(eyeLoc.getY() - y) + org.bukkit.util.NumberConversions.square(eyeLoc.getZ() - z);
 +        return reachDistance > (this.getCraftPlayer().getGameMode() == org.bukkit.GameMode.CREATIVE ? CREATIVE_PLACE_DISTANCE_SQUARED : SURVIVAL_PLACE_DISTANCE_SQUARED);
 +    }
 +    // Paper end
@@ -24,30 +26,26 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      public void handleUseItemOn(ServerboundUseItemOnPacket packet) {
          PacketUtils.ensureRunningOnSameThread(packet, this, this.player.getLevel());
 @@ -0,0 +0,0 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser
-         BlockPos blockposition = movingobjectpositionblock.getBlockPos();
-         Direction enumdirection = movingobjectpositionblock.getDirection();
  
-+        // Paper start - move check up and check actual location as well
+         this.player.resetLastActionTime();
+         int i = this.player.level.getMaxBuildHeight();
+-        // CraftBukkit start
+-        double distanceSqr = this.player.distanceToSqr((double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D);
+-        if (distanceSqr > 100.0D) {
++
++        // Paper start - improve distance check
 +        final Vec3 clickedLocation = movingobjectpositionblock.getLocation();
 +        if (isOutsideOfReach(blockposition.getX() + 0.5D, blockposition.getY() + 0.5D, blockposition.getZ() + 0.5D)
 +            || !Double.isFinite(clickedLocation.x) || !Double.isFinite(clickedLocation.y) || !Double.isFinite(clickedLocation.z)
 +            || isOutsideOfReach(clickedLocation.x, clickedLocation.y, clickedLocation.z)) {
-+            return;
-+        }
-+        // Paper end - move check up
-+
-         this.player.resetLastActionTime();
-         int i = this.player.level.getMaxBuildHeight();
+             return;
+         }
+-        // CraftBukkit end
++        // Paper end
  
          if (blockposition.getY() < i) {
-             if (this.awaitingPositionFromClient == null && this.player.distanceToSqr((double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D) < 64.0D && worldserver.mayInteract(this.player, blockposition)) {
-                 // CraftBukkit start - Check if we can actually do something over this large a distance
--                Location eyeLoc = this.getCraftPlayer().getEyeLocation();
--                double reachDistance = NumberConversions.square(eyeLoc.getX() - blockposition.getX()) + NumberConversions.square(eyeLoc.getY() - blockposition.getY()) + NumberConversions.square(eyeLoc.getZ() - blockposition.getZ());
--                if (reachDistance > (this.getCraftPlayer().getGameMode() == org.bukkit.GameMode.CREATIVE ? ServerGamePacketListenerImpl.CREATIVE_PLACE_DISTANCE_SQUARED : ServerGamePacketListenerImpl.SURVIVAL_PLACE_DISTANCE_SQUARED)) {
--                    return;
--                }
-+                // Paper - move check up
-                 this.player.stopUsingItem(); // SPIGOT-4706
-                 // CraftBukkit end
+-            if (this.awaitingPositionFromClient == null && distanceSqr < 64.0D && worldserver.mayInteract(this.player, blockposition)) { // CraftBukkit - reuse value
++            if (this.awaitingPositionFromClient == null && this.player.distanceToSqr((double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D) < 64.0D && worldserver.mayInteract(this.player, blockposition)) { // CraftBukkit - reuse value // Paper - revert CB change
+                 this.player.stopUsingItem(); // CraftBukkit - SPIGOT-4706
                  InteractionResult enuminteractionresult = this.player.gameMode.useItemOn(this.player, worldserver, itemstack, enumhand, movingobjectpositionblock);
+ 
diff --git a/patches/server/Paper-config-files.patch b/patches/server/Paper-config-files.patch
index a59c832faf..0541466b96 100644
--- a/patches/server/Paper-config-files.patch
+++ b/patches/server/Paper-config-files.patch
@@ -712,7 +712,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        this.paperConfig = new com.destroystokyo.paper.PaperWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName(), this.spigotConfig); // Paper
          this.generator = gen;
          this.world = new CraftWorld((ServerLevel) this, gen, biomeProvider, env);
-         this.ticksPerAnimalSpawns = this.getCraftServer().getTicksPerAnimalSpawns(); // CraftBukkit
+ 
 diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -726,7 +726,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
              world.serverLevelData.setDifficulty(config.difficulty);
              world.setSpawnSettings(config.spawnMonsters, config.spawnAnimals);
 @@ -0,0 +0,0 @@ public final class CraftServer implements Server {
-                 world.ticksPerAmbientSpawns = this.getTicksPerAmbientSpawns();
+                 }
              }
              world.spigotConfig.init(); // Spigot
 +            world.paperConfig.init(); // Paper
diff --git a/patches/server/Player.setPlayerProfile-API.patch b/patches/server/Player.setPlayerProfile-API.patch
index 14ea954df0..1c504b4340 100644
--- a/patches/server/Player.setPlayerProfile-API.patch
+++ b/patches/server/Player.setPlayerProfile-API.patch
@@ -37,6 +37,18 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  import net.minecraft.world.level.block.entity.SignBlockEntity;
  import net.minecraft.world.level.saveddata.maps.MapDecoration;
  import net.minecraft.world.level.saveddata.maps.MapItemSavedData;
+@@ -0,0 +0,0 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
+         return server.getPlayer(getUniqueId()) != null;
+     }
+ 
+-    @Override
+-    public PlayerProfile getPlayerProfile() {
+-        return new CraftPlayerProfile(this.getProfile());
+-    }
+-
+     @Override
+     public InetSocketAddress getAddress() {
+         if (this.getHandle().connection == null) return null;
 @@ -0,0 +0,0 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
          this.hiddenEntities.put(entity.getUniqueId(), hidingPlugins);
  
diff --git a/patches/server/Provide-E-TE-Chunk-count-stat-methods.patch b/patches/server/Provide-E-TE-Chunk-count-stat-methods.patch
index ff9d72ee63..4e1181e35f 100644
--- a/patches/server/Provide-E-TE-Chunk-count-stat-methods.patch
+++ b/patches/server/Provide-E-TE-Chunk-count-stat-methods.patch
@@ -24,7 +24,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 --- 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 {
-     private int ambientSpawn = -1;
+     private final Object2IntOpenHashMap<SpawnCategory> spawnCategoryLimit = new Object2IntOpenHashMap<>();
      private net.kyori.adventure.pointer.Pointers adventure$pointers; // Paper - implement pointers
  
 +    // Paper start - Provide fast information methods
diff --git a/patches/server/Retain-block-place-order-when-capturing-blockstates.patch b/patches/server/Retain-block-place-order-when-capturing-blockstates.patch
index 6f01622f48..ba7a9b341f 100644
--- a/patches/server/Retain-block-place-order-when-capturing-blockstates.patch
+++ b/patches/server/Retain-block-place-order-when-capturing-blockstates.patch
@@ -20,5 +20,5 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 -    public Map<BlockPos, BlockEntity> capturedTileEntities = new HashMap<>();
 +    public Map<BlockPos, BlockEntity> capturedTileEntities = new java.util.LinkedHashMap<>(); // Paper
      public List<ItemEntity> captureDrops;
-     public long ticksPerAnimalSpawns;
-     public long ticksPerMonsterSpawns;
+     public final it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap<SpawnCategory> ticksPerSpawnCategory = new it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap<>();
+     // Paper start
diff --git a/patches/server/Show-Paper-in-client-crashes-server-lists-and-Mojang.patch b/patches/server/Show-Paper-in-client-crashes-server-lists-and-Mojang.patch
index 13b179cc69..c201ad146d 100644
--- a/patches/server/Show-Paper-in-client-crashes-server-lists-and-Mojang.patch
+++ b/patches/server/Show-Paper-in-client-crashes-server-lists-and-Mojang.patch
@@ -49,7 +49,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 --- a/src/main/java/org/bukkit/craftbukkit/Main.java
 +++ b/src/main/java/org/bukkit/craftbukkit/Main.java
 @@ -0,0 +0,0 @@ public class Main {
-                     deadline.add(Calendar.DAY_OF_YEAR, -14);
+                     deadline.add(Calendar.DAY_OF_YEAR, -28);
                      if (buildDate.before(deadline.getTime())) {
                          System.err.println("*** Error, this build is outdated ***");
 -                        System.err.println("*** Please download a new build as per instructions from https://www.spigotmc.org/go/outdated-spigot ***");
diff --git a/patches/server/Timings-v2.patch b/patches/server/Timings-v2.patch
index b6b4608a96..29d08f4193 100644
--- a/patches/server/Timings-v2.patch
+++ b/patches/server/Timings-v2.patch
@@ -1167,7 +1167,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          this.level.getProfiler().popPush("unload");
          this.chunkMap.tick(shouldKeepTicking);
 @@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource {
-             boolean flag1 = level.ticksPerAnimalSpawns != 0L && worlddata.getGameTime() % level.ticksPerAnimalSpawns == 0L; // CraftBukkit
+             boolean flag1 = level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) != 0L && worlddata.getGameTime() % level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) == 0L; // CraftBukkit
  
              gameprofilerfiller.push("naturalSpawnCount");
 +            this.level.timings.countNaturalMobs.startTiming(); // Paper - timings
@@ -1336,10 +1336,10 @@ diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListener
 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 @@ import org.bukkit.inventory.EquipmentSlot;
+@@ -0,0 +0,0 @@ import org.bukkit.inventory.CraftingInventory;
+ import org.bukkit.inventory.EquipmentSlot;
  import org.bukkit.inventory.InventoryView;
  import org.bukkit.inventory.SmithingInventory;
- import org.bukkit.util.NumberConversions;
 +import co.aikar.timings.MinecraftTimings; // Paper
  // CraftBukkit end
  
diff --git a/patches/server/Use-TerminalConsoleAppender-for-console-improvements.patch b/patches/server/Use-TerminalConsoleAppender-for-console-improvements.patch
index 2db47f39b5..3c25da3a05 100644
--- a/patches/server/Use-TerminalConsoleAppender-for-console-improvements.patch
+++ b/patches/server/Use-TerminalConsoleAppender-for-console-improvements.patch
@@ -255,14 +255,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  import net.minecraft.advancements.Advancement;
  import net.minecraft.commands.CommandSourceStack;
  import net.minecraft.commands.Commands;
-@@ -0,0 +0,0 @@ import net.minecraft.resources.RegistryReadOps;
- import net.minecraft.resources.ResourceKey;
- import net.minecraft.resources.ResourceLocation;
- import net.minecraft.server.ConsoleInput;
-+//import jline.console.ConsoleReader; // Paper
- import net.minecraft.server.MinecraftServer;
- import net.minecraft.server.bossevents.CustomBossEvent;
- import net.minecraft.server.commands.ReloadCommand;
 @@ -0,0 +0,0 @@ public final class CraftServer implements Server {
          return this.logger;
      }
diff --git a/patches/server/add-per-world-spawn-limits.patch b/patches/server/add-per-world-spawn-limits.patch
index a00f4b3ee8..b9a7228e7c 100644
--- a/patches/server/add-per-world-spawn-limits.patch
+++ b/patches/server/add-per-world-spawn-limits.patch
@@ -52,12 +52,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
          this.environment = env;
 +        // Paper start - per world spawn limits
-+        this.monsterSpawn = this.world.paperConfig.perWorldSpawnLimits.getInt(net.minecraft.world.entity.MobCategory.MONSTER);
-+        this.animalSpawn = this.world.paperConfig.perWorldSpawnLimits.getInt(net.minecraft.world.entity.MobCategory.CREATURE);
-+        this.waterAnimalSpawn = this.world.paperConfig.perWorldSpawnLimits.getInt(net.minecraft.world.entity.MobCategory.WATER_CREATURE);
-+        this.waterAmbientSpawn = this.world.paperConfig.perWorldSpawnLimits.getInt(net.minecraft.world.entity.MobCategory.WATER_AMBIENT);
-+        this.ambientSpawn = this.world.paperConfig.perWorldSpawnLimits.getInt(net.minecraft.world.entity.MobCategory.AMBIENT);
-+        this.waterUndergroundCreatureSpawn = this.world.paperConfig.perWorldSpawnLimits.getInt(net.minecraft.world.entity.MobCategory.UNDERGROUND_WATER_CREATURE);
++        for (SpawnCategory spawnCategory : SpawnCategory.values()) {
++            if (CraftSpawnCategory.isValidForLimits(spawnCategory)) {
++                setSpawnLimit(spawnCategory, this.world.paperConfig.perWorldSpawnLimits.getInt(CraftSpawnCategory.toNMS(spawnCategory)));
++            }
++        }
 +        // Paper end
      }
  
diff --git a/patches/server/use-CB-BlockState-implementations-for-captured-block.patch b/patches/server/use-CB-BlockState-implementations-for-captured-block.patch
index 5330efcb7e..8b20cf0948 100644
--- a/patches/server/use-CB-BlockState-implementations-for-captured-block.patch
+++ b/patches/server/use-CB-BlockState-implementations-for-captured-block.patch
@@ -29,7 +29,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    public Map<BlockPos, org.bukkit.craftbukkit.block.CraftBlockState> capturedBlockStates = new java.util.LinkedHashMap<>(); // Paper
      public Map<BlockPos, BlockEntity> capturedTileEntities = new HashMap<>();
      public List<ItemEntity> captureDrops;
-     public long ticksPerAnimalSpawns;
+     public final it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap<SpawnCategory> ticksPerSpawnCategory = new it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap<>();
 @@ -0,0 +0,0 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
      public boolean setBlock(BlockPos pos, BlockState state, int flags, int maxUpdateDepth) {
          // CraftBukkit start - tree generation
diff --git a/work/Bukkit b/work/Bukkit
index 5c21a63f5f..8085edde6d 160000
--- a/work/Bukkit
+++ b/work/Bukkit
@@ -1 +1 @@
-Subproject commit 5c21a63f5f835b4e1d8ea8cb49d6217dc606a603
+Subproject commit 8085edde6dfb5c9a95741c0c499ac9317e406ea1
diff --git a/work/CraftBukkit b/work/CraftBukkit
index c26a7b54dc..de95135562 160000
--- a/work/CraftBukkit
+++ b/work/CraftBukkit
@@ -1 +1 @@
-Subproject commit c26a7b54dc3973edd00e88f83b37a566b0027053
+Subproject commit de9513556263817fab3394a69f580b8564932083
diff --git a/work/Spigot b/work/Spigot
index 862678eabf..6edb62f30a 160000
--- a/work/Spigot
+++ b/work/Spigot
@@ -1 +1 @@
-Subproject commit 862678eabf78c1fc309e4b9cd1c38515712e7ada
+Subproject commit 6edb62f30a5d2783a1b7087322029a92b02f17b5