第二十六章 更加精确的目标选择器 下
——游戏模式——
用游戏模式筛选玩家的参数,在Java1.13及以上版本中是gamemode,在基岩版以及低于Java1.13的版本均为m。
那么它们是怎么用的呢?
众所周知,基岩版服务器并没有像Java版服务器一样具有出生点保护,所以在基岩版,要让服务器主城不被恶意破坏,最基本的操作就是使用『范围冒险模式』。比如这个中国版租贷服最初就用了如下指令:
/gamemode a @a[x=726,y=89,z=-263,r=50]
重复执行这条指令,就可以让主城范围50格内的所有玩家都改为冒险模式。但这有个缺点:腐竹也被改为冒险模式了。为了解决这个问题,这个腐竹给该目标选择器添加了一个参数,变成:
/gamemode a @a[x=726,y=89,z=-263,r=50,m=s]
这条指令和上一条指令的唯一区别就在于,它不会将不是生存模式的玩家改为冒险模式,这样子腐竹开着创造就不会受到影响了。其中新添加的m参数,值是s,也就是survival(生存)的缩写。
m参数可以使用游戏模式的全称,缩写和数字ID。比如:
/tbr / @a[r=20,m=6]^^^-100(仅适用于基岩版)
这条指令的作用是:将以基准点(在这是指令执行位置)为中心,半径20格内的所有处于旁观模式的玩家,全都传送到执行者背后100米处(^^^-100为局部坐标,会在第五十九章讲到。『6』在这是基岩版1.19及以上版本为实验玩法的旁观者模式的数字ID。没错,基岩版也有旁观者模式了,只不过在测试而已)
而gamemode参数.......在Java1.13版本中,Mojang重写了大量游戏基础代码,导致Java1.12.2和1.13版本中,许多游戏内容差异极大。所以在之前的章节中,你会发现许多指令都在Java1.13更新中发生了大改,这种情况在以后的章节中也会持续存在。游戏模式也一样,在Java1.13版本更新后,游戏模式就仅支持全称,即:
survival(生存模式)、creative(创造模式)、adventure(冒险模式)和sbr /ectator(旁观模式)。
所以,虽然gamemode参数和m用法一样,但它仅支持上面四个值。
现在我们知道如何选择特定游戏模式的玩家。但如果我们要反过来,排除特定模式的玩家该怎么办?
这位腐竹造了一个房子。为了防止他的房子被破坏,就做了一个简易的安保措施:
/kill @a[r=15,m=!c]
这条指令的意思是:将范围15格内所有不处于创造模式的玩家全部杀死。
仔细观察这条指令,你发现了什么?
没错,m参数要反过来排除特定模式的玩家,只需要在值前面加一个英文半角感叹号。和m参数一样,gamemode参数也同理:
/kill @a[distance=..15,gamemode=!creative]
感叹号的这种用法,其实就是不等号在计算机语言中的常见形式(=!)。这种不等号反转选择的用法在接下来几个目标选择器参数中也会频繁出现。
——目标名称——
name参数可以用于选取指定名称的实体。
使用/give给予某位特定玩家物品时,一般不会用到目标选择器,而是直接指定该玩家的玩家名(不会吧,不会吧,不会真的有人会用玩家的UUID来使用/give指令吧)。
但如果要用目标选择器,还要锁定这名玩家该怎么办?
举个例子:
/give @a[name=JIE灬挥刀乱砍] Skyward_Blade
这样子就可以锁定这名玩家并给予物品了。
name参数不光可以用于玩家名上,还可以用于实体名上。
比如某位Java腐竹为了实现将宝箱随机放在世界各处,用了如下指令:
/summon minecraft:armor_stand ~~~{CustomName:“\“A\““}
\\召唤一个名为A的盔甲架\\
/sbr /readbr /layers ~~ 32 10000 false @e[tybr /e=minecraft:armor_stand,name=A]
\\将所有名为A的盔甲架随机传送到以该命令方块为中心的20001×20001范围内,且每个盔甲架间距不小于32格,不考虑盔甲架的队伍属性\\
/execute as @e[tybr /e=minecraft:armor_stand,name=A] at @s run ......(后面省略)
\\将执行者、执行位置和旋转角度都设定为名为A的盔甲架,并运行.....\\
可以发现,该腐竹为了防止执行指令时和其他实体发生冲突,特别使用了名叫A的盔甲架并用name参数锁定。同时,这位腐竹还用到了tybr /e参数。关于这个参数待会会讲到。
name参数也可以像m、gamemode参数一样,使用感叹号反转为排除指定名称的实体,比如:
/kill @e[name=!A]
这条指令的作用就是:杀死名字不是A的实体。
需要注意一点,如果名字中包含空格,需要用双引号括起。比如:
/kill @e[name=“genshin imbr /act“]
——垂直旋转角度——
——水平旋转角度——
还记得第九章的/tbr /吗?我们就在那第一次接触到了垂直旋转角度和水平旋转角度:
这两个由于是同类,本书就合起来讲了。
在Java1.13及以上版本中,垂直旋转角度参数是x_rotation,水平旋转角度参数是y_rotation。在Java1.13以下和基岩版中,两类角度分别是rx、rxm和ry、rym。
唉,你发现了没有?这和我们之前了解过的经验参数(l、lm和level)还有距离参数(r、rm和distance)差不多。那么格式是不是也是一样呢?
还真是一样的。既然格式一样,这里就不多说它的格式了。
rx、ry参数的作用是:选取垂直、水平旋转角度小于等于RX或RY的实体
rxm、rym参数的作用是:选取垂直、水平旋转角度大于等于RXM或RYM的实体
垂直旋转角度其范围是:90度(看地上)到-90度(看天空)。
水平旋转角度其范围是:-180度(北)到180度(还是北),或者说是上北-180°,下南0°。左西90°,右东-90°。
等等,我们在讲tbr /时,不是说水平旋转角度是:『以实体为中心,以正南(z轴正方向)为0°,顺时针下来,实体朝向和正南方向的夹角(也或者说实体在真南方位角体系中朝向的角度),就是该实体的水平旋转角度。』
那这怎么跑出来负数了?
其实在Minecraft中,水平旋转角度虽然可以像我们之前在第九章讲tbr /时那么用,但大多数时候,你都得这么用:
以正南(z轴正方向)为0°,顺时针旋转180°通过正西至正北,用正数,逆时针旋转180°通过正东至正北,用负数。比如-45°,就代表以正南为基准,逆时针旋转45°的方向;30°,就代表以正南为基准,顺时针旋转30°的方向。
也就是说,在Minecraft中,水平旋转角度的正确范围是-180°~180°,而不是0°~360°。至于为什么我要在第九章那么讲,只是怕一下子就把负数搬出来会吓你们一跳。
现在我们回到正题。
举个例子。某网易手机租贷服为了让玩家回城方便,搞了一个“回城雪球”,其指令如下:
A→B→C→
A:重复,无条件,始终活动
/execute @e[tybr /e=snowball]~~~ execute @br /[r=1.5,rx=90,rxm=60]~~~ execute @e[tybr /e=snowball,c=1]~~~ tag @s add back_home
\\选取雪球作为指令执行者,再以这个雪球为中心寻找半径1.5格内最近的低着头(头自水平线向下90°到向下60°)的玩家。如果找到,再以该玩家为中心寻找最靠近他的雪球,并给这个雪球赋予back_home标签。\\
B:连锁,有条件的,始终活动
/execute @e[tybr /e=snowball,tag=back_home]~~~ tbr / @br /[r=1.5] 323 65 72
\\选取具有back_home标签的雪球,并以它为中心将半径1.5格内最靠近它的玩家传送到(323,65,72)。\\
C:连锁,有条件的,始终活动
/kill @e[tybr /e=snowball,tag=back_home]
\\清除所有具有back_home标签的雪球\\
其中就有用到rx和rxm参数,用于筛选那些低着头扔雪球的玩家。
至于其中出现的tag参数,我们在上一章已经略过了。关于tag会在以后讲到计分板时提到。
ry和rym参数目前来说没有特别广的用途,只能举个没啥用的例子:
/kill @a[ry=180,rym=-180]
这条指令可以杀死所有面向正北的玩家(神奇的是这并不会框选住所有活着的玩家,@a[ry=180,rym=179]才会框选住几乎所有活着的玩家)
至于x_rotation和y_rotation参数,你应该知道怎么用了吧?
@a[x_rotation=35..]——所有头水平线朝下35°及以上的玩家
@a[x_rotation=..35]——所有头没有水平线朝下35°以上的玩家
@a[x_rotation=0..35]——所有头水平线朝下0到35°(含)的玩家
@a[x_rotation=35]——所有头水平线朝下35°的玩家
@a[y_rotation =60..]——所有朝向是在南偏西60°顺时针到正北这个范围内的玩家
@a[y_rotation =..60]——所有朝向是在南偏西60°逆时针到正北这个范围内的玩家
@a[y_rotation =60..120]——所有朝向是在南偏西60°顺时针到北偏西60°这个范围内的玩家
@a[y_rotation =60]——所有朝着南偏西60°的玩家
——实体类型——
实体类型是tybr /e,上面我们已经见过了。
tybr /e和name本质上是差不多的,但是它筛选不是通过名字,而是通过实体种类。
什么是实体种类?比如一个玩家叫Notch,另一个玩家叫Herobrine,虽然名字不同,但他们都是『玩家』种类的。又比如这里有一只马,那里有一只叫马的驴,虽然它们都叫马,但前者是马,后者是驴,并不是一个种类的。
tybr /e可以选定指定类型的实体,比如:
/kill @e[tybr /e=minecraft:villager]
就可以杀死全部已生成的村民。
tybr /e一般来说仅用于@e,因为只有@e是包含非玩家实体的。在Java1.13以下和基岩版中,你也可以用在@r中来随机选择特定类型的实体。
和name一样,tybr /e也支持感叹号反转:
/kill @e[tybr /e=!br /layer]
这条指令的作用是:杀死所有非玩家实体。
需要注意的是,name和tybr /e这两个参数在非感叹号反转的情况下都是不可重叠的,比如:
@e[tybr /e=cow,tybr /e=br /layer]
像上面那样是不可以的,Minecraft中可没有既是牛又是玩家的动物。如果真有,那么也应该合并成为一个新的实体,也许会叫作cow_br /layer(牛人)呢。
——实体家族——
我们知道通过tybr /e可以选取特定类型的实体。但如果我们要选取一堆不同类型的实体用于执行同一种指令呢?
记分板、NBT和标签可以很好的解决这个问题。但在了解这三个东西之前,我们暂且没有除多弄命令方块以外的更好的办法。
Mojang估计看我们这么可怜,于是在基岩版1.16.100中,加入了family参数。
family参数和tybr /e参数差不多,只不过它是通过实体家族筛选的。
什么是实体家族?
举个例子:
僵尸知道吧?僵尸一般来说有三类变种:
尸壳、僵尸村民、溺尸
虽然它们客观上并不属于同一种实体,但主观上我们仍然会将它们归类到一个大类:僵尸类。
用Mojang的说法,它们都是属于同一个族(family)的。
现在你应该知道实体家族到底是什么了吧?
举个例子:
/tbr / @e[family=creebr /er]@s
这条指令的作用是:将所有属于苦力怕家族的实体传送到自己身旁。
当然,你也可以使用感叹号将作用反转。需要注意,和tybr /e、name参数不同的是,family参数在非反转情况下也是可以叠加使用的,因为有些实体可能会属于多个族。
你可以在Minecraft Wiki上搜索『族』词条来查看原版所有可用的实体家族。
——物品——
我们知道,在Java版中,如果要筛选具有指定物品的实体,可以使用NBT或/clear。那在基岩版中该怎么办?使用/rebr /laceitem或/clear吗?
这的确是两种可行的方法,但Mojang还给了我们第三个方法:
hasitem目标选择器参数
这个参数可能会比较复杂,因此在了解这个参数之前,我们得先了解一下:背包中的物品
背包具有很多个物品栏,每个物品栏具有多个栏位,栏位储存着物品。因此,每个放在背包中的物品都具有栏位标签。物品有很多个种类,因此,物品还具有id标签。大部分类型的物品可以堆叠,因此,物品还具有数量标签。在Java1.13以下版本和基岩版中,同id的物品也可能不同,因此,物品在这些版本中还具有数据值标签。
hasitem参数可以用来选取具有指定物品的实体。更准确一点来说,hasitem参数可以通过检测已选实体的背包中指定条件下的物品,来筛选实体。
hasitem参数的值比较特别,它的值可以是单个条件,也可以是由多个条件组成的条件列表。让我们来看看hasitem参数的一个条件项目到底可以指定哪些东西。
一个条件项目可以检测单个类型的物品,具体来说可以指定以下的参数:
item——物品id
data——物品的数据值[可选]
quantity——限制所选范围内物品的总数量[可选]
location——需要检测的物品栏[可选]
slot——需要检测的槽位[可选,必须配合location参数使用]
比如:
{item=bed,data=1}
这个条件可以筛选背包内具有橙色床的实体。不难发现,单个条件可以具有多个不同的筛选参数,并且外面要使用花括号({})包裹起来。
需要注意的是,data参数目前有个BUG,就是不能适用于方块类物品。如果你对一个方块类物品使用了data参数,那么不管你怎么改data的值,游戏总会认为该参数的值为0。怎么判断一个物品是不是方块类的呢?看物品的图标。如果图标是直接给出了方块的3D图像(也就是渲染图),比如羊毛,那就是方块类物品。像是床这种图标是专门画的就不是方块类物品,所以能够正常使用data参数。
quantity参数得好好讲一讲,它并不是说检测单个栏位内物品的数量,而是检测整个范围内的指定类型物品数量总和。比如:
{item=dirt,quantity=100..}
这个条件可以选择到那些背包内泥土数量达到甚至超过100块的实体。也就是说,假设这里有一个张三,背包内有两组泥土,那么这个条件就可以选择到它。
不难发现,quantity的值简直是基岩版的一股清流,竟然向Java1.13及以上的版本学习,采用了Java版先进的『两点法』,不错不错,值得表扬。而且,这参数还在Java版的基础上改造了一下,变成了还可以使用不等号进行反选:
{item=dirt,quantity=!100..}
这将会选取拥有泥土数量<100的实体。
对了,如果你指定了这个参数为0,那就可以选择『没有指定物品的实体』:
{item=dirt,quantity=0}
这将会选取背包内没有泥土的实体。
location参数可以缩小检测的范围到指定的物品栏。具体可以使用哪些物品栏以及这里的物品栏是个啥东西.....这就需要你前往第三十八章了解/rebr /laceitem指令。
这里就先假装你已经搞懂了这些内容。举个例子:
{item=dirt,quantity=0,location=slot.enderchest}
这将会选取所有在其末影箱内没有泥土的实体。需要注意,对于玩家来说,默认是不会检测到末影箱的。也就是说,如果你在末影箱内放了一块泥土,那么{item=dirt,quantity=0}这个条件还是会选择到你,但{item=dirt,quantity=0,location=slot.enderchest}这个条件则不会。另外,即使是对于没有末影箱的非玩家实体,游戏仍然会假装其具有末影箱,然后又因为游戏假装出来的末影箱内没有泥土,导致上述条件也会选择到非玩家实体。
在使用location参数时,还可以更进一步使用slot参数来缩小检测范围到指定的栏位。比如:
{item=dirt,quantity=1..,location=slot.enderchest,slot=0}
这将会选取到所有在其末影箱左上角第一格内放有泥土的玩家。和quantity一样,这参数同样也支持升级后的『两点法』:
{item=dirt,quantity=1..,location=slot.enderchest,slot=1..}
\\选取到所有在其末影箱内除了左上角第一格外其他位置放有泥土的玩家\\
{item=dirt,quantity=1..,location=slot.enderchest,slot=!0}
\\同上\\
上面这些都是一个条件内可以弄的参数,hasitem参数的值也确实可以直接放入单个条件:
/testfor @a[hasitem={item=dirt,quantity=1..,location=slot.enderchest,slot=!0}]
但是如果要多个条件呢?这时候就需要用到列表:
@a[hasitem=[{item=dirt},{item=abr /br /le}]]
这将会选择到背包内同时具有泥土和苹果的玩家。
不难发现,在多个条件组成的列表中,每个条件也是使用逗号分开,列表最外侧被中括号([])包起来。
这就是hasitem参数的具体使用方法,其中部分内容超纲了一些,但总体上还是易于理解的。
——NBT——
——进度——
——谓词——
上面这三个均为Java版独有,且我们还未接触到,暂时先留个坑,以后再填。
附录:目标选择器发展历史
Java
1.4.2——加入目标选择器,最初只有@a、@r、@br /三个变量
1.8——加入了@e变量和dx、dy、dz参数
1.9——m参数现在接受游戏模式全称和缩写,在此版本之前只支持数字ID。并加入了tag参数。
1.11——移除了隐含目标选择器(如@a[26,65,-28],代表@a[x=26,y=65,z=-28]),并且错误的目标选择器不再略过,而是会报错。
1.12——加入了@s变量
1.13——加入了NBT和进度advancements参数,并对原本的参数进行大改
1.15——加入了谓词br /redicate
基岩版
1.16.100——加入了family参数
1.17.10——加入@initiator变量用于NPC
1.18.30——加入了hasitem参数
(注:网易版我的世界截止目前[2022.8.3]仅仅更新到1.18.10版本,因此没有hasitem参数)