本帖最后由 Dahesor 于 2023-1-1 12:22 编辑

☆【命令】命令教程"真"从零开始(Y )☆
NBT选择器参数的一切,大概


声明:
1. 本系列教程默认读者拥有关于Minecraft游戏本身的基础了解。
2. 本系列全部教程绝对适用于当前Java最新版 (1.19.3) 但本帖适用于自1.13以来的任何版本
3. 本系列教程致力于基础原理而非使用方法,因为某些原因,这是本声明里最重要的一条。
4. 本教程 需要读者有特定的命令知识。这些这些内容在 下方表格中列出

<<<<<< 返回系列目录 ←←

需要前置知识:
请确保你理解下列的内容。下表的所有内容一定在本系列教程的某一帖讨论过,所以若您是从本系列第一帖起阅读至今,可以不用看。
对于跳跃式阅读的读者,还请检查一番:





D在回顾自己的教程的时候,发现了一件神奇的事,那就是我从来没有讲过任何关于选择其参数中nbt=的任何东西。
补上补上。


前言

以前讲过,当我们使用选择器时,可以通过选择器参数作进一步限制。
比如选择器@e会选择所有实体,但是@e[type=fox,distance=..6,limit=1,sort=nearest]会选择执行位置6格内的离得最近的一只狐狸。
这部分内容在 系列第六帖 说过了。不过当时我们并没有实际到所有的选择器参数,而是有一个没有讲,即是匹配目标有无指定NBT的选择器参数“nbt=”


1. 基本

本选择器参数非常简单。nbt=只是在侦测,从实体的根标签算起,有没有等号后面的nbt
如果你使用/data get命令获取一个实体的NBT,大概长这样。



那么

  1. @e[nbt={Air:300s}]
复制代码

就可以选中该实体,因为{Air:300s}是其NBT的一部分。 或者说,该实体的NBT中含有" { Air : 300s } "
注意这个选择器参数的逻辑是,只要你指定的部分满足就好,你如你指定了Air:300s,那么实体只要有该NBT就能通过,即使该实体还有大量你没有指定的NBT。

该选择器参数支持反选:

  1. @e[nbt=!{Air:300s}]
复制代码
选中所有没有{Air:300s}的实体。 或者说,选中所有氧气不满的实体。


亦可以使用任何列表,符合标签等:
  1. @e[nbt={Air: 300s, data: {test: 3b, test2: 4s, test3: [1, 2, 4, 55] }}]
复制代码
选中所有NBT含有{Air: 300s, data: {test: 3b, test2: 4s, test3: [1, 2, 4, 55] }}的实体。


你也可以将多个nbt=放到一起,就像你对其他选择其参数做的那样:

  1. @e[nbt={Air: 300s, data: {test2: 4s, test3: [1, 2, 4, 55]}}, nbt={data: {test: 1b}}, nbt=!{Air: 200s}]
复制代码
选中所有NBT含有{Air: 300s, data: {test2: 4s, test3: [1, 2, 4, 55]}},含有{data: {test: 1b}},且不包含{Air: 200s}的实体。


看起来很简单,是吧。
的确没什么,不过我需要给一些额外的提示与注意事项。


2. "包容性"

假设一个标记实体 有如下NBT:

{ data :{ mode : "normal" , name : "zombie" }}
(部分NBT省略)
那么,显而易见地,我们可以使用选择器:
  1. @e[nbt={data:{mode:"normal", name:"zombie"}}]
复制代码
来选中它。

但是不止如此。除了将data这一个复合标签内的所有子标签都标明出来,选择器
  1. @e[nbt={data:{mode:"normal"}}]
复制代码
或者
  1. @e[nbt={data:{name:"zombie"}}]
复制代码
都可以选中这个标记实体。
你会许已经注意到了,对于复合标签,匹配复合标签时,成功的前提是,只要复合标签包含了在选择器中指定的标签即可,而并不需求选择器中指定出目标的所有标签。

这种逻辑,让我们起一个名字,叫做“ 包容性 ”。

复合标签和列表都有 包容性 ,即不需求选择器指明其包含的所有内容。


所以在上面的例子中,选择器@e[nbt={data:{name:"zombie"}}]只包含了目标NBT的一部分内容,就可以匹配成功。
那么这个呢?
  1. @e[nbt={data:{}}]
复制代码
我们写的是"data:{}"。data标签中没有写入任何NBT。
本选择器也可以选中上述实体,因为一个“ 空的 ”复合标签当然是一个“ 有内容 ”的复合标签的一部分。
上面的选择器,只要实体有名为data的第一层复合标签,那么无论内容如何,都能选中。

你可能还记得,根标签也是一个复合标签,所以说:
  1. @e[nbt={}]
复制代码
本复合标签可以选中所有实体,与@e等价
而反过来,以下选择器永远无法选中任何实体:
  1. @e[nbt=!{}]
复制代码

列表


那么,既然复合标签有这样的“包容性”,那么列表如何呢?

列表也是有包容性的,我们不需要指定列表中的所有项目,只要指定其中的至少一项即可。
假设一个标记实体有NBT如下:
{ data :{ list:[1,2,3,4] }}
(部分NBT省略)
当然,下列选择器可以选中这个实体:
  1. @e[nbt={data:{list:[1, 2, 3, 4] }}]
