数据附加(Data Attachments)
数据附加系统允许模组(mod)在方块实体(block entities)、区块(chunks)、实体(entities)和世界(levels)上附加并存储额外的数据。
如果你需要存储额外的世界数据,可以使用 已保存数据(SavedData)。
物品堆叠(item stacks)的数据附加已经被原版的 数据组件(data components)所取代。
创建一个附加类型(Attachment Type)
要使用该系统,你需要注册一个 AttachmentType。附加类型包含以下配置项:
- 一个默认值提供器(default value supplier),用于在首次访问数据时创建实例。
- 如果需要持久化附加数据,可以选择性地提供一个序列化器(serializer)。
- (如果配置了序列化器)
copyOnDeath标志,控制实体死亡时是否自动复制数据(详见下文)。
如果你不希望你的附加数据被持久化,请不要提供序列化器。
你可以通过几种方式提供附加序列化器(attachment serializer):直接实现 IAttachmentSerializer,实现 INBTSerializable 并使用静态方法 AttachmentType#serializable 创建构建器(builder),或者为构建器提供一个 codec。
无论采用哪种方式,附加类型都必须注册到 NeoForgeRegistries.ATTACHMENT_TYPES 注册表(Registry)。以下是一个示例:
// 为附加类型创建 DeferredRegister
private static final DeferredRegister<AttachmentType<?>> ATTACHMENT_TYPES = DeferredRegister.create(NeoForgeRegistries.ATTACHMENT_TYPES, MOD_ID);
// 通过 INBTSerializable 进行序列化
private static final Supplier<AttachmentType<ItemStackHandler>> HANDLER = ATTACHMENT_TYPES.register(
"handler", () -> AttachmentType.serializable(() -> new ItemStackHandler(1)).build()
);
// 通过 codec 进行序列化
private static final Supplier<AttachmentType<Integer>> MANA = ATTACHMENT_TYPES.register(
"mana", () -> AttachmentType.builder(() -> 0).serialize(Codec.INT).build()
);
// 不进行序列化
private static final Supplier<AttachmentType<SomeCache>> SOME_CACHE = ATTACHMENT_TYPES.register(
"some_cache", () -> AttachmentType.builder(() -> new SomeCache()).build()
);
// 在你的模组构造方法中,别忘了将 DeferredRegister 注册到你的 mod bus:
ATTACHMENT_TYPES.register(modBus);
使用附加类型(Using the attachment type)
一旦附加类型被注册,就可以在任何持有者对象(holder object)上使用。调用 getData 时,如果尚未存在数据,将会附加一个新的默认实例。
// 如果已存在,则获取 ItemStackHandler,否则附加一个新的:
ItemStackHandler stackHandler = chunk.getData(HANDLER);
// 如果玩家的法力值已存在,则获取,否则附加 0:
int playerMana = player.getData(MANA);
// 以此类推...
如果你不希望自动附加默认实例,可以先进行 hasData 检查:
// 在进行操作前,先检查区块是否已有 HANDLER 附加数据。
if (chunk.hasData(HANDLER)) {
ItemStackHandler stackHandler = chunk.getData(HANDLER);
// 对 chunk.getData(HANDLER) 进行操作。
}
数据也可以通过 setData 方法进行更新:
// 法力值增加 10。
player.setData(MANA, player.getData(MANA) + 10);
通常情况下,当方块实体(block entity)和区块(chunk)被修改时,需要将它们标记为已更改(通过 setChanged 和 setUnsaved(true))。对于使用 setData 的调用,这一操作会自动完成:
chunk.setData(MANA, chunk.getData(MANA) + 10); // 会自动调用 setUnsaved
但如果你修改的是通过 getData 获得的数据(包括新创建的默认实例),那么你必须手动将方块实体或区块标记为已更改:
var mana = chunk.getData(MUTABLE_MANA);
mana.set(10);
chunk.setUnsaved(true); // 必须手动调用,因为我们没有用 setData
与客户端共享数据(Sharing data with the client)
要将方块实体、区块或实体(entity)的附加数据同步到客户端,你需要自行 发送一个数据包 到客户端。对于区块,你可以利用 ChunkWatchEvent.Sent 事件来判断何时将区块数据发送给玩家。
玩家死亡时复制数据(Copying data on player death)
默认情况下,实体 的数据附加项在玩家死亡时 不会被复制。若要在玩家死亡后自动复制某个附加项,需要在附加项构建器中设置 copyOnDeath。
如果需要更复杂的处理方式,可以通过监听 PlayerEvent.Clone 事件来实现:从原实体读取数据并赋值给新实体。在该事件中,可以使用 #isWasDeath 方法区分是因死亡重生还是从末地返回。这一点很重要,因为从末地返回时数据已经存在,所以此时需要避免数据重复。
例如:
NeoForge.EVENT_BUS.register(PlayerEvent.Clone.class, event -> {
if (event.isWasDeath() && event.getOriginal().hasData(MY_DATA)) {
event.getEntity().getData(MY_DATA).fieldToCopy = event.getOriginal().getData(MY_DATA).fieldToCopy;
}
});