用法
# 基本用法
# 基础概述
大多数接口都在 NBT (opens new window) 类里面。这个 wiki 不一定覆盖了你的一些特定用法,所以必要时可以翻阅 Javadoc (opens new window),或者问你的 IDE 要智能提示!
# 基本的获取与设置数据
ReadableNBT (opens new window) 和 ReadWriteNBT (opens new window) 用于读取和写入 nbt:
// 创建一个空的 NBT 标签
ReadWriteNBT nbt = NBT.createNBTObject();
// 设置 NBT
nbt.setString("Stringtest", "Teststring");
nbt.setInteger("Inttest", 42);
nbt.setDouble("Doubletest", 1.5);
nbt.setBoolean("Booleantest", true);
// 更多的用法可用!去问你的 IDE,或者 Javadoc!
// 获取 NBT
String s1 = nbt.getString("Stringtest");
int i1 = nbt.getInteger("Inttest");
double d = nbt.getDouble("Doubletest");
boolean b = nbt.getBoolean("Booleantest");
// 或者可以这样
String s2 = nbt.getOrDefault("Stringtest", "fallback_value");
Integer i2 = nbt.getOrNull("Inttest", Integer.class);
// 键操作
nbt.getKeys(); // 获取所有键
nbt.hasTag("key"); // 检查一个标签是否存在
nbt.hasTag("key", NbtType.NBTTagString); // 检查一个标签是否存在,并且类型是否符合
nbt.removeKey("key"); // 删除一个标签
NBTType nbtType = nbt.getType("key"); // 获取标签类型
// 子符合标签
nbt.getCompound("subtag"); // 获取一个子标签,或者 null
nbt.getOrCreateCompound("subtag"); // 获取或创建一个子标签
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 序列化和反序列化
转换 NBT 到字符串,以及将字符串转换回 NBT
// 将 SNBT 解析为 NBT
ReadWriteNBT nbt = NBT.parseNBT("{Health:20.0f,Motion:[0.0d,10.0d,0.0d],Silent:1b}");
// 将 NBT 转换回 SNBT (对所有 NBT 对象有效)
String snbt = nbt.toString();
// 再次解析回 NBT
ReadWriteNBT nbt2 = NBT.parseNBT(snbt);
2
3
4
5
6
# 比较 NBT 数据
就像其它 Java 对象一样,你可以使用 equals
来比较 NBT。
要看具体哪些没有匹配,你可以使用 ReadableNBT#extractDifference
:
ReadWriteNBT nbt1 = NBT.parseNBT("{intTag:1,compoundTag:{floatTag:20.0f,booleanTag:1b}},intArray:[I;1,2]");
ReadWriteNBT nbt2 = NBT.parseNBT("{intTag:1,compoundTag:{floatTag:20.0f,booleanTag:0b}},intArray:[I;1,2,3],alsoIntTag:2");
ReadWriteNBT diff1 = nbt1.extractDifference(nbt2);
ReadWriteNBT diff2 = nbt2.extractDifference(nbt1);
2
3
4
5
对于上面的示例,调用 ReadableNBT#toString
会产生这样的结果:
diff1: {compoundTag:{booleanTag:1b},intArray:[I;1,2]}
diff2: {compoundTag:{booleanTag:0b},intArray:[I;1,2,3],alsoIntTag:2}
# 合并复合标签
你可以从任何给定的符合标签,合并数据到另一个复合标签。这个操作会覆写同名的标签。
示例:
Tag1: {Num1:1, Num2:2} Tag2: {Num1:7, Num3:3}
在合并 Tag2 到 Tag1 后:
tag1.mergeCompound(tag2);
Tag1: {Num1:7, Num2:2, Num3:3} Tag2: {Num1:7, Num3:3}
# 解析嵌套数据
要简化使用多重嵌套的 NBT,使用 resolve 方法可以直接获取,或者作用于这些嵌套标签。
复合标签使用点 .
来解析。如果你要在键名使用 .
,可以使用捺斜杠 \
来转义。
// 举个例子,下面这行代码:
nbt.resolveOrCreateCompound("foo.bar.baz");
// 与下面的代码一样,会获取/创建下面这样的子复合标签:
nbt.getOrCreateCompound("foo").getOrCreateCompound("bar").getOrCreateCompound("baz");
// 如果存在,则获取复合标签,反之 null
nbt.resolveCompound("foo.some.key.baz");
// 设置 foo/bar/baz/test 为 42
nbt.resolveOrCreateCompound("foo.bar.baz").setInteger("test", 42);
// 一个简明包含 . 的示例。设置 foo/some.key/baz/other 为 123
nbt.resolveOrCreateCompound("foo.some\\.key.baz").setInteger("other", 123);
// 类似地,你可以从嵌套的复合标签获取其它类型的值
String s = nbt.resolveOrDefault("foo.bar.Stringtest", "fallback_value");
Integer i = nbt.resolveOrNull("foo\\.bar.baz.Inttest", Integer.class);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 访问/创建列表以及它们的数据
注意
如果获取列表的时候,列表不存在,将会自动创建。列表没有 setter!
// 获取或创建一个字符串列表
ReadWriteNBTList<String> stringList = nbt.getStringList("list_key");
stringList.add("value");
// 获取列表的类型,在这个示例中是 NBTTagString
NBTType type = nbt.getListType("list_key");
// 你可以就像普通的 List 一样获取值
nbt.getStringList("list_key").get(0);
// 或者像这样获取
nbt.resolveOrNull("list_key[0]", String.class); // 获取列表的第一个值
// 你也可以使用负数,来获取列表的最后一个值
nbt.resolveOrDefault("list_key[-1]", "fallback_value"); // 获取列表的最后一个值,或使用默认值
// NBT 复合标签列表
// 获取列表 (如果需要的话将会创建)
ReadWriteNBTCompoundList nbtList = nbt.getOrCreateCompound("foo").getCompoundList("other_key");
// 向列表添加新的复合标签
ReadWriteNBT nbtListEntry = nbtList.addCompound();
nbtListEntry.setBoolean("bar", true);
// 向列表添加已有的复合标签
ReadWriteNBT otherNbtListEntry = nbtList.addCompound(NBT.parseNBT("{foo_bar:1b}"));
// 你可以从列表中获取复合标签
nbt.resolveCompound("foo.other_key[0]"); // 获取第一个复合标签,或者 null
nbt.resolveOrCreateCompound("foo.other_key[1]"); // 获取第二个复合标签,或者创建一个
// 或者像这样获取其中的数据
boolean bar = nbt.resolveOrDefault("foo.other_key[0].bar", false);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 处理物品
你可以使用 NBT.get
/NBT.modify
方法,向物品读取/写入NBT数据。
// 设置数据
NBT.modify(itemStack, nbt -> {
nbt.setString("Stringtest", "Teststring");
nbt.setInteger("Inttest", 42);
nbt.setDouble("Doubletest", 1.5d);
nbt.setBoolean("Booleantest", true);
// 更多的用法可用!去问你的 IDE,或者 Javadoc!
});
// 读取数据
NBT.get(itemStack, nbt -> {
String stringTest = nbt.getString("Stringtest");
int intTest = nbt.getOrDefault("Inttest", 0);
// 做更多操作
});
// 读取数据
String string = NBT.get(itemStack, nbt -> (String) nbt.getString("Stringtest"));
// 修改和获取数据一起
int someValue = NBT.modify(itemStack, nbt -> {
int i = nbt.getOrDefault("key", 0) + 1;
nbt.setInteger(i);
return i;
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# ItemMeta 用法
警告
永远不要混合 ItemMeta 和 NBT 的使用。
设置 ItemMeta 会覆盖任何对物品 NBT 的更改。
// 错误示例,不要这么做!
ItemMeta meta = itemStack.getItemMeta();
meta.setDisplayName("Modified!");
NBT.modify(itemStack, nbt -> nbt.setBoolean("modified", true));
itemStack.setItemMeta(meta); // 这个操作会撤销对 NBT 的所有更改!
// 正确示例, ItemMeta 和 NBT 都正确应用了
NBT.modify(itemStack, nbt -> nbt.setBoolean("modified", true));
// 在更改 NBT 之后再获取 ItemMeta 快照
ItemMeta meta = itemStack.getItemMeta();
meta.setDisplayName("Modified!");
itemStack.setItemMeta(meta);
// 如果你对 NBT & ItemMeta 做了任何更进一步的更改,
// 要在更改 NBT 后重新获取 ItemMeta!
2
3
4
5
6
7
8
9
10
11
12
13
14
15
或者,你可以在 NBT 作用于里安全地修改 ItemMeta:
// 使用 NBT 更新 ItemMeta
NBT.modify(itemStack, nbt -> {
nbt.setInteger("kills", nbt.getOrDefault("kills", 0) + 1);
nbt.modifyMeta((readOnlyNbt, meta) -> { // 不要在修改 meta 时修改 nbt!
meta.setDisplayName("Kills: " + readOnlyNbt.getOrDefault("kills", 0));
});
// 做更多操作
});
2
3
4
5
6
7
8
# 在 1.20.5+ 更改原版物品 NBT
重要事项
自 Minecraft 1.20.5 起,物品在运行时环境不存在原版的 NBT 了。
任何 NBT.get
或 NBT.modify
调用将只会访问物品的 custom_data
组件。
作为一种解决方案,你可以使用以下代码:
// 注意: 这些代码只能在 1.20.5+ 使用!
// 只读取原版数据
NBT.getComponents(item, nbt -> {
if (nbt.hasTag("minecraft:custom_name")) {
String customName = nbt.getString("minecraft:custom_name");
}
});
// 修改原版数据
NBT.modifyComponents(item, nbt -> {
nbt.setString("minecraft:custom_name", "{\"extra\":[\"foobar\"],\"text\":\"\"}");
});
2
3
4
5
6
7
8
9
10
11
12
13
# 处理实体和方块实体
处理实体和方块实体的方式和处理物品类似。
# 访问原版 nbt
为实体编写的示例代码也对方块实体有效。
// 获取数据
boolean silent = NBT.get(entity, nbt -> (boolean) nbt.getBoolean("Silent"));
// 修改数据
NBT.modify(entity, nbt -> {
nbt.setBoolean("Silent", true);
nbt.setByte("CanPickUpLoot", (byte) 1);
});
2
3
4
5
6
7
# 访问自定义数据
警告
自定义的(方块)实体持久数据储存只在 1.14+ 有效。
如果你需要支持低于 1.14 的版本,你只能使用将数据存储在物品的 NBT 里,然后让实体穿戴这个物品作为解决方案 (举个例子: 怪物的头盔栏位戴一个按钮,然后把数据存到按钮里)。
要读取/储存(方块)实体的自定义数据,你应该使用以 PersistentData
结尾的方法。
重要事项
当处理方块实体数据时,请确保方块实体在世界上存在。
举个例子,你不能在 BlockPlaceEvent
事件添加数据到箱子,因为箱子还没有被放置。在这样的情况下,你可以延时 1 tick 再执行操作,或者在代码手动放置箱子。
// 获取数据
boolean test = NBT.getPersistentData(entity, nbt -> (boolean) nbt.getBoolean("custom_key"));
// 修改数据
NBT.modifyPersistentData(entity, nbt -> {
nbt.setBoolean("custom_key", true);
nbt.setByte("custom_byte", (byte) 1);
});
2
3
4
5
6
7
注意
如果你计划为玩家储存数据,你可能需要考虑使用外置存储,而非弄乱世界文件夹里的玩家数据。任何写在持久储存里的数据会永远在那里,你无法在移除插件后抛弃冗余的数据,但以其他方式储存是完全可以抛弃冗余数据的。
像是每个玩家一个 NBT文件 的储存解决方案也许已经足够使用了 (而且基本原版都够用了,但你会有你自己的文件,而不是使用世界里面的那个)。在玩家进服时,缓存文件的数据,离开时/服务器关闭时保存数据,可选添加自动保存。
# 模拟 "/data merge" 命令
为物品/方块实体/实体等应用。
NBT.modify(zombie, nbt -> {
nbt.mergeCompound(NBT.parseNBT("{Silent:1b,Invulnerable:1b,Glowing:1b,IsBaby:1b}"));
});
2
3
# 处理方块
你可以使用之前的章节提到的示例,储存数据到方块实体里面 (方块实体有箱子、熔炉等等),但普通的方块没有 NBT 数据可以操作。
所以,你需要使用你自己的方块数据存储来储存自定义方块数据。
在 1.16.4 起,你可以储存数据到区块里,而且 NBT-API 允许你使用 NBTChunk
这么做:
ReadWriteNBT nbt = new NBTChunk(chunk).getPersistentDataContainer();
类似地,还有 NBTBlock
,允许你储存方块数据到区块数据里。
// 方块数据会储存到区块数据的 "blocks.x_y_z" 子标签
ReadWriteNBT nbt = new NBTBlock(block).getData();
2
然而,请记住这些数据是仅用位置链接的,如果方块发生了破坏/更改/爆炸/移动等等,数据依然会在那个位置,除非手动清除/移动!
此外,由于数据储存在区块里,这还会额外占用区块在磁盘中的文件大小。
# 其它
提示
除了这个页面,你也可以在 示例用法 查看一些代码示例。
# NBT文件
NBTFileHandle 允许你处理 NBT 文件。
// 如果文件不存在,会自动创建
NBTFileHandle nbtFile = NBT.getFileHandle(new File("directory", "test.nbt"));
// 设置数据
nbtFile.setString("foo", "bar");
// 在应用更改后保存文件
nbtFile.save();
2
3
4
5
6
或者,你可以不维护文件链接而读取 NBT 文件。
不像 NBTFileHandle,如果文件不存在,将不会自动创建。
File file = new File("directory", "test.nbt");
// 从文件读取数据 (如果文件不存在,将会返回一个空的复合标签)
ReadWriteNBT nbt = NBT.readFile(file);
// 保存 NBT 到文件
NBT.writeFile(file, nbt);
2
3
4
5
# 转换 Minecraft 对象到 NBT 和字符串
基本的思路是你可以转换数据到不同的形式:
Minecraft 对象 <-> NBT <-> 字符串 (SNBT)
# 物品
虽然 NBT.get/modify
允许你修改物品直接的 NBT (或者自 1.20.5 以来仅修改 custom_data
组件),但 ItemStack 对象的 NBT 表示项目的序列化数据。它包括物品类型、数量以及所有额外的物品 NBT 标签。
举个例子,当调用 NBT.get
将会获得这样的结果:
{foo:"bar",points:12,test:1b}
从 NBT.itemStackToNBT
读取的 ItemStack 对象的 NBT 标签长这样:
{components:{"minecraft:custom_data":{foo:"bar",points:12,test:1b}},count:2,id:"minecraft:stone"}
或者在 1.20.5 以前的版本长这样:
{Count:2b,id:"minecraft:stone",tag:{foo:"bar",points:12,test:1b}}
# 序列化/反序列化物品
// 保存
ReadWriteNBT nbt = NBT.itemStackToNBT(itemStack);
ReadWriteNBT nbt = NBT.itemStackArrayToNBT(itemStacks);
// 恢复
ItemStack itemStack = NBT.itemStackFromNBT(nbt);
ItemStack[] itemStacks = NBT.itemStackArrayFromNBT(nbt);
// 提醒一下可以使用 (NBT <-> String)
String snbt = nbt.toString();
ReadWriteNBT nbt = NBT.parseNBT(snbt);
2
3
4
5
6
7
8
9
# 实体和方块实体
// 保存
ReadWriteNBT entityNbt = NBT.createNBTObject();
NBT.get(entity, entityNbt::mergeCompound);
// 恢复
NBT.modify(entity, nbt -> {
// 你也许也想先过滤实体NBT标签,
// 示例: 删除一些像是位置、UUID、实体ID 等的数据
nbt.mergeCompound(entityNbt);
});
2
3
4
5
6
7
8
9
# 游戏档案 GameProfile
// 保存
ReadWriteNBT nbt = NBT.gameProfileToNBT(profile);
// 恢复
GameProfile profile = NBT.gameProfileFromNBT(nbt);
2
3
4
# 接口代理
你可以定义你自己的接口,继承 NBTProxy
来创建 NBT 包装器。
以 has
/get
/set
开头的方法将会被解析为它们各自的调用。
interface TestInterface extends NBTProxy {
// 执行: return nbt.hasTag("kills");
boolean hasKills();
// 执行: nbt.setInteger("kills", amount);
void setKills(int amount);
// 执行: return nbt.getInteger("kills");
int getKills();
// 也支持这样
default void addKill() {
setKills(getKills() + 1);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
Getter 也支持返回另一个继承了 NBTProxy
的接口。
以及使用 @NBTTarget
注解,你可以对数据有着更进一步的控制。它允许设置这些方法 (has
/get
/set
) 的类型,以及数据的键名。
interface TestInterface extends NBTProxy {
// 将会使用 "other" 键,从数据获取 PointsInterface 复合标签
@NBTTarget(type = Type.GET, value = "other")
PointsInterface getOtherInterface();
}
interface PointsInterface extends NBTProxy {
int getPoints();
void setPoints(int points);
}
2
3
4
5
6
7
8
9
10
要支持其它数据类型比如 ItemStack,你需要重写 init 方法,然后注册合适的处理器。
interface TestInterface extends NBTProxy {
@Override
default void init() {
registerHandler(ItemStack.class, NBTHandlers.ITEM_STACK);
registerHandler(ReadableNBT.class, NBTHandlers.STORE_READABLE_TAG);
registerHandler(ReadWriteNBT.class, NBTHandlers.STORE_READWRITE_TAG);
}
ItemStack getItem();
void setItem(ItemStack item);
ReadWriteNBT getBlockStateTag();
void setBlockStateTag(ReadableNBT blockState);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
NBTHandlers
类包含了一些预定义的处理器。
# 自定义处理器
如果你需要支持不在 NBTHandlers
里面的自定义数据类型,你可以创建一个新的 NBTHandler
来编写自己的处理器。
你可以参考 NBTHandlers (opens new window) 类来查看默认实现是怎么写的。
# Data fixer 工具类
DataFixerUtil
允许你从旧版本更新 NBT 到新一点的版本
举个例子,给出 1.12.2 版本的输入:
{Count:42,id:"cobblestone",tag:{display:{Name:"test"},ench:[{id:34,lvl:3}]}}
你可以将其更新到 1.20.6:
{components:{"minecraft:custom_name":'{"text":"test"}',"minecraft:enchantments":{levels:{"minecraft:unbreaking":3}}},count:42,id:"minecraft:cobblestone"}
使用以下代码即可:
DataFixerUtil.fixUpItemData(nbt, DataFixerUtil.VERSION1_12_2, DataFixerUtil.VERSION1_20_6);
你也可以使用 DataFixerUtil.getCurrentVersion()
来获取当前服务器使用的版本。