diff --git a/paper-api/src/main/java/io/papermc/paper/entity/EntitySerializationFlag.java b/paper-api/src/main/java/io/papermc/paper/entity/EntitySerializationFlag.java
new file mode 100644
index 0000000000..4a76c3491b
--- /dev/null
+++ b/paper-api/src/main/java/io/papermc/paper/entity/EntitySerializationFlag.java
@@ -0,0 +1,38 @@
+package io.papermc.paper.entity;
+
+import org.bukkit.UnsafeValues;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.Player;
+
+/**
+ * Represents flags for entity serialization.
+ *
+ * @see UnsafeValues#serializeEntity(Entity, EntitySerializationFlag... serializationFlags)
+ * @since 1.21.4
+ */
+public enum EntitySerializationFlag {
+
+ /**
+ * Serialize entities that wouldn't be serialized normally
+ * (e.g. dead, despawned, non-persistent, etc.).
+ *
+ * @see Entity#isValid()
+ * @see Entity#isPersistent()
+ */
+ FORCE,
+ /**
+ * Serialize misc non-saveable entities like lighting bolts, fishing bobbers, etc.
+ *
Note: players require a separate flag: {@link #PLAYER}.
+ */
+ MISC,
+ /**
+ * Include passengers in the serialized data.
+ */
+ PASSENGERS,
+ /**
+ * Allow serializing {@link Player}s.
+ *
Note: deserializing player data will always fail.
+ */
+ PLAYER
+
+}
diff --git a/paper-api/src/main/java/org/bukkit/UnsafeValues.java b/paper-api/src/main/java/org/bukkit/UnsafeValues.java
index d0de7ce3c3..56fa266e4b 100644
--- a/paper-api/src/main/java/org/bukkit/UnsafeValues.java
+++ b/paper-api/src/main/java/org/bukkit/UnsafeValues.java
@@ -1,6 +1,7 @@
package org.bukkit;
import com.google.common.collect.Multimap;
+import io.papermc.paper.entity.EntitySerializationFlag;
import org.bukkit.advancement.Advancement;
import org.bukkit.attribute.Attribute;
import org.bukkit.attribute.AttributeModifier;
@@ -9,7 +10,9 @@ import org.bukkit.block.data.BlockData;
import org.bukkit.damage.DamageEffect;
import org.bukkit.damage.DamageSource;
import org.bukkit.damage.DamageType;
+import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
+import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.inventory.CreativeCategory;
import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.ItemStack;
@@ -198,13 +201,81 @@ public interface UnsafeValues {
*/
@NotNull ItemStack deserializeItemFromJson(@NotNull com.google.gson.JsonObject data) throws IllegalArgumentException;
- byte[] serializeEntity(org.bukkit.entity.Entity entity);
+ /**
+ * Serializes the provided entity.
+ *
+ * @param entity entity
+ * @return serialized entity data
+ * @see #serializeEntity(Entity, EntitySerializationFlag...)
+ * @see #deserializeEntity(byte[], World, boolean, boolean)
+ * @throws IllegalArgumentException if couldn't serialize the entity
+ * @since 1.17.1
+ */
+ default byte @NotNull [] serializeEntity(@NotNull Entity entity) {
+ return serializeEntity(entity, new EntitySerializationFlag[0]);
+ }
- default org.bukkit.entity.Entity deserializeEntity(byte[] data, World world) {
+ /**
+ * Serializes the provided entity.
+ *
+ * @param entity entity
+ * @param serializationFlags serialization flags
+ * @return serialized entity data
+ * @throws IllegalArgumentException if couldn't serialize the entity
+ * @see #deserializeEntity(byte[], World, boolean, boolean)
+ * @since 1.21.4
+ */
+ byte @NotNull [] serializeEntity(@NotNull Entity entity, @NotNull EntitySerializationFlag... serializationFlags);
+
+ /**
+ * Deserializes the entity from data.
+ *
The entity's {@link java.util.UUID} as well as passengers will not be preserved.
+ *
+ * @param data serialized entity data
+ * @param world world
+ * @return deserialized entity
+ * @throws IllegalArgumentException if invalid serialized entity data provided
+ * @see #deserializeEntity(byte[], World, boolean, boolean)
+ * @see #serializeEntity(Entity, EntitySerializationFlag...)
+ * @see Entity#spawnAt(Location, CreatureSpawnEvent.SpawnReason)
+ * @since 1.17.1
+ */
+ default @NotNull Entity deserializeEntity(byte @NotNull [] data, @NotNull World world) {
return deserializeEntity(data, world, false);
}
- org.bukkit.entity.Entity deserializeEntity(byte[] data, World world, boolean preserveUUID);
+ /**
+ * Deserializes the entity from data.
+ *
The entity's passengers will not be preserved.
+ *
+ * @param data serialized entity data
+ * @param world world
+ * @param preserveUUID whether to preserve the entity's uuid
+ * @return deserialized entity
+ * @throws IllegalArgumentException if invalid serialized entity data provided
+ * @see #deserializeEntity(byte[], World, boolean, boolean)
+ * @see #serializeEntity(Entity, EntitySerializationFlag...)
+ * @see Entity#spawnAt(Location, CreatureSpawnEvent.SpawnReason)
+ * @since 1.17.1
+ */
+ default @NotNull Entity deserializeEntity(byte @NotNull [] data, @NotNull World world, boolean preserveUUID) {
+ return deserializeEntity(data, world, preserveUUID, false);
+ }
+
+ /**
+ * Deserializes the entity from data.
+ *
+ * @param data serialized entity data
+ * @param world world
+ * @param preserveUUID whether to preserve uuids of the entity and its passengers
+ * @param preservePassengers whether to preserve passengers
+ * @return deserialized entity
+ * @throws IllegalArgumentException if invalid serialized entity data provided
+ * @see #serializeEntity(Entity, EntitySerializationFlag...)
+ * @see Entity#spawnAt(Location, CreatureSpawnEvent.SpawnReason)
+ * @since 1.21.4
+ */
+ @NotNull Entity deserializeEntity(byte @NotNull [] data, @NotNull World world, boolean preserveUUID, boolean preservePassengers);
/**
* Creates and returns the next EntityId available.
diff --git a/paper-api/src/main/java/org/bukkit/entity/Entity.java b/paper-api/src/main/java/org/bukkit/entity/Entity.java
index 19272cff8d..996527dbdf 100644
--- a/paper-api/src/main/java/org/bukkit/entity/Entity.java
+++ b/paper-api/src/main/java/org/bukkit/entity/Entity.java
@@ -13,6 +13,7 @@ import org.bukkit.World;
import org.bukkit.block.BlockFace;
import org.bukkit.block.PistonMoveReaction;
import org.bukkit.command.CommandSender;
+import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause;
import org.bukkit.material.Directional;
@@ -1051,11 +1052,12 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent
*
* Also, this method will fire the same events as a normal entity spawn. * - * @param location The location to spawn the entity at. - * @return Whether the entity was successfully spawned. + * @param location the location to spawn the entity at + * @return whether the entity was successfully spawned + * @since 1.17.1 */ - public default boolean spawnAt(@NotNull Location location) { - return spawnAt(location, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT); + default boolean spawnAt(@NotNull Location location) { + return spawnAt(location, CreatureSpawnEvent.SpawnReason.DEFAULT); } /** @@ -1065,11 +1067,12 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent *
* Also, this method will fire the same events as a normal entity spawn.
*
- * @param location The location to spawn the entity at.
- * @param reason The reason for the entity being spawned.
- * @return Whether the entity was successfully spawned.
+ * @param location the location to spawn the entity at
+ * @param reason the reason for the entity being spawned
+ * @return whether the entity was successfully spawned
+ * @since 1.17.1
*/
- public boolean spawnAt(@NotNull Location location, @NotNull org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason);
+ boolean spawnAt(@NotNull Location location, @NotNull CreatureSpawnEvent.SpawnReason reason);
/**
* Check if entity is inside powdered snow.
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch
index f1de578de1..780a51d408 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch
@@ -706,52 +706,44 @@
public void awardKillScore(Entity entity, DamageSource damageSource) {
if (entity instanceof ServerPlayer) {
-@@ -1752,34 +_,70 @@
+@@ -1752,15 +_,22 @@
}
public boolean saveAsPassenger(CompoundTag compound) {
+- if (this.removalReason != null && !this.removalReason.shouldSave()) {
+ // CraftBukkit start - allow excluding certain data when saving
-+ return this.saveAsPassenger(compound, true);
++ // Paper start - Raw entity serialization API
++ return this.saveAsPassenger(compound, true, false, false);
+ }
-+
-+ public boolean saveAsPassenger(CompoundTag compound, boolean includeAll) {
++ public boolean saveAsPassenger(CompoundTag compound, boolean includeAll, boolean includeNonSaveable, boolean forceSerialization) {
++ // Paper end - Raw entity serialization API
+ // CraftBukkit end
- if (this.removalReason != null && !this.removalReason.shouldSave()) {
++ if (this.removalReason != null && !this.removalReason.shouldSave() && !forceSerialization) { // Paper - Raw entity serialization API
return false;
} else {
- String encodeId = this.getEncodeId();
+- String encodeId = this.getEncodeId();
- if (encodeId == null) {
-+ if (!this.persist || encodeId == null) { // CraftBukkit - persist flag
++ String encodeId = this.getEncodeId(includeNonSaveable); // Paper - Raw entity serialization API
++ if ((!this.persist && !forceSerialization) || encodeId == null) { // CraftBukkit - persist flag // Paper - Raw entity serialization API
return false;
} else {
compound.putString("id", encodeId);
- this.saveWithoutId(compound);
-+ this.saveWithoutId(compound, includeAll); // CraftBukkit - pass on includeAll
++ this.saveWithoutId(compound, includeAll, includeNonSaveable, forceSerialization); // CraftBukkit - pass on includeAll // Paper - Raw entity serialization API
return true;
}
}
- }
-+
-+ // Paper start - Entity serialization api
-+ public boolean serializeEntity(CompoundTag compound) {
-+ List