diff --git a/paper-api/src/main/java/io/papermc/paper/persistence/PersistentDataContainerView.java b/paper-api/src/main/java/io/papermc/paper/persistence/PersistentDataContainerView.java index fd27d65893a2..544b0f37daa2 100644 --- a/paper-api/src/main/java/io/papermc/paper/persistence/PersistentDataContainerView.java +++ b/paper-api/src/main/java/io/papermc/paper/persistence/PersistentDataContainerView.java @@ -1,6 +1,7 @@ package io.papermc.paper.persistence; import java.util.Set; +import net.kyori.adventure.key.Key; import org.bukkit.NamespacedKey; import org.bukkit.persistence.PersistentDataAdapterContext; import org.bukkit.persistence.PersistentDataContainer; @@ -110,6 +111,23 @@ public interface PersistentDataContainerView { */ C getOrDefault(NamespacedKey key, PersistentDataType type, C defaultValue); + /** + * Estimates the {@link PersistentDataType} of the value based on the primitive + * type that is stored for the given key. + *
+ * WARNING: This method will only return data types + * defined in {@link PersistentDataType} which directly map to a specific + * nbt tag. The return value will be wrong if the data type is plugin + * specific or not represented by a specific nbt tag. + * Use {@link #has(NamespacedKey, PersistentDataType)} if you expect a + * specific data type. + * + * @param key the key to look up the value type of + * @return estimated data type or null if no value is present, the type is + * unknown or an empty list is encountered as value. + */ + @Nullable PersistentDataType getPrimitiveStorageType(Key key); + /** * Get the set of keys present on this {@link PersistentDataContainer} * instance. 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 index 6f78279f4e54..614a655555e2 100644 --- a/paper-server/src/main/java/io/papermc/paper/persistence/PaperPersistentDataContainerView.java +++ b/paper-server/src/main/java/io/papermc/paper/persistence/PaperPersistentDataContainerView.java @@ -7,13 +7,16 @@ import java.util.Collections; import java.util.HashSet; import java.util.Set; +import net.kyori.adventure.key.Key; import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; 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.ListPersistentDataType; import org.bukkit.persistence.PersistentDataAdapterContext; import org.bukkit.persistence.PersistentDataContainer; import org.bukkit.persistence.PersistentDataType; @@ -73,6 +76,18 @@ public C getOrDefault(final NamespacedKey key, final PersistentDataType

getPrimitiveStorageType(final Key key) { + Preconditions.checkArgument(key != null, "The NamespacedKey key cannot be null"); + + final Tag value = this.getTag(key.asString()); + if (value == null) { + return null; + } + + return getTagType(value); + } + @Override public Set getKeys() { final Set names = this.toTagCompound().keySet(); @@ -117,4 +132,62 @@ public byte[] serializeToBytes() throws IOException { return byteArrayOutput.toByteArray(); } } + + private static @Nullable PersistentDataType getTagType(final Tag value) { + return switch (value.getId()) { + case Tag.TAG_BYTE -> PersistentDataType.BYTE; + case Tag.TAG_SHORT -> PersistentDataType.SHORT; + case Tag.TAG_INT -> PersistentDataType.INTEGER; + case Tag.TAG_LONG -> PersistentDataType.LONG; + case Tag.TAG_FLOAT -> PersistentDataType.FLOAT; + case Tag.TAG_DOUBLE -> PersistentDataType.DOUBLE; + case Tag.TAG_BYTE_ARRAY -> PersistentDataType.BYTE_ARRAY; + case Tag.TAG_STRING -> PersistentDataType.STRING; + case Tag.TAG_COMPOUND -> PersistentDataType.TAG_CONTAINER; + case Tag.TAG_INT_ARRAY -> PersistentDataType.INTEGER_ARRAY; + case Tag.TAG_LONG_ARRAY -> PersistentDataType.LONG_ARRAY; + case Tag.TAG_LIST -> { + if (!(value instanceof ListTag listTag)) { + yield null; + } + + yield getListTagType(listTag); + } + default -> null; + }; + } + + private static @Nullable ListPersistentDataType getListTagType(final ListTag value) { + return switch (value.identifyRawElementType()) { + case Tag.TAG_BYTE -> PersistentDataType.LIST.bytes(); + case Tag.TAG_SHORT -> PersistentDataType.LIST.shorts(); + case Tag.TAG_INT -> PersistentDataType.LIST.integers(); + case Tag.TAG_LONG -> PersistentDataType.LIST.longs(); + case Tag.TAG_FLOAT -> PersistentDataType.LIST.floats(); + case Tag.TAG_DOUBLE -> PersistentDataType.LIST.doubles(); + case Tag.TAG_BYTE_ARRAY -> PersistentDataType.LIST.byteArrays(); + case Tag.TAG_STRING -> PersistentDataType.LIST.strings(); + case Tag.TAG_COMPOUND -> PersistentDataType.LIST.dataContainers(); + case Tag.TAG_INT_ARRAY -> PersistentDataType.LIST.integerArrays(); + case Tag.TAG_LONG_ARRAY -> PersistentDataType.LIST.longArrays(); + case Tag.TAG_LIST -> { + if (value.isEmpty()) { + yield null; + } + + final Tag nestedTag = value.getFirst(); + if (!(nestedTag instanceof ListTag nestedListTag)) { + yield null; + } + + ListPersistentDataType nestedListType = getListTagType(nestedListTag); + if (nestedListType == null) { + yield null; + } + + yield PersistentDataType.LIST.listTypeFrom(nestedListType); + } + default -> null; + }; + } }