Usage

The first step to using this pack is deciding how you want to use it. There are 3 general buckets that catch most folks:

  • Shippable survival datapacs (PMC)

  • Helper Pack to enable multiplayer for maps

  • Public/private servers (esp for migrating name-changes)

For these methods, there are 2 main ways to then utilize this pack. You can either use it as a standalone zip. This is nice and easy and works easily by just dropping it in your datapacks folder which you can either ship with your map or use for your server. You can use all of the features from another datapack. You should make sure that PlayerDB is loaded at the very top of your other datapacks, so verify this via the /datapack list command. Feel free to skip the bundling section!

However, if you wish to ship your datapack for users in survival (like hosting on PMC), then you can consider this next section

Bundling

How to bundle PlayerDB v2.0+ with your datapack

While this process requires some minor changes to your datapack, it will make the use of this library seemless for your users and most likely, most won’t even know it’s existence.

First, head over to the releases tab, download the latest release, then unzip.

Grab all the contents, except the minecraft folder, inside the data folder, and plop them inside your own datapack’s data. Nothing should collide here (if global collides, you can keep either global/advancements/root).

To properly merge the minecraft namespace, you’ll have to pay a bit of attention.

For tags/functions/load.json, you will see the following contents:

{
    "values": [
        "#load:_private/load"
    ]
}

This odd looking setup is known as lantern load and helps manage load order allowing PlayerDB to be ran first before other packs. You may be used to using the #minecraft:load and #minecraft:tick tags. Lantern load provides an alternative #load:load tag to use which allows your pack to ensure it runs after other packs.

Instead of placing your pack inside the #minecraft:load tag file, you can instead use the #load:load tag and insert your load function after my #rx.playerdb:load. For example:

{
    "values": [
        "#rx.playerdb:load",
        "your_pack_here:load"
    ]
}

This will ensure that my pack will run it’s load function before your pack, which is very important for PlayerDB to run properly.

If you are using the #minecraft:tick tag for ticking code, you will need to migrate to a schedule loop based system. While this might seem like a large change, it actually won’t alter much of how your code runs. Essentially, it’ll ensure that all of PlayerDB’s ticking code will run before your code.

To do this, at the top of your functiom, run schedule function your_pack_here:tick 1t replace. Instead of looping via the minecraft tick tag, you function will schedule it’s execution the following tick. Now in your load function, you can “boot” your tick loop by running the same command!

After these steps, you should be ready to go! If you wish to update your PlayerDB to a new version, you only need to update the rx and rx.playerdb namespaces, unless written otherwise.

Using the API

Before using this pack, you should be familiar with providing unique scoreboard ids to players and some moderate experience with the storage space (via data modify).

This datapack assigns a unique id to each player via the rx.uid scoreboard. To access the data stored on the data, you will need to reference a uid. Note, by default, the database will only have an entry for a player once requested / created (this allows for some performance boosts).

Now, we can get started with some basic to moderate use cases 🎉!

Storing simple data

This will get @s’s database entry. If it does not exist, it’ll dynamically create it.

function #rx.playerdb:api/v2/get/self

Our data is available at the storage location rx.playerdb:io player.data. We should write some data, notice how we organized our data by cool_pack.

data modify storage rx.playerdb:io player.data.cool_pack set value {eggs: 3b}

Note that we stored our data in author.cool_pack. Namespacing our data allows us to have better compatibility with other packs!

Finally, let’s save our data!

function #rx.playerdb:api/v2/save/self

Getting some previously stored data

function #rx.playerdb:api/v2/get/self
execute store result score @s eggs run data get storage rx.playerdb:io player.data.cool_pack.eggs

# No need to save, we are just reading

Note, we’ve taken some numerical data and stored it on a scoreboard. Here, we’ve used it for a simple score for how many eggs our cool_pack owns but perhaps it could hold much more!

Getting someone else’s data

Sometimes, we don’t want to get our own data, but someone else’s. Let’s say that we have someone else’s id stored in author.obj.

scoreboard players operation $in.uid rx.playerdb.io = @s author.obj

# Note that we are using `api/get` here, `get` will **not** make a new entry on the fly
function #rx.playerdb:api/v2/get

