Doc ID:
SIRC-080
Item-NBT-API 完整知识文档
Item-NBT-API 是一个用于在 Minecraft 服务器插件中操作 NBT 数据的库,支持 Spigot 和 Paper 服务器,从 1.8 版本开始支持,1.7.10 也可运行但部分功能可
Overview
Item-NBT-API 完整文档
概述
Item-NBT-API 是一个用于在 Minecraft 服务器插件中操作 NBT 数据的库,支持 Spigot 和 Paper 服务器,从 1.8 版本开始支持,1.7.10 也可运行但部分功能可能不可用。
核心类
主要类
- NBT: 主要的工具类,包含所有核心功能
- ReadableNBT: 只读 NBT 接口
- ReadWriteNBT: 可读写 NBT 接口
- NBTFileHandle: 用于操作 NBT 文件
- NBTChunk: 用于操作区块数据
- NBTBlock: 用于操作方块数据
- DataFixerUtil: 数据版本修复工具
基本用法
创建和操作 NBT 数据
// 创建新的空 NBT 标签
ReadWriteNBT nbt = NBT.createNBTObject();
// 设置 NBT 数据
nbt.setString("Stringtest", "Teststring");
nbt.setInteger("Inttest", 42);
nbt.setDouble("Doubletest", 1.5);
nbt.setBoolean("Booleantest", true);
// 获取 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"); // 获取或创建子标签
NBT 与字符串转换
// 解析 SNBT 为 NBT
ReadWriteNBT nbt = NBT.parseNBT("{Health:20.0f,Motion:[0.0d,10.0d,0.0d],Silent:1b}");
// 将 NBT 转换为 SNBT
String snbt = nbt.toString();
// 重新转换为 NBT
ReadWriteNBT nbt2 = NBT.parseNBT(snbt);
数据解析
解析嵌套数据
// 使用点号分隔复合体
nbt.resolveOrCreateCompound("foo.bar.baz");
// 获取复合体(如果存在)
nbt.resolveCompound("foo.some.key.baz");
// 设置嵌套值
nbt.resolveOrCreateCompound("foo.bar.baz").setInteger("test", 42);
// 获取嵌套值
String s = nbt.resolveOrDefault("foo.bar.Stringtest", "fallback_value");
Integer i = nbt.resolveOrNull("foo\.bar.baz.Inttest", Integer.class);
列表操作
字符串列表
// 获取或创建字符串列表
ReadWriteNBTList<String> stringList = nbt.getStringList("list_key");
stringList.add("value");
// 获取列表类型
NBTType type = nbt.getListType("list_key");
// 获取列表值
nbt.getStringList("list_key").get(0);
nbt.resolveOrNull("list_key[0]", String.class); // 获取第一个值
nbt.resolveOrDefault("list_key[-1]", "fallback_value"); // 获取最后一个值
复合体列表
// 获取列表(如果不存在则创建)
ReadWriteNBTCompoundList nbtList = nbt.getOrCreateCompound("foo").getCompoundList("other_key");
// 添加新复合体到列表
ReadWriteNBT nbtListEntry = nbtList.addCompound();
nbtListEntry.setBoolean("bar", true);
// 添加现有 NBT 复合体到列表
ReadWriteNBT otherNbtListEntry = nbtList.addCompound(NBT.parseNBT("{foo_bar:1b}"));
// 获取列表中的复合体
nbt.resolveCompound("foo.other_key[0]"); // 获取第一个复合体
nbt.resolveOrCreateCompound("foo.other_key[1]"); // 获取第二个复合体或创建
// 从列表复合体中获取数据
boolean bar = nbt.resolveOrDefault("foo.other_key[0].bar", false);
物品操作
设置物品数据
NBT.modify(itemStack, nbt -> {
nbt.setString("Stringtest", "Teststring");
nbt.setInteger("Inttest", 42);
nbt.setDouble("Doubletest", 1.5d);
nbt.setBoolean("Booleantest", true);
});
读取物品数据
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;
});
ItemMeta 使用注意事项
警告: 不要混合使用 ItemMeta 和 NBT。
// 错误示例 - 不要这样做!
ItemMeta meta = itemStack.getItemMeta();
meta.setDisplayName("Modified!");
NBT.modify(itemStack, nbt -> nbt.setBoolean("modified", true));
itemStack.setItemMeta(meta); // 将撤销所有 NBT 更改!
// 正确示例 - 在 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));
});
});
1.20.5+ 版本的特殊处理
从 Minecraft 1.20.5 开始,ItemStacks 在运行时不再有原版 NBT。
// 仅读取原版数据(1.20.5+)
NBT.getComponents(item, nbt -> {
if (nbt.hasTag("minecraft:custom_name")) {
String customName = nbt.getString("minecraft:custom_name");
}
});
// 修改原版数据(1.20.5+)
NBT.modifyComponents(item, nbt -> {
nbt.setString("minecraft:custom_name", "{"extra":["foobar"],"text":""}");
});
实体和方块实体操作
访问原版 NBT
// 获取数据
boolean silent = NBT.get(entity, nbt -> (boolean) nbt.getBoolean("Silent"));
// 修改数据
NBT.modify(entity, nbt -> {
nbt.setBoolean("Silent", true);
nbt.setByte("CanPickUpLoot", (byte) 1);
});
访问自定义数据
警告: 自定义(方块)实体 NBT 的持久数据存储仅在 1.14+ 版本可用。
// 获取数据
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);
});
模拟 "/data merge" 命令
NBT.modify(zombie, nbt -> {
nbt.mergeCompound(NBT.parseNBT("{Silent:1b,Invulnerable:1b,Glowing:1b,IsBaby:1b}"));
});
方块操作
区块数据存储(1.16.4+)
ReadWriteNBT nbt = new NBTChunk(chunk).getPersistentDataContainer();
方块数据存储
// 方块数据将存储在区块数据的 "blocks.x_y_z" 子标签中
ReadWriteNBT nbt = new NBTBlock(block).getData();
注意: 此数据仅与位置关联,如果方块被破坏/更改/爆炸/移动等,数据仍将保留在该位置,除非手动清除/移动!
NBT 文件操作
NBTFileHandle
// 如果文件不存在将自动创建
NBTFileHandle nbtFile = NBT.getFileHandle(new File("directory", "test.nbt"));
// 设置数据
nbtFile.setString("foo", "bar");
// 保存文件
nbtFile.save();
读取文件(不维护链接)
File file = new File("directory", "test.nbt");
// 从文件读取数据(如果文件不存在则返回空复合体)
ReadWriteNBT nbt = NBT.readFile(file);
// 保存 NBT 到文件
NBT.writeFile(file, nbt);
Minecraft 对象与 NBT/字符串转换
物品序列化/反序列化
// 保存
ReadWriteNBT nbt = NBT.itemStackToNBT(itemStack);
ReadWriteNBT nbt = NBT.itemStackArrayToNBT(itemStacks);
// 恢复
ItemStack itemStack = NBT.itemStackFromNBT(nbt);
ItemStack[] itemStacks = NBT.itemStackArrayFromNBT(nbt);
// NBT <-> 字符串转换
String snbt = nbt.toString();
ReadWriteNBT nbt = NBT.parseNBT(snbt);
实体序列化
// 保存
ReadWriteNBT entityNbt = NBT.createNBTObject();
NBT.get(entity, entityNbt::mergeCompound);
// 恢复
NBT.modify(entity, nbt -> {
// 可能需要先过滤 entityNbt,例如移除位置、uuid、entityId 等数据
nbt.mergeCompound(entityNbt);
});
游戏配置文件
// 保存
ReadWriteNBT nbt = NBT.gameProfileToNBT(profile);
// 恢复
GameProfile profile = NBT.gameProfileFromNBT(nbt);
接口代理
可以定义扩展 NBTProxy 的接口来创建 NBT 包装器。
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);
}
}
使用 @NBTTarget 注解
interface TestInterface extends NBTProxy {
// 将从 "other" 键的数据中获取 PointsInterface
@NBTTarget(type = Type.GET, value = "other")
PointsInterface getOtherInterface();
}
interface PointsInterface extends NBTProxy {
int getPoints();
void setPoints(int points);
}
支持其他数据类型
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);
}
数据修复工具
DataFixerUtil 允许将 NBT 从旧版本更新到新版本。
// 例如,将 1.12.2 的数据更新到 1.20.6
DataFixerUtil.fixUpItemData(nbt, DataFixerUtil.VERSION1_12_2, DataFixerUtil.VERSION1_20_6);
// 也可以使用当前版本
DataFixerUtil.fixUpItemData(nbt, DataFixerUtil.VERSION1_12_2, DataFixerUtil.getCurrentVersion());
示例用法
设置头颅皮肤
final String textureValue = "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYTQyY2M5MjAzYzkwYjg5YmRhYzFkZjI4NDE2NzI2NmI5NTNkZmViZjNjNDY5MGE3Y2QwYjE1NzkxYTYyZTU4MiJ9fX0=";
// 创建 ItemStack
final ItemStack item = new ItemStack(Material.PLAYER_HEAD);
// 应用 NBT
NBT.modify(item, nbt -> {
ReadWriteNBT skullOwnerCompound = nbt.getOrCreateCompound("SkullOwner");
skullOwnerCompound.setUUID("Id", UUID.randomUUID());
skullOwnerCompound.getOrCreateCompound("Properties")
.getCompoundList("textures")
.addCompound()
.setString("Value", textureValue);
});
创建自定义僵尸
// 创建僵尸
Zombie zombie = (Zombie) world.spawnEntity(location, EntityType.ZOMBIE);
// 修改原版 NBT
NBT.modify(zombie, nbt -> {
nbt.setBoolean("Silent", true);
nbt.setByte("CanPickUpLoot", (byte) 1);
// 添加属性修饰符
ReadWriteNBTList<ReadWriteNBT> list = nbt.getOrCreateCompound("Attributes")
.getCompoundList("generic.attack_damage");
ReadWriteNBT listEntryNbt = list.addCompound();
listEntryNbt.setString("Name", "generic.attack_damage");
listEntryNbt.setDouble("Base", damageValue);
});
// 修改自定义数据
NBT.modifyPersistentData(zombie, nbt -> {
nbt.setBoolean("custom_zombie", true);
});
读取世界数据
// 获取主世界文件夹
File worldDataFolder = Bukkit.getWorlds().getFirst().getWorldFolder();
// 读取等级数据
NBTFileHandle levelNbtFile = NBT.getFileHandle(new File(worldDataFolder, "level.dat"));
// 获取世界名称
String worldName = levelNbtFile.resolveOrNull("Data.LevelName", String.class);
// 读取玩家数据
UUID playerUuid;
File playerFile = new File(worldDataFolder, "playerdata/" + playerUuid + ".dat");
if (!playerFile.exists()) {
return;
}
NBTFileHandle playerNbtFile = NBT.getFileHandle(playerFile);
// 更改玩家生命值
float health = playerNbtFile.getFloat("Health");
playerNbtFile.setFloat("Health", health + 5);
// 保存文件
playerNbtFile.save();
包结构
主要包名:de.tr7zw.changeme.nbtapi
核心类:
de.tr7zw.changeme.nbtapi.NBTde.tr7zw.changeme.nbtapi.ReadableNBTde.tr7zw.changeme.nbtapi.ReadWriteNBTde.tr7zw.changeme.nbtapi.NBTFileHandlede.tr7zw.changeme.nbtapi.NBTChunkde.tr7zw.changeme.nbtapi.NBTBlockde.tr7zw.changeme.nbtapi.DataFixerUtilde.tr7zw.changeme.nbtapi.NBTHandlersde.tr7zw.changeme.nbtapi.NBTProxy
注意事项
- 版本兼容性: 支持 Spigot/Paper 1.8+,1.7.10 部分功能可能不可用
- ItemMeta 冲突: 不要混合使用 ItemMeta 和 NBT 操作
- 1.20.5+ 变化: 从 1.20.5 开始,物品的原版 NBT 处理方式发生变化
- 持久数据存储: 自定义实体 NBT 持久存储仅在 1.14+ 可用
- 方块实体存在性: 操作方块实体时确保方块实体已在世界中存在
- 数据清理: 自定义数据会永久存储,插件移除时可能留下冗余数据
最佳实践
- 使用
resolve方法处理嵌套数据 - 对于自定义实体数据,使用
PersistentData方法 - 考虑使用外部存储而不是玩家世界文件存储玩家数据
- 使用
DataFixerUtil处理版本间数据迁移 - 对于复杂数据结构,考虑使用接口代理模式