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 null
++ */
++ @NotNull
++ PlayerTextures getTextures();
++
++ /**
++ * Copies the given textures.
++ *
++ * @param textures the textures to copy, or null 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 {
+ * null and the name is null 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 null
+ */
+ @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
+ * null and the name is null 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 null
+ */
+ @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;
+ *
+ * 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 null 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 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 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 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 serialize(Builder 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 populators = new ArrayList();
+ private final BlockMetadataStore blockMetadata = new BlockMetadataStore(this);
+ private final Object2IntOpenHashMap 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 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 serialize() {
++ Map 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