本帖最后由 Dahesor 于 2022-3-28 04:30 编辑
【命令】命令教程"真"从零开始(十二)MC数据运算三种方法之一的修改NBT,/data
声明:
1. 本系列教程默认读者拥有关于Minecraft游戏本身的基础了解。
2. 本系列全部教程绝对适用于当前Java最新版(1.18)。但本帖的内容适用于自1.15以来的任意版本。除此之外,大体结构仍适用于1.14,1.13等,仅有细微不同。
3. 本系列教程致力于基础原理而非使用方法,因为某些原因,这是本声明里最重要的一条。
4. 本教程需要读者有特定的命令知识。这些这些内容在下方表格中列出:
需要前置知识:
请确保你理解下列的内容。下表的所有内容一定在本系列教程的某一帖讨论过,所以若您是从本系列第一帖起阅读至今,可以不用看。
对于跳跃式阅读的读者,还请检查一番:
前言
数据操作是一个十分重要又高大上的课题。
我们所谓的数据操作实际上就是对特定数字的提取,运算,代入,与导出。
在Minecraft我的世界,数据操作主要由3条不同的命令实现:
- 用于记录,运算的计分板命令/scoreboard,这个我们在系列第5帖已经说过了。
- 用于导入,导出的/execute的子命令store,这个我们会在以后探讨。
- 用于查询,修改的NBT命令/data,这就是我们今天的内容。
再次提醒,在阅读本帖之前请先阅读本系列的第11(X)帖,这是你需要的知识背景。还有。本文的一些字符被添加了颜色或大小,这些只是为了让你更好理解。命令本身与颜色等无关。
1. 命令/data的概述
/data为Java版独有特性
上面我们说了一些有点高大上的内容,但说回今天要讨论的命令本身——/data命令是用来干什么的?
本命令基础上说,作用就是查询,修改,与存储NBT。
整个命令不过是对于特定的NBT做出这几种调整罢了。
让我们首先看向/data的格式:
或者说:
- /data (get|merge|modify|remove) (entity|block|storage)
复制代码
/data这个指令有4个子指令,分别对应了3种不同的“修改”,
这些子指令是:查询(get),删除(remove),与修改(merge与modify):
它们紧接着data之后,决定了“你要干什么”。
而在无论哪一条子指令后,接下来要写入的是NBT的来源,它可以是实体(entity)的NBT,也可以是方块(block)或是存储(storage)的。
这个来源即是“你要对谁执行操作”。
对于entity,我们需要一个选择器,对于方块,我们需要坐标,而对于storage,我们需要它的存储路径(这个下面讲)。
我们可以用下面的表格来展示:
/data
get modify merge remove
entity <选择器> 如/data get entity @s …… block <x> <y> <z> 如/data modify block 13 4 56 …… storage foo:bar 如/data remove minecraft:test ……
命令/data的结构大概就长这样。
当然上面列出的只是前面的部分。当你确定了子命令与目标后,紧接着,根据子命令的不同还要加上更多元素来指定命令。
接下来,我们将挨个讲述这4条子命令:
先从最简单的开始吧。
2. 修改子命令merge
导图
/data get modify merge >>>>>>>>我们在这里 remove
entity <选择器> 如/data get entity @s …… block <x> <y> <z> 如/data modify block 13 4 56 …… storage foo:bar 如/data remove minecraft:test ……
你可能注意到了,有两个子命令:merge与modify都是“修改”。
两者的区别在于,modify比merge更“高级”。
merge的所有功能modify也都有,但modify更加的复杂,功能更多。我们一般会把merge称为“合并”。
所以,先让我们从merge开始吧:
先把这个格式抛在一边,我们简单地举个例子:
有1只僵尸,生命值现在是10点血(5心),那么该如何把它的生命值调到20点?
我们当然可以简单地使用/effect为它回复血量,但是该如何使用/data从数据层面修改呢?
用于存储生命值的NBT为“Health”,数据类型为小数(单精度浮点数)float (f)。
一个描述了"20生命值"的NBT应为Health:20f。
那么,简单地把它扔进复合标签,再放到命令后面:
- /data merge entity @e[type=zombie,limit=1] {Health:20f}
复制代码
让我们做一个命令解析:
data 命令头 数据命令
merge 子命令 修改(merge)
entity 目标 实体(entity)
- @e[type=zombie,limit=1] 选择器 选择1(limit=1)只僵尸(type=zombie)
{Health:20f} NBT 生命值:20
结论:
本命令可以将1只僵尸的生命值修改为20。
/data命令有个比较麻烦的地方,就是它只处理单个目标。这就是为什么我在选择器里打上了"limit=1"的原因。
当你使用/data时,只要使用了@a或者是@e,必须使用“limit=1”限制数量为1个。
咳咳,好。
那你现在可能有点激动——啊啊,我可以随便修改生命值啦。
那,我能不能在我自己快死的时候这么给我自己来一下:
- /data merge entity @p {Health:20f}
复制代码 将最近玩家的生命值修改为20点。
这样很牛有没有。
但是我得给你泼冷水,因为上面的这个命令无法成功修改。
为什么呢?
因为,玩家的NBT是无法被修改的
这点要注意,/data命令无法修改玩家的NBT。
我们的确可以使用/effect给自己一个lv255的瞬间治疗效果来回满生命值,但是,MC阻止直接使用/data来直接修改玩家的任何NBT。
当然,只是无法修改,想要读取,复制还是可以的,我们会在后面讲述。
好,在结束这一子命令之前,让我们再看一个例子。
现在,我们有一个掉落物,就是浮在地面上等着玩家来捡的那个。
它是实体的一种,而且注意的是所有物品的掉落物都是同一个实体,ID为“Item”。
这个掉落物是什么东西是由其NBT决定的。
所以说,我们可以通过/data更改其NBT让一个掉落的“石头”,直接变成“沙子“。
那,该如何使用/data做到呢?
掉落物包含的物品是由其NBT”Item“决定的,它是一个复合标签,内部有3个子标签分别决定了“物品是什么(id)”,“这一坨东西有几个,(Count)”,“与该物品自己的标签(tag),比如有没有附魔”。
你可能知道sand是沙子的意思,所以:
- /data merge entity @e[limit=1,type=item] {Item: {id: "sand"}}
复制代码 将一个掉落物的“包含的物品”改为石头
你要注意的是,Item这个标签下的其他子标签,比如Count(数量)或tag(其标签)并不会被本命令改动,因为我们只指定了id。
这玩意之前一坨有5个,那么改了之后虽然变成了石头但同样是5个。
如果这玩意原来有一个锋利V的附魔,那么命令运行后得到的5块石头`同样会有锋利V。
所以merge命令的核心:
在命令中指定的标签将会被新的覆盖,任何未在命令中提到的标签均会保留 复合标签将会合并到一处,例如{a:{b:x}}merge{a:{c:y}}后将得到{a:{b:x,c:y}} 然而,列表或数列不会合并,如{a:[1,2,3,4]}merge{a:[6,6,7]}后得到{a:[6,6,7]}
用两个字概括上面的句子:merge的行为模式是合并。就像在电脑里你将一个文件夹复制到另一个同名的文件夹处,并在电脑提示“有相同名称的文件”时选择合并一样。
在这个比喻中,文件夹就是复合标签,子标签是其内部的文件——注意列表或数列在这个比喻中不是文件夹(所以会被覆盖)。
这样,你应该对merge这个子命令有一点了解了吧!我在下面列出了三条命令,用于检测你自己是否真的看懂了:
1.
- /data merge entity @e[type=skeleton,limit=1] {AbsorptionAmount:20f}
复制代码 "AbsorptionAmount"为伤害吸收值(也就是金苹果的那个金色的心)本命令将一只骷髅的伤害吸收值更改为20点。
2.让我们看一下merge的合并特性。
假设我们有一个实体,带有标签“ing”,并假设世界上只有一个带有该标签的实体,以让选择器@e[tag=ing,limit=1]可以选中该实体。
假设本实体拥有下列NBT:
{A:{B:"原标签", C:"原标签"}, D:"原标签", E:"原标签"}
执行命令:
/data merge entity @e[tag=ing,limit=1] {A:{B:"新标签",F:"新标签"},E:"新标签"}
结果为:
{A:{B:"新标签", C:"原标签", F:"新标签"}, D:"原标签", E:"新标签"}
上方,蓝色代表未被更改的标签。红色代表新出现的标签,紫色代表原来有但是被命令更改(覆盖)的标签。绿色代表被合并的复合标签。
3. - /data merge block 237 5 317 {Items:[{Slot:7b,id:'glass',Count:34b}]}
复制代码 这个位于坐标(237, 5, 317)的方块是一只箱子。NBT Items为箱子的物品,这是一个列表,其中的每一项代表存储于箱子中的一个物品。
Slot为箱子的栏位(也就是哪一格),id是存储的物品是什么,Count则是物品的数量。
这个标签我们上一帖讲过,不做赘述。
本命令为将该坐标(237, 5, 317)的箱子中的物品改为在第8格(栏位7)放置34个玻璃(glass)。
注意该箱子原有的物品将会被覆盖,因为上面说了,列表不算文件夹。
好,相信这样应该差不多了。merge这个子命令就先讲到这里。
3. 读取子命令get
导图 /data get >>>>>>>>我们在这里 modify merge remove
entity <选择器> 如/data get entity @s …… block <x> <y> <z> 如/data modify block 13 4 56 …… storage foo:bar 如/data remove minecraft:test ……
读取子命令(get)的作用是获取特定NBT的值。
格式:
- /data get <目标> [<NBT路径>] [<倍率>]
复制代码
先把后面的可选元素[<NBT路径>]和[<倍率>]到一边,直接打上前面的东西:
比如:
这样。
本命令的含义是,读取属于命令执行者(本例中就是我这个玩家自己)的NBT。
上面说过玩家的NBT不让修改,但是是可以读取的。
那么,当你执行这个这条指令后,游戏就会在聊天框中输出你这个玩家的所有NBT:
看看,有多少东西。
这里详细记录了你的背包,你的装备,你的生命值,你的状态,你的朝向,你有没有打开GUI【注2】,你在游戏里的一切数据……
说实话第一次跑一遍这条指令后,我有点理解为什么mojang不让我们用/data更改玩家的NBT了。
好,你可以尝试获取很多其他实体或方块实体的NBT,很有趣。
你应该注意到了,当我们直接使用/data get <目标> 而不添加任何其他元素时,游戏会返回所有它的所有NBT,或者说,根NBT(也就是NBT最外面的大括号里面的内容——包括它自己——也就是所有内容)
那么,我们该如何只让游戏返回特定的NBT呢?
比如,如何返回我这个玩家的生命值?
这个很简单,我们只要把生命值的键名“Health”打在后面就好了:
- /data get entity @p Health
复制代码 得到最近的玩家的生命值
游戏会返回:
Dahesor(我)拥有以下实体数据:20.0f
注意游戏并没有输出“{Health:20f}”,而是直接输出了我们指定的标签Health的值:“20.0f”
同理,我们再看向掉落物的例子。
你可能知道如果一个掉落的物品长时间(5分钟)无人捡起就会消失。记录该物品何时消失的是一个叫做“Age”的NBT标签。
那么,该如何通过/data get得到这个数值?
举一反三:
- /data get entity @e[limit=1,sort=nearest,type=item] Age
复制代码 得到最近的掉落物的年龄(Age)值
怎么样?
现在应该差不多明白了吧……
但是get这个子命令有点神奇的地方就是,它可以在后面再加上一个数字,作为输出结果的倍率。
简单地举个例子:
- /data get entity @e[limit=1,sort=nearest,type=item] Age
复制代码 这是上面的命令,我们假设当前Age是18。
正常执行上面的命令应该会得到18。
但是:
- /data get entity @e[limit=1,sort=nearest,type=item] Age 2
复制代码 这会给你36(18*2)
- /data get entity @e[limit=1,sort=nearest,type=item] Age 4
复制代码 这个会返回72(18*4)
- /data get entity @e[limit=1,sort=nearest,type=item] Age 0.5
复制代码 这会返回9(18*0.5)
你可能会疑惑,get不就是用来告诉自己特定的NBT标签是什么的么?你加个倍率算什么鬼?骗自己?
现在看来没用,但是以后说到/execute store时,这个东西就有用了……我们以后再说。
这里注意:
倍率只对数值有效,对字符串无效。
还有注意,倍率后数值会变为整数,向下取整
所以像上面的Age是20:
- /data get entity @e[limit=1,sort=nearest,type=item] Age 0.3
复制代码 这条命令会得到5而非5.4 floor (18*0.3)=5
这就是倍率。
接下来,让我们看向下面的例子:
我们有一个掉落物,这个物品是一把钻石剑。
它被附上了数个附魔。
现在要求:
使用/data get得到该掉落物的物品的第二个附魔的等级。
怎么办?
这个标签在哪里呢?
附魔的等级NBT(lvl)这个标签在这里:
{Item:{tag:{Enchantments: [{id:"sharpness",lvl:3},{id:"knockback",lvl:6}] }}}
上面标红加粗的就是我们的目标:Item(物品)标签中tag(物品标签)标签中Enchantments(附魔)列表中的第二项的lvl(等级)的值。
那,该如何得到它呢。
嗯嗯,NBT下的第一层标签,比如像上面的例子“Health”和“Age”,我们直接放上去就好了,但是……该如何表示复合标签的标签呢?又该如何表示“列表内的第二项”呢?
这就要用到下面的东西:NBT路径。
4. NBT路径
导图
/data
什么是NBT路径呢?
简单来讲,就是一串描述了特定NBT位置的东西。
我们可以简单地使用电脑路径做类比:
假设我的电脑上有一个文件夹叫做mc。在这个文件夹中有一个文件夹child,其中有一个文件,叫做target。
那么,该文件的路径是什么呢?
呐呐,电脑用“\”来代表“进入次级目录【注3】”,所以,上面文件的位置是:
mc\child\target
mc是最上层的文件夹。
每一个“\”都代表了“进入下一级的文件”。
所以上面的这个路径描述了“mc文件夹里叫child文件夹里叫target的文件。”
NBT的路径是一样的东西,不过不是使用斜线“\”,而是使用点“.”。
所以:
Circle.AAA.Item
本路径所代表的就是名为Cirlce复合标签内的AAA复合标签内的Item的值。
也就是下面下面标红的NBT:
{Circle:{BBB: 3s, AAA:{Item:1b, Count:4b, id:"apple"}}, babab: "x", lala: "y"}
红色的是上述路径的目标,蓝色的是我们经过的"文件夹"。
所以若对上述标签使用get:
若只填入Circle:
- /data get entity @e[limit=1] Circle
复制代码 得到:
{BBB: 3s, AAA:{Item:1b, Count:4b, id:"apple"}}
向下一级:
- /data get entity @e[limit=1] Circle.AAA
复制代码 得到:
{Item:1b, Count:4b, id:"apple"}
再向下一级:
- /data get entity @e[limit=1] Circle.AAA.Item
复制代码 得到:
1b
同理,我们把Circle.AAA.Item换成Circle.AAA.id:
- /data get entity @e[limit=1] Circle.AAA.id
复制代码 得到:
"apple"
可以了。
这就是复合标签的路径格式。
但是上面的这些路径还不足以让我们解决之前提到的获取附魔等级的问题,因为该NBT是一个列表:
Enchantments: [{id:"sharpness",lvl:3},{id:"knockback",lvl:6}]
在列表中,你可以发现名为lvl的元素不止一个,但我们要得是后面的那个lvl。
对于上面的Enchantments NBT标签,如果你的路径只是写Enchantments.lvl,谁知道那个lvl是指前面的lvl还是后面的lvl?
所以对于列表或数列,我们还要在标签后,使用中括号内部的内容来表示该列表特定的元素。
什么意思呢?
在上面的例子中的“Enchantments”不是很适合举例,所以我们换一个:
假设实体@e[tag=set, limit=1]有以下标签:
List: ["adm" , "adj" , "exe" , "com"]
那么,List[0]
就代表了名为List列表中的第一个元素"adm"。
注意我们是从零开始数的。
同样:
List[1] 代表List的第2个元素"adj"。
List[2] 代表其中的第三个元素”exe“。
以此类推。
我们还可以倒着数:
List[-1] 代表List列表中的倒数第一个元素"com"。
那么,List[-2] 就是列表中的倒数第二个元素"exe"。
同样,以此类推。
所以:
- /data get entity @e[tag=set, limit=1] List[-3]
复制代码 得到:"adj"
要注意,你是可以不添加中括号的:
- /data get entity @e[tag=set, limit=1] List
复制代码 返回:["adm" , "adj" , "exe" , "com"]
这样直接写入标签名,将会返回整个列表。
你也可以防止空的中括号,这会返回所有值:
List[]
返回:"adm" , "adj" , "exe" , "com"
以上就是列表的格式。
那么现在看回附魔的例子:
该如何从下列NBT中提取到附魔Enchantments列表第二项的lvl呢?
{Item:{tag:{Enchantments: [{id:"sharpness",lvl:3},{id:"knockback",lvl:6}] }}}
综合以上内容我们可以得到后面标红加粗的lvl的路径:
Item.tag.Enchantments[1].lvl
上面的路径代表了,标签Item内的标签tag内的列表Enchantments第二项内的标签lvl。
所以:
- /data get entity @e[limit=1,sort=nearest,type=item] Item.tag.Enchantments[1].lvl
复制代码 返回:6
也就是我们要的值。
4.1 筛选
我们之前的问题已经解决了。
但是又冒出来一个新的问题。
我们上一个例子要得到的是第二个附魔的等级。
根据我们的NBT标签,第二个附魔是击退(knockback)。
但实际上,附魔的顺序是不固定的,完全取决于你附上去的顺序。没人规定击退附魔就永远在第二个。
那么,该如何得到击退附魔的等级,无论该附魔在Enchantments列表中的排序?
这里要用到筛选。
我们是这样选择列表内的元素的:
List[4]
通过在中括号内填入数字,我们选择了位于特定位置的元素。
然而在面对由复合标签组成的列表时,我们可以将其中的数字更改为一个条件,来只输出特定的复合标签。
- 假设实体@e[tag=ex, limit=1]拥有以下标签:
sample: [{apple:1b, fur: 56},{apple:2b, fur: 37},{apple:1b,limp:20},{apple:3b, punch:17}]
我们可以将中括号其中的数字替换为一个复合标签,以选择拥有这些标签的元素:
sample[{apple:2b}] 中填入了”apple:2b“,可以匹配到上面列表的第二项”{apple:2b, fur: 37}“,因为只有该复合标签拥有键值对“apple:2b”。
所以使用get获取该路径将输出 {apple:2b, fur: 37}
同理:
路径 | 将选中 | 解释 | sample[{apple:3b}] | {apple:3b, punch:17} | 仅有该复合标签拥有名为"apple:3b"的标签 | sample[{apple:3b}].apple | 3b | 这选择了该标签下的apple的值 | sample[{apple:4b}] | 无 | 无复合标签拥有名为"apple:4b"的标签 | sample[{apple:1b}] | {apple:1b, fur: 56},{apple:1b,limp:20} | 这两者均拥有该标签。 | sample[{apple:1b, fur:56}] | {apple:1b, fur: 56} | 设置两个条件,选择第一项。 | sample[{}] | 所有4个 | 不设置条件将选中所有复合标签 |
要注意的是,get子命令一次只能获取一个值或复合标签。
所以上表可以选择两个或以上复合标签的路径无法应用于get。
也就是说,路径sample[{apple:1b}] 与 sample[{}] 无法应用于get,因为它们选择了不止一个标签。(但是可以应用于别的,后面会说。)
那么综合以上,看回附魔的例子,我们可以得到:
Item.tag.Enchantments[{id:"knockback"}].lvl
本路径选择了物品击退(knockback)附魔的等级。
还有一件事是,这种筛选同样是可以应用于复合标签的。
比如,若有标签为:{Test:{amp:1b, rep:2b}}
使用路径Test{amp:1b}
将会返回{amp:1b, rep:2b}你可以发现并不是只有amp:1b被返回了……而是返回整个复合条件的元素……
所以复合标签的筛选与其说是筛选还不如说是条件判断……
而若使用路径Test{amp:2b},因为没有匹配的元素,所以什么都没有。
最后,我们可以用单独的空的大括号{}来指定根标签,也就是整个所有NBT。
以下两条命令等价:
注意因为整个NBT本身就是1个复合标签,所以我们也可以对其使用筛选,如:
- /data get entity @s {Health:20f}
复制代码
我是有点无法理解复合标签筛选有什么实际的作用啦……感觉没有什么不可替代的功能……
4.2 小结
NBT路径这一块讲的可能有点乱。所以你现在脑袋里一团浆糊那是我的错。
为了让你的大脑清明一些,我去请一个朋友为各位写下了以下总结:
NBT路径
1. 使用“{}”指代根标签,使用键名指代其值。
2. 使用”.“代表进入下一层目录。
3. 列表或数列使用中括号内数字指定位于特定位置的元素。正数从零正数,tag[n]代表正数第n+1个目标。负数从-1倒数,tag[-n]代表倒数第n个目标。
不指定索引将选择列表或数列内所有元素。
- 如在数列UUID:[I; 1,2,3,4]中,UUID[0]指代1,UUID[-2]指代3,UUID[]指代1,2,3,4。
4. 使用大括号{}设置筛选条件。大括号内包含的标签必须出现在目标标签中以被选中。筛选可被应用于复合标签,列表或数列。
- 如在标签 {ING:{bar:1, foo:2}}中,路径ING{bar:1}指代{bar:1, foo:2}。
- 如在列表{List:[{foo:1, bar:2},{foo:2, bar:1},{foo:3,bar:3}]}中,路径List[{foo:1}]指代{foo:1, bar:2}。
5. 筛选中,空{}代表不设置筛选条件。特别地,对于列表空{}使用将只选择复合标签元素。
好就这样。
上面就是NBT路径与get的内容。
需要注意的是,不只是get在使用NBT路径。事实上,除了merge以外的所有子命令都适用NBT路径,你将在接下来看到。
但在我们看向下一个子命令之前,前来讲一下来源里的那个storage是什么东西吧。
5. 存储NBTStorage
导图 /data get modify merge remove
entity <选择器> 如/data get entity @s …… block <x> <y> <z> 如/data modify block 13 4 56 …… storage foo:bar 如/data remove minecraft:test …… >>>>>>>>我们在这里
如果你突然忘了storage是什么,从上面的导图就能看到,storage是来源的一种,与entity和block类似。
存储NBT(storage NBT),简单来讲就是一个用于存储NBT的虚拟对象。
NBT都存在哪里?实体身上,或者一些方块上。如果你想要保存特定的NBT,这些都不是什么好地方。
那么,当你想要保存特定的NBT的时候,我们可以创建一个虚拟的容器来存储它们。
这就是存储NBT。
——就是一个专门给你存储用的,假的NBT容器啦。
就像entity或者是block一样,我们可以用merge来创建一个:
- /data merge storage minecraft:test {Health:20f}
复制代码 上面的这个命令会在一个叫做”minecraft:test“的存储NBT中添加NBT{Health:20f}(生命值:20)。
就这样。
现在,如果你简单用get来读取这个存储NBT:
- /data get storage minecraft:test
复制代码 游戏就会返回:
minecraft:test有以下内容:{Health:20f}
嗯就这样。
你会发现,我建立的这个存储NBT叫做“minecraft:test”。这种命名方式是不是有点熟悉?
对,这就是用在了游戏物品或者实体ID上的“命名空间制”。
这种命名方式我们在系列第10帖讲过,如果你看过会很容易理解:
minecraft:test前面的minecraft是其命名空间(NameSpace),后面的test是ID。在不填入命名空间的情况下,它默认为minecraft。
就像文件夹一样,minecraft是个文件夹,test是其中的文件,minecraft:test就是指minecraft文件夹下的test文件。而如果你不指定文件夹只指定“test”,游戏就会默认其命名空间为“minecraft:"。
所以说以下两条命令是等价的:
- /data merge storage minecraft:test {Health:20f}
复制代码- /data merge storage test {Health:20f}
复制代码
当然,你是可以不用minecraft来当你的命名空间,你可以用别的比如“uuhc:item”之类的,但是在这种情况下,你不可以省略命名空间。因为省略了直接输入item,游戏将会认为是minecraft:item而不是uuhc:item。
……靠……从这个角度太难讲了,如果你没有看懂就点一下上面的链接去第10帖看一下吧。【注1】如果你在制作一张地图,或者是任何不可能与其他人的StorageNBT有任何冲突的作品时,你完全可以无视命名空间,只是用不重复ID把命名空间默认为minecraft即可,这样方便得多。(命名空间见鬼去吧)
但是如果你在做数据包,还是加上的好。
好,把命名空间的事情抛到一边——
让我们举上几个例子:
- /data merge storage gopher {test:[{la: 2}, {fr:4}], folk: 3s}
复制代码
我们可以向其中添加任何标签,即使是游戏不使用的。
- /data get storage gopher test[1].fr
复制代码 获取minecraft:gopher test列表第二项的fr标签的值
除了是“虚拟的”以外,storage与entity和block作用的方式一模一样
你可能有个疑问。上面的例子中我是用merge来修改存储NBT的。那该如何把,比如说,把一个实体NBT复制保存到存储NBT呢?
嘛嘛,这就是接下来的内容。
6. 高级修改子命令modify
导图 /data get modify >>>>>>>>我们在这里 merge remove
entity <选择器> 如/data get entity @s …… block <x> <y> <z> 如/data modify block 13 4 56 …… storage foo:bar 如/data remove minecraft:test ……
命令/data的子命令modify可以对特定NBT路径下的标签执行复杂的修改,包括覆盖,合并,插入,复制等等。
格式:
- /data modify <目标> <NBT路径> (set|merge|append|insert <索引>|prepend) (value|from) ......更多
复制代码
看向上方的格式。
在填入子命令modify,需要接上上面的说过的目标(entity|block|storage)与NBT路径。这个路径所指定的目标就是我们要修改的目标。
后面的5个选项“(set|merge|append|insert <索引>|prepend)”则是要如何修改被选中的标签。其中,“append”,“insert”,和“prepend”是专门用来修改列表或数列的。
在指定修改方式后就要指定修改值的来源。可以使用"value",即指定一个数值,抑或是“from”,从某处复制一个数值。
如果上面的描述你没看明白没关系,下面的会详细举例。
让我们先看想可以针对任何标签的“set”与“merge”。
6.1 set与merge
set(设置),与merge(合并),是/data子命令modify的两种修改方式。其中,merge只能只能针对复合标签,但set可以设定单个的值……两者的主要区别在于,merge为合并,但set为覆盖。
说起merge你绝对不陌生。
这个merge就是在最开始讲的子命令merge的进阶版:
如:
- /data modify entity @e[limit=1,type=item] Item merge value {id:"dirt"}
复制代码 看向上面的命令。该命令的含义为,将实体@e[limit=1, type=item]的NBT"Item"的值与value后面的NBT“{id:"dirt"}”合并。
上面的“value”代表了我们要设置一个自定的值,而{id:"dirt"}则是要被合并到该实体Item标签的值。
本命令与下面的命令效果等价:
- /data merge entity @e[limit=1,type=item] {Item:{id:"dirt"}}
复制代码 简单地对比两者,你就很容易理解。这只是相当于将{Item:{id:"dirt"}}转换为NBT路径的表达方式“Item”,再把要更改的值“{id:"dirt"}”挪到后面,中间夹上一个“merge value”而已。
但要注意merge只能针对复合标签,后面被设置的内容也必须是复合标签。
如,下面这个例子是不合法的:
- /data modify entity @e[limit=1] Health merge value 10f
复制代码
因为Health不是复合标签,后面的10f也不是复合标签。
上面的命令应该这样写:
- /data modify entity @e[limit=1] {} merge value {Health:10f}
复制代码
这里我们选中了根标签,它是复合标签,此时再将它与{Health:10f}合并就能得到我们想要修改生命值的效果。
但是,我们完全不用这么麻烦。
除了merge外,另一个修改方式“set”可以直接更改单个数值,比如生命值(Health):
上面的例子命令可以这样转换为set,两者在大部分情况下都是等价的:
- /data modify entity @e[limit=1] Health set value 10f
复制代码 将实体@e[limit=1]的生命值(Health)设置为10f。
当然,set也是可以应用于复合标签的,复合标签不是merge的专场:
- /data modify storage test FRIST.sec set value {third:{lap:[1f, 4f, 6f]}}
复制代码 将存储minecraft:test的FRIST.sec标签的的值为{third:{lap:[1f, 4f, 6f]}}
然而,尽管两者均可修改复合标签,却又很大的不同:
merge是“合并”,但set是“覆盖”。对于目标路径下未被命令指定的标签,merge会保留,但set会覆盖。
看向下面的例子:
假设实体@e[limit=1]拥有以下标签:
{Test:{stone:{type:1, size:3}, sand:1}}
若执行命令:
- /data modify entity @e[limit=1] Test.stone merge value {shape:6}
复制代码 在Test.stone标签下合并标签{shape:6}
那么结果为:
{Test:{stone:{type:1, size:3, shape:6}, sand:1}}
- 注意未被命令指定的标签,如type:1与size:3,未被命令改变。
- 注意命令不影响位于路径Test.stone之外的标签,如sand:1不受影响。
但若执行命令:
- /data modify entity @e[limit=1] Test.stone set value {shape:6}
复制代码 将Test.stone标签设置为{shape:6}
那么结果为:
{Test:{stone:{shape:6}, sand:1}}
- 注意路径下的标签完全被新标签覆盖。原有的,如type:1与size:3,消失了。
- 注意命令不影响位于路径Test.stone之外的标签,如sand:1不受影响。
这就是set与merge的最大区别——一个是覆盖,一个是合并。
6.2 复制
在上面的例子中,我们一直用的都是指定一个NBT“value”,但是除了对目标覆盖或合并一个由我们确定好的内容,还可以从一个位置复制一个NBT过来:
格式:
- /data modify <目标> <NBT路径> (set|merge) from <来源> <来源NBT路径>
复制代码 注意原来的value变为了from。
在from后面的“<来源>”与“<来源NBT路径>”与前面的<目标>与<NBT路径>一样,指定了一个实体/方块/或存储中的特定NBT。
这个NBT就会被覆盖或合并到目标NBT上。
比如:
- /data modify storage uuhc:items Item set from entity @p Inventory
复制代码 将最近玩家的物品栏(Inventory)复制到名为“uuhc:items”存储NBT的标签“Item”下。
上面说过玩家的NBT无法修改,但是是可以复制的。
这里要注意,命令不会改变来源NBT。(这是复制而不是剪切)
- /data modify block 14 3 67 Items set from storage uuhc:items Item
复制代码 再将该storage刚刚保存的一背包物品复制到位于坐标(14, 3, 67)的箱子内。(假设该方块是箱子或其他容器)
(物品栏有36格,但是箱子只有27格,所以有可能有最多9个东西溢出消失)
当然,你也可以直接从玩家的背包复制到箱子,而不使用1个存储NBT……但是这个中间步骤在某些时候是需要的(比如这个箱子一开始并不存在)
再比如:
- /summon item ~ ~ ~ {Item:{id:'stone', Count: 1b}}
复制代码 在原地生成一个掉落物,物品为1个石头
- /data modify entity @e[limit=1,type=item,sort=nearest] Item set from entity @p EnderItems[0].Item
复制代码 将最近玩家的末影箱中的第一个物品(EnderItems[0])复制到这个掉落物上
所以除非这位玩家末影箱第一格放的也是1块普通的石头,不然该掉落物已经变得和玩家的末影箱第一格物品一致。
上面的两条命令相当于将玩家末影箱中的一个物品在地上复制了一份。
当然,from也可以应用于merge:
- /data modify storage 1 {} merge from storage 2 {}
复制代码 将存储minecraft:1与存储minecraft:2合并
当然,from也适用于下面讲的其他3种方法,后面会说。
好,例子举到这里。
这里再次提醒,无论merge或set都会覆盖列表或数列,原因在一开始的子命令merge处讲过。
——诶诶诶,你有没有发现,目前为止还没有任何一种方法可以在列表或数列中插入元素。
无论megre还是set都只能复制。(要么全部要么全不)
这就相当于无法在箱子里插入一个新物品了?(箱子用于存储物品的NBT是一个列表。)
不要慌。modify的修改方式除了set与merge还有领完3个——它们都是用来修改列表的。
6.3 修改列表append/insert/prepend
子命令modify下的修改方式“prepend”,“append”,与“insert”均为专门修改列表或数列的修改模式。这三个方法均可以在列表或数列中插入一个元素。
其中,"prepend"是在最前面插入,“append”则是在最后面插入,"insert"则可以在任意位置插入。
为了让各位好理解,我在全网找了个遍,最后决定举这样一个可视化的例子:
有一个盔甲架,它当前是空的,身上什么盔甲都没装。
盔甲架用于存储其盔甲的NBT叫做“ArmorItems”。就是该NBT描述了盔甲架上装的盔甲。
如果现在使用/data get得到该盔甲架的ArmorItems,你将得到:
ArmorItems:[{},{},{},{}]
一个列表,内部有4个复合标签。从前之后分别代表了该盔甲架的足部装备,腿部装备,胸甲,与头盔。
由于这个盔甲架上什么也没有,所以这四个大括号都是空的。
但是,现在我们运行命令:
- /data modify entity @e[type=armor_stand,limit=1] ArmorItems prepend value {id:'iron_boots', Count:1b}
复制代码
在列表ArmorItems中的最前面插入NBT“{id:'iron_boots', Count:1b}”,即“1个铁质靴子”
那么,现在该盔甲架的NBT是什么样了?
ArmorItems:[{id:'iron_boots', Count:1b},{},{},{}]
我们在该列表的最前面插入了元素“{id:'iron_boots', Count:1b}”即一只铁靴子。这导致原来的第一个空大括号{}挪到了第二个,原来的第二个空大括号{}挪到了第三个……以此类推……原来的第四个空大括号{}现在挪到了第五个……然而盔甲位只有4个,所以原来的第四个{}现在的第五个{}就这样被删除了【注4】,以保证该列表内只有4个元素。
该列表的第一个位置被铁靴子占上,而该列表的第一位又代表了盔甲架的足部装备……所以现在再看向该盔甲架你就会发现它腿上多了个靴子:
好,接下来,让我们执行以下命令:
- /data modify entity @e[type=armor_stand,limit=1] ArmorItems prepend value {}
复制代码
我们再次在盔甲架的ArmorItems最前面插入了一个空标签"{}",这把ArmorItems变成了这样:
ArmorItems:[{},{id:'iron_boots', Count:1b},{},{}]
原来的铁靴子现在被新的空标签挤到了第二个。原来的第四个空大括号{}再次被挤到第5个而被删除。
但现在你再看向盔甲架就会发现其原来穿着的铁靴子现在失踪了。
因为该标签跑到了第二位,也就是护腿的位置……靴子当然无法在护腿上显示。
但若你对该盔甲架看起来空空如也的腿部按一下右键,就可以把这只铁靴子拿下来:
(从腿上摘靴子)
好好好,我们把靴子放回去,不拿下来,再运行一遍该命令:
- /data modify entity @e[type=armor_stand,limit=1] ArmorItems prepend value {}
复制代码 ArmorItems变成了这样:
ArmorItems:[{},{},{id:'iron_boots', Count:1b},{}]
现在靴子跑到胸甲栏了。
再运行一遍,就变成这样:
ArmorItems:[{},{},{},{id:'iron_boots', Count:1b}]
现在靴子跑到头上了。
这下能看见了:
呵呵。
再运行一遍上面的命令:
ArmorItems:[{},{},{},{}]
原来的铁靴子被新的{}挤到了不存在的第五个,消失了。
这就是在最前方插入“prepend”。
模式Append/Insert
而与prepend相对的,"append"为在列表或数列最后面插入。
当然,你不会想将其使用与上面的盔甲架的例子……因为该标签一插入就会处于第五位(插入在最后面嘛),进而被删除。
但不是所有的列表或数列都会删除溢出的标签……
比如:
- /data modify block 1 1 1 Items append value {id:'grass_block',Count:10b,Slot:0b}
复制代码 比如这只位于(1, 1, 1)的箱子就不会。
上面的命令在这箱子的第一格加入了10个草方块(grass_block)。
你可以发现,物品在什么地方跟其在列表中的位置一点关系都没有……所以虽然上面的标签被插进了列表最后,但是物品却放在箱子的第一格。
最后是“insert”。它可以在任意位置插入一个元素。
这个“位置”由一个紧跟其后的数字来指定:
- /data modify storage test List insert 2 {O:1b}
复制代码 在存储“minecraft:test”的列表“List”的第三个位置插入元素“{O:1b}”
注意数字是从0开始算的,所以上面的“2”指的是第三个位置。
和prepend一样,原来的第3个元素被挤到了第四个……以此类推。
而位于第三个之前的则不受影响。
和NBT路径一样,我们也可以使用负数来在倒数的位置插入:
- /data modify storage test List insert -2 {A:1b}
复制代码 在存储“minecraft:test”的列表“List”的倒数第二个位置插入元素“{A:1b}”
注意这是从-1开始算的,不是-0(-0是什么鬼)。
最后,你可能注意到了,下两条命令等价:
- /data modify storage test List prepend {O:1b}
复制代码- /data modify storage test List insert 0 {O:1b}
复制代码
好,以上就是modify的全部内容。
7. 删除子命令remove
导图 /data get modify merge remove >>>>>>>>我们在这里
entity <选择器> 如/data get entity @s …… block <x> <y> <z> 如/data modify block 13 4 56 …… storage foo:bar 如/data remove minecraft:test ……
这就是最后的内容了。
子命令remove可以删除特定的NBT。
其格式为:
- /data remove <目标> <NBT路径>
复制代码
这是十分简单的。
比如,
- /data remove block 12 2 13 Items
复制代码 假设位于坐标(12, 2, 13)的方块是个箱子,本命令会将这个箱子的标签Items“移除”。结果是该箱子内的物品都消失了。
你可以尝试在remove后接入任何路径,但是并不是所有的NBT都可以被删除。
像是生命值。如果你执行命令/data remove @e[limit=1] Health
这并不会将该实体的NBT“生命值”移除。
注意,以下标签是不能被删除的:
- 根标签。你不能把根,也就是说整个NBT删除。
- 第一层的非复合或列表标签。位于第一级,且并不是复合或列表的标签无法被删除。比如上面的生命值Health。
- 大部分第一层的标签,即使是复合或列表标签也不能被删除,但是有例外,像上面的示例,箱子的Items标签可以被remove移除。(实际上还在,只不过内容被清空了。)
- 一些特定的NBT无法被删除
在讲过modify后remove也就没那么复杂了,所以就这样。
总结
以上就是/data命令的所有内容。命令data无法修改玩家的NBT还真是残念,所以接下来的两帖都会讲一些可以间接修改NBT的命令……比如/attribute……
我还以为我自己会写的赖赖巴巴的呢……结果相当地精神。
——很好,现在是早上6点。
我知道我知道,我鸽了两周半……
但是/data不是什么简单的东西啊……而且昨天我家断网,前天MCBBS关闭发帖,我有什么办法。
所以,各位就不会给点人气么?反正给人气又不用花你自己的人气,给金粒却要花你自己的。
还有,点赞收藏请自觉……我敢打赌你的点赞基本上每天都是浪费的。
那好就这些。
本教程对我来说最痛苦的事是编键值对
附录,注释,与外部链接
注释
【1】这个是路径的,和数据包资源包的那种一样,你可以有类似uuu:1/dd/ff这样的……但是我没说。
【2】标签isGuiOpen与类似的都好像不管用。
【3】至少Windows和Mac用的是"\"……linux则很不合群地用了斜线“/”
【4】实体无法保存无法被游戏解析的NBT。
#更新日志
1.16.5/a 页面发布
1.16.5/b 十分难受地弄反了几个斜线,已修复
Java 1.18/a 1.18版本升级完成
上一篇:【命令】命令教程“真”从零开始 ( X ) 我就不信不能用大白话讲清楚NBT
系列《命令教程“真”从零开始》
前往来自 麻瓜草稿簿 的 系列教程索引下一篇:【命令】 命令教程“真”从零开始(十三)给予与替换物品,命令/replaceitem
恶搞:
/data命令格式:
- /data (get(entity <目标>|block <x> <y> <z>|storage <路径>) <NBT路径> [<倍率>]|merge(entity <目标>|block <x> <y> <z>|storage <路径>) <NBT>|modify(entity <目标>|block <x> <y> <z>|storage <路径>) <NBT路径> (set|merge|(prepend|append|insert <索引>) (value <NBT>|from (entity <目标>|block <x> <y> <z>|storage <路径>) <NBT路径>))|remove(entity <目标>|block <x> <y> <z>|storage <路径>) <NBT路径>)
复制代码 |
|