Custom Data

Object Custom Data

The core part of custom data is the DataSerializable. To implement it, you must first decide if you want to create a separate API for your custom data. Generally speaking it’s best to separate the API from the implementation (as SpongeAPI does), but if it won’t be seen by other developers then you can just put both in the same class.

First, create a class and define the data you wish to store. In the following example we will use the idea of storing a players last attacker, therefore we will have only the data of the last attackers UUID.

import java.util.UUID;

public class LastAttackerDataSerilizable {

    private UUID lastAttackerId;

}

Notitie

Any data you wish to store must be able to be serialized into java primitives and/or strings

From here you will want to implement the javadoc:DataSerializable which will give you two methods to implement. The first is contentVersion which is for the version of your data manipulator. The other method (toContainer) is used for serializing your data to the dataholder it belongs to. To do this you need to create a new DataContainer then set your value(s) to the newly created DataContainer

import org.spongepowered.api.data.persistence.DataSerializable;
import org.spongepowered.api.data.persistence.DataContainer;
import org.spongepowered.api.data.persistence.DataQuery;

public class LastAttackerDataSerilizable implements DataSerializable {

    private UUID lastAttackerId;

    public static final DataQuery UUID_PATH = DataQuery.of("attack", "last");

    @Override
    public int contentVersion(){
        return 1;
    }

    @Override
    public DataContainer toContainer(){
        return DataContainer.createNew()
            .set(LastAttackerDataSerilizable.UUID_PATH, lastAttackerId.toString());
    }

}

After that you will want to create a class that can build the data from a DataContainer this is known as the DataBuilder which can be implemented as follows:

import org.spongepowered.api.data.persistence.InvalidDataException;

public class LastAttackerDataBuilder implements DataBuilder<LastAttackerDataSerilizable> {

    @Override
    public Optional<LastAttackerDataSerilizable> build(DataView container) throws InvalidDataException {
        Optional<String> lastAttackerAsStringId container.getString(LastAttackerDataSerilizable.UUID_PATH);
        if(lastAttackerAsStringId.isPresent()){
            UUID lastAttacker = UUID.fromString(lastAttackerAsStringId.get());
            return Optional.of(new LastAttackerDataSerilizable(lastAttacker));
        }
        return Optional.empty();
    }

}

Registration

Registering your DataSerializable allows it to be accessible by Sponge and by other plugins in a generic way. The game/plugin can create copies of your data and serialize/deserialize your data without referencing any of your classes directly.

To register a DataSerializable Sponge has the RegisterDataEvent event. This will allow you to register your data with the appropriate DataHolder

Simple Custom Data

All of above is a lot of work if you just wanting to register a java primitive or String to a DataHolder. Thankfully there is a much shorter way to do all of that.

Key<? extends Value<String>> key = Key.from(pluginContainer, "my_simple_data", String.class);
DataRegistration myData = DataRegistration.of(key, ServerPlayer.class);
event.register(myData);

Registration Key

When it comes to registering your data, you are required to register it with a Key which will allow you and other developers access to your data manipulator.

import org.spongepowered.api.ResourceKey;
import org.spongepowered.api.data.Key;
import org.spongepowered.api.data.value.Value;

ResourceKey resourceKey = ResourceKey(pluginContainer, "last_attacker_manipulator");
Key<? extends Value<LastAttackerDataSerilizable>> key = Key
    .builder()
    .key(resourceKey)
    .elementType(LastAttackerDataSerilizable.class)
    .build();

Waarschuwing

Be sure to store your Key somewhere global so you can access it later.

Tip

You can register a key for a specific element within a DataSerializable

Data Store

The DataStore is used to register your Key with the appropriate DataHolder and also register any other keys you may have accessing your DataSerializable. In the example below, it creates a DataStore and makes it appliciable to only the Entity DataHolder.

import org.spongepowered.api.data.persistence.DataStore;

DataStore datastore = DataStore
    .builder()
    .pluginData(resourceKey)
    .holder(Entity.class)
    .key(key)
    .build();

Simple Data Store

The above code is a lot for such a simple DataStore, so thankfully Sponge allows a quick way to create a DataStore for a single key. The following example does the same as the above example.

DataStore datastore = DataStore.of(key, DataQuery.of(), Entity.class);

Multi-Key Data Store

If you are registering multiple keys onto a single DataStore then the first approach should be used, however the other keys should be specified with the original key, such as the following example.

import org.spongepowered.api.entity.Entity;

DataStore datastore = DataStore
    .builder()
    .pluginData(resourceKey)
    .holder(Entity.class)
    .key(key)
    .key(innerKey, DataQuery.of("inner_data"))
    .build();

Data Provider

For data that requires more code to be used whenever the getter, setter, deleter are used will require the use of a DataProvider. With a dataProvider a plugin is able to manipulate how its data should be received, set, and deleted automatically.

In the following example, we will be getting the UUID from the last attacker but if there is no last attacker, then return the player’s UUID instead.

import org.spongepowered.api.data.DataProvider;

DataProvider<Value<UUID>, UUID> provider = DataProvider.mutableBuilder()
    .dataKey(innerKey)
    .dataHolder(ServerPlayer.class)
    .get(this::myCustomGetter)
    .build();

public UUID myCustomGetter(ServerPlayer player){
    return player.get(key).orElse(player.uniqueId());
}

Notitie

Data Providers are completely optional, if your data does not require one then don’t use one

Tip

Data Providers are great if you wish to have your data be synced with a database

Data Registration

The final object you will need to register your data is the DataRegistration which combines your Key, DataStore and DataProvider together into a single package that you can register.

import org.spongepowered.api.data.DataRegistration;

DataRegistration myData = DataRegistration.builder()
    .key(key)
    .store(datastore)
    .provider(provider)
    .build();

event.register(myData);

Data Builder Register

The final part of your custom data registration is registering the data builder so your data can be constructed upon reboot. This is registered though the DataManager, although it is recommended that you register it within the RegisterDataEvent.

Sponge.dataManager().registerBuilder(LastAttackerDataSerilizable.class, new LastAttackerDataBuilder());

Updating Data Containers

You may wish to update the data found within a DataHolder to a new and improved DataSerializable. This can be done with the use of the DataContentUpdater interface. In the example below we will be adding a field of the nanosecond the attack occurred, with the update value being LocalDateTime.MIN.

import org.spongepowered.api.data.persistence.DataContentUpdater;

public class LastAttackerUpdater implements DataContentUpdater {

    @Override
    public int inputVersion(){
        return 1;
    }

    @Override
    public int outputVersion(){
        return 2;
    }

    @Override
    public DataView update(DataView view){
        view.set(DataQuery.of("attack", "occurred"), LocalDateTime.MIN.getNano());
        return view;
    }

}

This can then be registered with your DataStore, whereby specifying a version number on the pluginData function will allow you to register your DataContentUpdater.

DataStore.builder()
    .pluginData(resourceKey, 1)
    .updater(new LastAttackerUpdater())
    //continue with the normal registeration