Skip to content

Nbt — NBT 读写

NbtValue 是纯 Rust 的 SNBT 对象模型。事件载荷、实体快照、命令参数、方块数据出来的都是它。

rust
use levilamina::prelude::*;

let v = NbtValue::parse(r#"{name:"steve",hp:20.0f,tags:["a","b"]}"#)?;
assert_eq!(v.get("name").and_then(|n| n.as_str()), Some("steve"));

类型

rust
pub enum NbtValue {
    Byte(i8), Short(i16), Int(i32), Long(i64),
    Float(f32), Double(f64),
    String(String),
    List(Vec<NbtValue>),
    Compound(BTreeMap<String, NbtValue>),
    ByteArray(Vec<i8>), IntArray(Vec<i32>), LongArray(Vec<i64>),
}

复合标签用 BTreeMap,所以序列化结果是确定的——diff 稳定,测试稳定。

解析与序列化

API说明
NbtValue::parse(text)解析 SNBT,接受引擎输出的完整语法(含类型化数组、两种引号)
NbtValue::compound()一个空复合标签
v.to_snbt() -> String序列化成最小化 SNBT,键只在需要时加引号

取值

API说明
v.get(key)取子项,Option<&NbtValue>
v.get_mut(key)可变版本
v.path("a.b.c")点号路径取值
v.index(i)列表下标
v.insert(key, value) -> bool插入 / 覆盖;不是复合标签返回 false

投影方法:as_i64() as_f64() as_bool() as_str() as_list() as_compound() is_compound()

as_i64 会跨整数类型工作

Byte / Short / Int / Long 都能用 as_i64() 取。不用先判断具体是哪一种。as_f64() 同理覆盖 Float / Double

实战:从事件载荷里挖东西

rust
ctx.server().subscribe_event("PlayerUseItemOnEvent", EventPriority::Normal, |ev| {
    let Ok(v) = ev.value() else { return };
    let x = v.get("x").and_then(|n| n.as_i64()).unwrap_or(0);
    let name = v.path("_player.name").and_then(|n| n.as_str()).unwrap_or("?");
    println!("{name} 在 x={x} 用了个东西");
})?.forget();

path() 比嵌套一堆 get() 好读得多。

二进制 NBT

SNBT 对象模型是纯 Rust 的,但磁盘和网络上的二进制格式是跟着引擎版本走的,所以这两个方法委托给桥接,字节布局永远和运行中的服务器一致。

rust
use levilamina::nbt::NbtBinaryFormat;

let bytes = v.to_binary(NbtBinaryFormat::Disk)?;
let back  = NbtValue::from_binary(&bytes, NbtBinaryFormat::Disk)?;

NbtBinaryFormatDisk = 0(存档格式)、Network = 1(网络格式)。

和 serde 的关系

这个模块不依赖 serde。要把 NbtValue 和自己的结构体互转,自己写转换函数,或者在自己的 crate 里用 serde_json 中转一道。

给这个 crate 加 serde 依赖意味着每个链接它的模组都要背上——为了省几行转换代码不值当。