← Roadmap

EffectiveDataContainerUtils

PDC utils

Why

PersistentDataContainer is the only honest data store on an item, entity, block or world. But the API is verbose: get itemMeta, get the container, name the type, put the meta back. UUID, Location or a list of items cannot be stored at all.

EffectiveDataContainerUtils reduces it to get / set with the type inferred from the generic and adds what vanilla lacks: inventories, locations, UUIDs, arbitrary objects via Base64 and nested containers.

Minimal example

Primitives
val KEY = NamespacedKey(MyPlugin.instance, "charges")

val stack = EffectiveDataContainerUtils.setContainerValue(stack, KEY, 3)
val charges = EffectiveDataContainerUtils.getContainerValue<Int>(stack, KEY) ?: 0

EffectiveDataContainerUtils.setContainerValue(player, KEY, listOf("a", "b"))
val tags = EffectiveDataContainerUtils.getContainerValue<List<String>>(player, KEY)

For an ItemStack set returns a new stack — use the returned value, the original is unchanged.

Inventory, location, entities
EffectiveDataContainerUtils.setItems(player, BACKPACK, player.inventory.contents.toList())
val items = EffectiveDataContainerUtils.getItems(player, BACKPACK)

EffectiveDataContainerUtils.setLocation(player, HOME, player.location)
val home = EffectiveDataContainerUtils.getLocation(player, HOME)

EffectiveDataContainerUtils.setUUIDToLongArray(pet, OWNER, player.uniqueId)
val owner = EffectiveDataContainerUtils.getEntityFromLongArray(pet, OWNER)

Types, nesting, inventory, location, Base64, UUIDs

Explicit type and nested containers
EffectiveDataContainerUtils.setContainerValue(holder, KEY, 5L, PersistentDataType.LONG)
val n = EffectiveDataContainerUtils.getContainerValue(holder, KEY, PersistentDataType.LONG)

EffectiveDataContainerUtils.setContainer(player, STATS) { c ->
    EffectiveDataContainerUtils.setContainerValue(c, KILLS, 10)
    EffectiveDataContainerUtils.setContainerValue(c, DEATHS, 2)
}

val kills = EffectiveDataContainerUtils.getContainer(player, STATS) { c ->
    EffectiveDataContainerUtils.getContainerValue<Int>(c, KILLS)
}

Overloads with PersistentDataType — when the type cannot be inferred from the generic or you need a custom one. A nested container is a group of keys under one key: setContainer creates (or opens) it and hands it to the lambda, getContainer reads it and returns the lambda result.

Inventory — setItems / getItems
EffectiveDataContainerUtils.setItems(player, BACKPACK, backpackMenu.contents.toList())
val items: List<ItemStack?>? = EffectiveDataContainerUtils.getItems(player, BACKPACK)

val bag = EffectiveDataContainerUtils.setItems(bag, CONTENTS, listOf(sword, null, null, apple))
val inside = EffectiveDataContainerUtils.getItems(bag, CONTENTS)

EffectiveDataContainerUtils.setItems(player, BACKPACK, null)

A whole ItemStack list in one key, null cells preserved — store inventory contents as-is and restore by index. Stored via Base64 (BukkitObjectOutputStream), so meta, enchants and the items' own PDC survive too. null instead of a list removes the key. On an ItemStack — take and use the result: that is how a pocket shulker is done.

Location — setLocation / getLocation
EffectiveDataContainerUtils.setLocation(player, HOME, player.location)
val home: Location? = EffectiveDataContainerUtils.getLocation(player, HOME)
home?.let { player.teleport(it) }

val compass = EffectiveDataContainerUtils.setLocation(compass, TARGET, chest.location)
val target = EffectiveDataContainerUtils.getLocation(compass, TARGET)

EffectiveDataContainerUtils.setLocation(player, HOME, null)

Stored structurally in a nested container: world name, x/y/z, yaw/pitch — not Base64, fast to read and visible in NBT. If the world is not loaded on read, null is returned. null instead of a location removes the key.

Base64
EffectiveDataContainerUtils.base64SetContainerValue(player, LAST_POS, player.location.toVector())
val v = EffectiveDataContainerUtils.base64GetContainerValue(player, LAST_POS, Vector::class.java)

val stack = EffectiveDataContainerUtils.base64SetContainerValue(stack, PAYLOAD, hashMapOf("a" to 1))

Any Serializable or Bukkit ConfigurationSerializable (Location, Vector, ItemStack, Map …) is serialised with BukkitObjectOutputStream and stored as a string. Slower and bulkier than primitives — for rare data.

UUIDs and entities
EffectiveDataContainerUtils.setUUIDToLongArray(pet, OWNER, player.uniqueId)
val ownerId = EffectiveDataContainerUtils.getUUIDFromLongArray(pet, OWNER)
val owner = EffectiveDataContainerUtils.getEntityFromLongArray(pet, OWNER) as? Player

EffectiveDataContainerUtils.setUUIDsToLongArray(boss, MINIONS, minions.map { it.uniqueId })
val alive = EffectiveDataContainerUtils.getEntitiesFromLongArray(boss, MINIONS).filterNotNull()

A UUID is packed into a LONG_ARRAY of two longs (a list — 2n). getEntity* resolves through Bukkit.getEntity — null for unloaded or dead ones.

Pitfalls

  • Forgot to take the result of setContainerValue(stack, …) — nothing was written. ItemStack meta is copied.
  • A Base64 object must be Serializable or Bukkit ConfigurationSerializable; otherwise get returns null.
  • getEntityFromLongArray returns null if the entity is not loaded — the chunk must be in memory.