跳转至

错误与日志

读不出来的值怎么返回

这个 SDK 里凡是可能答不上来的,都返回 Result,而且错误里写着为什么。 没有任何东西返回一个看起来合理的默认值。

Rust
let dim = ev.dim()?;                  // 不是 .unwrap_or(0)
let pos = player.position()?;         // 不是悄悄给一个 (0, 0, 0)

理由是一次具体的事故。一个发生在自定义维度里的事件读不出维度,消费方写了 unwrap_or(0),于是每个这样的事件都被当成发生在主世界。土地保护随即在主世界里拒绝、 在别的所有地方放行。一行日志都没有,而这个行为看起来完全像是有意为之。

不知道的时候就拒绝。 整条规矩就这一句。

判断答不上来的时候

一个可能答不上来的判定,不会返回裸 bool。压成同一个 false,调用方只能猜; 压成 true,那是个安全洞。

所以 Ok(None) 和 Err 自始至终是两回事:

Rust
1
2
3
4
let rule = dimensions::rule(dim, DimensionRule::SpawnMonster)?;
// Ok(Some(false)) -> 登记过,而且是关的
// Ok(None)        -> 从没登记过,按原版行为
// Err             -> 这个问题根本问不出来

身份

PlayerSel::Name 不是身份。宿主匹配不到账号名时会退到显示名, 而显示名可以被别的模组改。一个玩家把自己的显示名改成某个离线玩家的账号名, 就能让所有按名字寻址的调用落到自己身上。

Rust
let p = Player::by_xuid(&id.xuid);     // 权限、经济、归属
let p = Player::by_name("Steve");      // 有人在聊天里打的一个名字

sel.is_stable() 告诉你手上拿的是哪一种。权限判定拿到 false 时应当格外小心。

日志

Rust
1
2
3
4
let log = ctx.logger();
log.info("started");
log.warn("配置里没有 X 这一项,本次不使用");
log.error(&format!("写不进地皮台账:{e}"));

这行会打在你的模组名下,不是 [pier] 下。

日志要能回答「我该做什么」。 「subscribe failed」不合格;「这个 id 在当前 BDS 版本上 不存在,注册表里相近的有 A、B、C」合格。Pier 自己的消息都按这条写,所以请读它们, 而不是只扫一眼有没有 error 字样。

成功路径不打 info。

panic

panic 不许穿回服务端,所以 SDK 交给宿主的每一个回调都包了围栏。你的处理函数里 panic 会被就地拦下、打日志,然后朝安全的方向降级:

在哪 会怎样
事件处理函数 拦下、打日志,本次修改丢弃
数据包拦截器 拦下、打日志,包原样放行
经济回调 拦下、打日志,按不否决处理
车道的表函数 拦下、打日志,返回兜底值
排期任务 拦下、打日志,只丢这一个任务

注意方向。panic 的数据包拦截器是放行而不是丢弃,因为全丢的表现是玩家进不了服, 而服务器看起来一切正常——那要难查得多。panic 的经济回调不否决,因为否决是更强的动作, 不该由一个 bug 触发。

这是围栏,不是功能。 你自己的错误请自己处理。