在 Minecraft 1.14 的时候,Bukkit 终于添加了持久化数据存储相关的 API。

简单的看了一下,一共添加了 4 个接口,其中 PersistentDataHolder 接口标记了对应的实现可以存储数据。

实现该接口的主要有三类比较重要:

  • 一类是所有的实体,也就是说我们可以在任何实体(比如玩家)身上存储永久的数据,比如玩家的属性、职业啥的;
  • 一类是所有附带 TileEntity 的 BlockState,对应的接口命名为 TileState,就是说可以往部分方块里存数据;
  • 一类是 ItemMeta,也就是我们可以正大光明的往物品里存数据了。

简介

  • 接口 PersistentDataHolder
    getPersistentDataContainer 用于获取持久化数据的存储容器,所有数据的读写都在这里

  • 接口 PersistentDataContainer

get has set 等一系列方法,类似于一个 Map,键为 NamespacedKey,用于区分不用插件的不同数据。

  • 接口 PersistentDataType<T, Z>

表示存储的数据类型,其中接口内部的字段定义了常用的一些数据类型,比如 Integer, String 等等。
可以通过实现该接口实现自定义数据的序列化和反序列化。

接口的两个泛型参数中,

  • T 表示存储的原生类型,目测必须为那几个内置的字段的类型之一
  • Z 表示你的自定义类型

简单样例

拿我上一篇文做例子

static class PlayerData {
    int hp = 20;
    double strength = 0;
    boolean haveJob;
    Job job;
}

static class Job {
    String name;
    int level;
    int exp;
    String prefix;
}

仍然是这两个类,这次我们不用保存在别的文件里了。
利用新的 Bukkit API,可以直接存到 Player 里去,因为 Player 继承了 PersistentDataHolder

首先要把我们的类转换成原生数据类型,按照上一篇,我们用 byte[] 存数据。

首先,实现一个 PersistentDataType<byte[], Job>,按照上一篇教程,大概长这样:
(不重复展示怎么读写字符串的方法)

static class JobDataType implements PersistentDataType<byte[], Job> {

    static final JobDataType INSTANCE = new JobDataType();

    @Override
    public Class<byte[]> getPrimitiveType() {
        return byte[].class;
    }

    @Override
    public Class<Job> getComplexType() {
        return Job.class;
    }

    @Override
    public byte[] toPrimitive(Job complex, PersistentDataAdapterContext context) {
        ByteBuf buffer = Unpooled.buffer();
        writeString(buffer, complex.name);
        buffer.writeInt(complex.level);
        buffer.writeInt(complex.exp);
        writeString(buffer, complex.prefix);
        return buffer.array();
    }

    @Override
    public Job fromPrimitive(byte[] primitive, PersistentDataAdapterContext context) {
        Job job = new Job();
        ByteBuf buffer = Unpooled.wrappedBuffer(primitive);
        job.name = readString(buffer);
        job.level = buffer.readInt();
        job.exp = buffer.readInt();
        job.prefix = readString(buffer);
        return job;
    }
}

接着,把数据存到 Player 里:

Plugin plugin = /*...*/;
Player player = /*...*/;
PersistentDataContainer container = player.getPersistentDataContainer();
// 存
Job job = /*...*/;
container.set(new NamespacedKey(plugin, "playerJob"), JobDataType.INSTANCE, job);
// 取
Job get = container.get(new NamespacedKey(plugin, "playerJob"), JobDataType.INSTANCE);

十分的简单。

Player 换成 ItemMeta,就是向物品中存储数据。

使用 Bukkit API 而不是操作 byte 数组

注意到 PersistentDataType 里有一个名为 CONTAINER 的字段,可以合理猜测内部原生类型支持 PersistentDataContainer,因此不难写出这样的代码:

static class JobContainerType implements PersistentDataType<PersistentDataContainer, Job> {

    static JobContainerType INSTANCE = new JobContainerType();

    @Override
    public Class<PersistentDataContainer> getPrimitiveType() {
        return PersistentDataContainer.class;
    }

    @Override
    public Class<Job> getComplexType() {
        return Job.class;
    }

    @Override
    public PersistentDataContainer toPrimitive(Job complex, PersistentDataAdapterContext context) {
        PersistentDataContainer container = context.newPersistentDataContainer();
        container.set(new NamespacedKey(plugin, "name"), PersistentDataType.STRING, complex.name);
        container.set(new NamespacedKey(plugin, "level"), PersistentDataType.INTEGER, complex.level);
        container.set(new NamespacedKey(plugin, "exp"), PersistentDataType.INTEGER, complex.exp);
        container.set(new NamespacedKey(plugin, "prefix"), PersistentDataType.STRING, complex.prefix);
        return container;
    }

    @Override
    public Job fromPrimitive(PersistentDataContainer primitive, PersistentDataAdapterContext context) {
        Job job = new Job();
        job.name = primitive.get(new NamespacedKey(plugin, "name"), PersistentDataType.STRING);
        job.level = primitive.get(new NamespacedKey(plugin, "level"), PersistentDataType.INTEGER);
        job.exp = primitive.get(new NamespacedKey(plugin, "exp"), PersistentDataType.INTEGER);
        job.prefix = primitive.get(new NamespacedKey(plugin, "primitive"), PersistentDataType.STRING);
        return job;
    }
}

看起来也还行。