# Let's hope they had some eggs stored
execute store result score @s eggs run data get storage rx.playerdb:io player.data.cool_pack.eggs

# No need to save, we are just reading someone else's data ;)
# If we were to... steal some eggs, we could save that change via:
#     function #rx.playerdb:api/v2/save

Other Basic Features

We can manually add a player entry for @s via:

function #rx.playerdb:api/v2/add_entry

You generally will never need to create a manual entry, but the api is available just incase!

We can also ‘select’/filter data for a player with id: $in.uid rx:io This exposing some internal logic, essentially outputs an entry at rx.playerdb:main players[{selected:1b}]. This does interface with the live database, so you’ll wanna be careful if you are using this.

scoreboard players operation $in.uid rx.playerdb.io = @s rx.uid
function #rx.playerdb:api/v2/select

# (Note: select is much faster than a `get`/`save` which is why I've included in the api)

We can check whether or not a player has a database entry. This incentivizes not creating a database entry for every player, but rather creating them dynamically when you need them. You can hook into the #api/v2/on_entry_add function tag which is talked about in a later section.

execute if score @s rx.pdb.has_entry matches 1 run ...

Some admin features (for testing or for servers)

I recommend adding the rx.admin tag to you or other admins. This will provide more debug information and a cool load message (Note that I use this for all my packs)!

Listing all the players stored in the database (✨ Coming Soon ✨)

This just pulls up a clickable tellraw menu for player @s in chat.

function rx.playerdb:admin/list_players

Note that anyone can click these buttons, but only an operator can run this function.

Admin tools for testing and for servers

# This will nuke the database and all ids, no warning!
function rx.playerdb:admin/reset_all

# This will remove `@s`'s entry from the database
function rx.playerdb:admin/delete_player

# This will remove `$in.uid rx.playerdb.io`'s entry from the database
function rx.playerdb:admin/remove_entry

# This will take the data stored at rx.playerdb:temp admin.migrate.UUID
#  and 'migrate' the data to the entity called as @s
function rx.playerdb:admin/migrate_account

More advanced API usage (events)

Dynamically prepping entries when they are added

The function tag, #rx.playerdb:api/v2/on_entry_add, allows a function to be run when an entry is added. Just add your function in the tag. This function will fire before a api/v2/get/self completes it’s execution allowing you to prep data for future use, very handy for a lean database!

The player will already be located at rx.playerdb:io player and will automatically save for you. There is no need to call any api functions and the datapack will bug you if you do!

data modify storage rx.playerdb:io player.data.cool_pack set value {eggs: 0b}
# Starting with no eggs is :(

Name changes: how to handle them

PlayerDB has a nifty feature of having data persist on a name change (no leftover data, etc), This allows you to cache your scoreboard scores, which are name specific, in the database.

Essentially, there is an event which fires when a player changes their name. When this occurs, you can take the scores stored in the database, and load them in the cooresponding scoreboards! All you need to do is save those scores to the database, and keep them updated.

There are two great ways of doing this:

  1. Caching scores on a slow clock (maybe every 5 minutes)

  2. Caching scores when they change

I prefer 2 since it leaves little to no room for error. If you wish to see an example of this, check out Enderchest+.

# I like to store the scores in a specific `scores` object so they are easy to identify
function #rx.playerdb:api/v2/get_self
execute store result storage rx.playerdb:io player.data.cool_pack.scores.eggs int 1 run scoreboard players get @s eggs

Once we implement this system, we have to implement a system to retrieve these scores when a name is changed.

The function tag, #rx.playerdb:api/v2/on_name_change, allows a function to be ran when a player changes their name. This allows you to access the old name, rx.playerdb:io old_name and the data if it has been created.

execute if score @s rx.pdb.has_entry matches 1 store result score @s eggs run data get storage rx.playerdb:io player.data.author.cool_pack.eggs
tellraw @a ["Yo, ", {"selector": "@s"}, " changed their name from ", {"storage": "rx:io", "nbt": "playerdb.old_name"}]

Make sure you prepend execute if score @s rx.pdb.has_entry matches 1 to any data get you perform otherwise, you might just be getting null data (which automatically gives 0 in Minecraft).

Also, be sure not run to any api calls in here as well, since the datapack will again nag you ;)