diff --git a/paper-server/src/main/java/io/papermc/paper/persistence/PaperPersistentDataContainerView.java b/paper-server/src/main/java/io/papermc/paper/persistence/PaperPersistentDataContainerView.java new file mode 100644 index 0000000000..122c32e82b --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/persistence/PaperPersistentDataContainerView.java @@ -0,0 +1,120 @@ +package io.papermc.paper.persistence; + +import com.google.common.base.Preconditions; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtIo; +import net.minecraft.nbt.Tag; +import org.bukkit.NamespacedKey; +import org.bukkit.craftbukkit.persistence.CraftPersistentDataAdapterContext; +import org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer; +import org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry; +import org.bukkit.persistence.PersistentDataAdapterContext; +import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.persistence.PersistentDataType; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +@NullMarked +public abstract class PaperPersistentDataContainerView implements PersistentDataContainerView { + + protected final CraftPersistentDataTypeRegistry registry; + protected final CraftPersistentDataAdapterContext adapterContext; + + public PaperPersistentDataContainerView(final CraftPersistentDataTypeRegistry registry) { + this.registry = registry; + this.adapterContext = new CraftPersistentDataAdapterContext(this.registry); + } + + public abstract @Nullable Tag getTag(final String key); + + public abstract CompoundTag toTagCompound(); + + @Override + public boolean has(final NamespacedKey key, final PersistentDataType type) { + Preconditions.checkArgument(key != null, "The NamespacedKey key cannot be null"); + Preconditions.checkArgument(type != null, "The provided type cannot be null"); + + final Tag value = this.getTag(key.toString()); + if (value == null) { + return false; + } + + return this.registry.isInstanceOf(type, value); + } + + @Override + public boolean has(final NamespacedKey key) { + Preconditions.checkArgument(key != null, "The provided key for the custom value was null"); // Paper + return this.getTag(key.toString()) != null; + } + + @Override + public @Nullable C get(final NamespacedKey key, final PersistentDataType type) { + Preconditions.checkArgument(key != null, "The NamespacedKey key cannot be null"); + Preconditions.checkArgument(type != null, "The provided type cannot be null"); + + final Tag value = this.getTag(key.toString()); + if (value == null) { + return null; + } + + return type.fromPrimitive(this.registry.extract(type, value), this.adapterContext); + } + + @Override + public C getOrDefault(final NamespacedKey key, final PersistentDataType type, final C defaultValue) { + final C c = this.get(key, type); + return c != null ? c : defaultValue; + } + + @Override + public Set getKeys() { + final Set names = this.toTagCompound().getAllKeys(); + final Set keys = new HashSet<>(names.size()); + names.forEach(key -> { + final String[] keyPart = key.split(":", 2); + if (keyPart.length == 2) { + keys.add(new NamespacedKey(keyPart[0], keyPart[1])); + } + }); + return Collections.unmodifiableSet(keys); + } + + @Override + public boolean isEmpty() { + return this.toTagCompound().isEmpty(); + } + + @Override + public void copyTo(final PersistentDataContainer other, final boolean replace) { + Preconditions.checkArgument(other != null, "The target container cannot be null"); + final CraftPersistentDataContainer target = (CraftPersistentDataContainer) other; + final CompoundTag tag = this.toTagCompound(); + for (final String key : tag.getAllKeys()) { + if (replace || !target.getRaw().containsKey(key)) { + target.getRaw().put(key, tag.get(key).copy()); + } + } + } + + @Override + public PersistentDataAdapterContext getAdapterContext() { + return this.adapterContext; + } + + @Override + public byte[] serializeToBytes() throws IOException { + final CompoundTag root = this.toTagCompound(); + final ByteArrayOutputStream byteArrayOutput = new ByteArrayOutputStream(); + try (final DataOutputStream dataOutput = new DataOutputStream(byteArrayOutput)) { + NbtIo.write(root, dataOutput); + return byteArrayOutput.toByteArray(); + } + } +} diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java index 49d2deac8d..756c73a401 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java @@ -496,4 +496,34 @@ public final class CraftItemStack extends ItemStack { return mirrored; } // Paper end + + // Paper start - pdc + private net.minecraft.nbt.CompoundTag getPdcTag() { + if (this.handle == null) { + return new net.minecraft.nbt.CompoundTag(); + } + final net.minecraft.world.item.component.CustomData customData = this.handle.getOrDefault(DataComponents.CUSTOM_DATA, net.minecraft.world.item.component.CustomData.EMPTY); + // getUnsafe is OK here because we are only ever *reading* the data so immutability is preserved + //noinspection deprecation + return customData.getUnsafe().getCompound("PublicBukkitValues"); + } + + private static final org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry REGISTRY = new org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry(); + private final io.papermc.paper.persistence.PaperPersistentDataContainerView pdcView = new io.papermc.paper.persistence.PaperPersistentDataContainerView(REGISTRY) { + + @Override + public net.minecraft.nbt.CompoundTag toTagCompound() { + return CraftItemStack.this.getPdcTag(); + } + + @Override + public net.minecraft.nbt.Tag getTag(final String key) { + return CraftItemStack.this.getPdcTag().get(key); + } + }; + @Override + public io.papermc.paper.persistence.PersistentDataContainerView getPersistentDataContainer() { + return this.pdcView; + } + // Paper end - pdc } diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/persistence/CraftPersistentDataContainer.java b/paper-server/src/main/java/org/bukkit/craftbukkit/persistence/CraftPersistentDataContainer.java index f55fdd57ce..9d867f1659 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/persistence/CraftPersistentDataContainer.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/persistence/CraftPersistentDataContainer.java @@ -16,11 +16,10 @@ import org.bukkit.persistence.PersistentDataContainer; import org.bukkit.persistence.PersistentDataType; import org.jetbrains.annotations.NotNull; -public class CraftPersistentDataContainer implements PersistentDataContainer { +public class CraftPersistentDataContainer extends io.papermc.paper.persistence.PaperPersistentDataContainerView implements PersistentDataContainer { // Paper - split up view and mutable private final Map customDataTags = new HashMap<>(); - private final CraftPersistentDataTypeRegistry registry; - private final CraftPersistentDataAdapterContext adapterContext; + // Paper - move to PersistentDataContainerView public CraftPersistentDataContainer(Map customTags, CraftPersistentDataTypeRegistry registry) { this(registry); @@ -28,10 +27,15 @@ public class CraftPersistentDataContainer implements PersistentDataContainer { } public CraftPersistentDataContainer(CraftPersistentDataTypeRegistry registry) { - this.registry = registry; - this.adapterContext = new CraftPersistentDataAdapterContext(this.registry); + super(registry); // Paper - move to PersistentDataContainerView } + // Paper start + @Override + public Tag getTag(final String key) { + return this.customDataTags.get(key); + } + // Paper end @Override public void set(@NotNull NamespacedKey key, @NotNull PersistentDataType type, @NotNull Z value) { @@ -42,44 +46,7 @@ public class CraftPersistentDataContainer implements PersistentDataContainer { this.customDataTags.put(key.toString(), this.registry.wrap(type, type.toPrimitive(value, this.adapterContext))); } - @Override - public boolean has(@NotNull NamespacedKey key, @NotNull PersistentDataType type) { - Preconditions.checkArgument(key != null, "The NamespacedKey key cannot be null"); - Preconditions.checkArgument(type != null, "The provided type cannot be null"); - - Tag value = this.customDataTags.get(key.toString()); - if (value == null) { - return false; - } - - return this.registry.isInstanceOf(type, value); - } - - @Override - public boolean has(NamespacedKey key) { - Preconditions.checkArgument(key != null, "The provided key for the custom value was null"); // Paper - return this.customDataTags.get(key.toString()) != null; - } - - @Override - public Z get(@NotNull NamespacedKey key, @NotNull PersistentDataType type) { - Preconditions.checkArgument(key != null, "The NamespacedKey key cannot be null"); - Preconditions.checkArgument(type != null, "The provided type cannot be null"); - - Tag value = this.customDataTags.get(key.toString()); - if (value == null) { - return null; - } - - return type.fromPrimitive(this.registry.extract(type, value), this.adapterContext); - } - - @NotNull - @Override - public Z getOrDefault(@NotNull NamespacedKey key, @NotNull PersistentDataType type, @NotNull Z defaultValue) { - Z z = this.get(key, type); - return z != null ? z : defaultValue; - } + // Paper - move to PersistentDataContainerView @NotNull @Override @@ -186,16 +153,7 @@ public class CraftPersistentDataContainer implements PersistentDataContainer { // Paper end // Paper start - byte array serialization - @Override - public byte[] serializeToBytes() throws java.io.IOException { - final net.minecraft.nbt.CompoundTag root = this.toTagCompound(); - final java.io.ByteArrayOutputStream byteArrayOutput = new java.io.ByteArrayOutputStream(); - try (final java.io.DataOutputStream dataOutput = new java.io.DataOutputStream(byteArrayOutput)) { - net.minecraft.nbt.NbtIo.write(root, dataOutput); - return byteArrayOutput.toByteArray(); - } - } - + // Paper - move to PersistentDataContainerView @Override public void readFromBytes(final byte[] bytes, final boolean clear) throws java.io.IOException { if (clear) {