复制代码
但是因为列表拥有包容性,所以我们并不需要将这个列表中包含的所有元素列出来,只要有至少一个元素列出来即可。
也就是说,下列的选择器也均能选中该实体:

  1. @e[nbt={data:{list:[1] }}]
复制代码
  1. @e[nbt={data:{list:[1, 2] }}]
复制代码
  1. @e[nbt={data:{list:[2, 3, 4] }}]
复制代码
注意我们只写入了一个或几个其中的元素,也能选中该实体。
但是不只!
NBT选择器中,列表不仅不需要包含所有元素,还不在意元素的顺序!
这意味着,与其写[1,2],[2,1]也一样可行。
那么,下列的例子也都能选中上面的实体:

  1. @e[nbt={data:{list:[4, 3, 2, 1] }}]
复制代码
  1. @e[nbt={data:{list:[3, 2, 1] }}]
复制代码
  1. @e[nbt={data:{list:[3, 1] }}]
复制代码
  1. @e[nbt={data:{list:[2, 3, 4, 1] }}]
复制代码


然而,列表的另一个特性是,它的“包容性”不包含空列表,换言之,虽然说@e[nbt={data:{list:[1] }}]可以匹配到实体,但是@e[nbt={data:{list:[] }}]却无法匹配到该实体。
@e[nbt={data:{list:[] }}]只能匹配到空列表。


数列

说完了列表,让我们最后看一看数列。既然列表有包容性,那么和其十分相似的数列有没有呢?
抱歉,完全没有。

[I; 1, 2, 3, 4]必须使用[I; 1, 2, 3, 4]匹配,不能多一个少一数字,也不能打乱顺序。
(实际上,这很合理)


3. 默认命名空间


让我们看一下物品实体的NBT格式。

来源于 wiki 物品(实体) 。截图 2022.3.30 14:27。当前页面可能已经被更改。

我们可以注意到,物品实体是“什么物品”被存储在其NBT的 Item 标签下。我们也可以使用summon命令配合来召唤一个特定物品:
summon item ~ ~ ~ { Item :{ Count : 1 b , id : " sand " }}
本命令可召唤一个沙子
没有问题。但是我们该如何选中一个“沙子”物品实体呢?
直接把我们生成用的NBT拷贝进来行不行?像这样:
kill @e [ type = item , nbt = { Item :{ id : " sand " }} ]
(清除所有带有NBT {Item:{id:" sand "}}的物品实体)

可以么?

你可以试一下,向地面上扔一个沙子,然后输入上面的命令看看能不能将其清除。

答案是不能。
游戏告诉我们“找不到实体”
为什么? {Item:{id:" sand "}}不是该物品实体的NBT么?
游戏不会这样就出错,nbt={Item:{id:" sand "}}找不到该实体只能证明该实体就是不包含你指定的NBT。
但我们创建的时候就输入的这个NBT,为什么还不一样了呢?

让我们使用/data命令查询一下就知道了:

使用/data get命令( 系列第12帖 ),我们可以看到,物品实体的NBT是这样的:


(Java 1.17.1)

仔细观察上面的实体数据,你就会发现,其包含的NBT并不是{Item:{id:" sand "}},而是{Item:{id:" minecraft:sand "}}
很简单,你忘记了, 默认命名空间(Default Namespace)
很显然,{Item:{id:" sand "}}和{Item:{id:" minecraft:sand "}}不是相同的NBT( sand minecraft:sand 不是两个相同的字符串),所以无法找到实体。

啊不是,不是说默认命名空间可以省略么?
的确可以省略,但可以省略是因为游戏会 自动补全 。自动补全的前提是游戏要知道你所输入的东西 需要补全。 但是这里我们只是在侦测两串NBT是不是一样,而答案是不一样。对于这个“NBT侦测机制”来说,它根本不知道" sand "意味着什么,也不知道 sand minecraft:sand 表达同一种东西。它只知道“ 它们不一样
它们不一样 ”。

就像我们都知道土豆和马铃薯是一个东西一样。但是它们只是“指代一个东西”,而“字符”是不一样的。

不知道我讲明白没有


总结一下
当游戏运行时,(几乎)所有被更新的不带默认命名空间的数据都会被自动替换为带有默认命名空间的版本。也就是说当我们需要进行NBT判定时,对于命名空间ID 总是 应当附上默认命名空间以使判定成功。
这包括:

方块实体ID,物品,实体种类,生物的记忆NBT(Brain),画的名字,村民的职业/种类,状态效果,药水效果,魔咒,和粒子的种类。

当对这些内容进行NBT判定时一定记住加默认命名空间。




4. 使用原则

  • 实体是无法保存自定义NBT的,所以nbt=参数对于实体只应该用于侦测实体状态而非区分。区分实体应该使用标签
  • 当想要侦测(物品栏内,容器内等)指定物品时,尝试在创建物品时使用自定义NBT,而非侦测一些不是特别靠谱的东西,比如物品Lore
  • 更多补充?


后记

各位好,这里是已经鸽了4个月的Dahesor。
好说歹说是写出了这个,并编号为Y。接下来还会写第18帖,及有关JSON应用问题。
嘛,
以后见。



下一篇: