本帖最后由 pca006132 于 2017-8-16 17:45 编辑

-------命令进阶-------
第五章 逻辑套路

本文首发于命令进阶教程全集
使用CC BY-SA-NC 4.0协议,转载请注明本页面链接


    命令强调逻辑,而部分逻辑在命令系统里已经有常用的做法,使用这些做法会比较方便、有效率、高可读性。

    本章将会介绍命令系统常见的逻辑。
    这里的章节会比较短,因为都是一些简单逻辑,如果需要更详细理解可以百度/Google或询问其他玩家。

    我们十分推荐各位亲自尝试其中的一些逻辑。

作为移植页面,格式可能会有偏差,请到教程全集中获得完整体验。
本页面可能随着版本推进而过期,内容以全集为准。



穷举
穷举(Enumerating)是一个命令中常见的做法。
就是把一切可能性列出。

比如说我们要把一个掉落物的数量(Count)写进其count分数里,我们可以怎么做呢?
由于没有一个方法可以检测掉落物的数量,也没法直接把NBT换为分数,我们只能穷举。

  1. scoreboard players set @e[type=item] count 1 {Item:{Count:1b}}
  2. scoreboard players set @e[type=item] count 2 {Item:{Count:2b}}
  3. # ...
  4. scoreboard players set @e[type=item] count 64 {Item:{Count:64b}}
复制代码



其实大部分命令都是穷举的,只是我们一般不把那些视为穷举而已,我们只是把这种特别多而且看上去特别蠢的列举说是穷举。


二分法
二分法是穷举的一个特殊情况,就是比较聪明的穷举。
我们没法直接获得某个数值,然而我们可以改变(不是直接设置)这个数值并且知道该数值所在的范围,这种情况就可以用二分法了。

使用二分法能把穷举量大幅减少,方法就是每次把数值范围减半进行处理,直到范围已经足够精确。
比如要知道一个随机数的数值,我们只知道它是否等于/大于/小于某个数,那么我们就可以用二分法,先猜50,如果大于的话范围就变为51-100,小于就变为0-49,如此类推。

NBT我们没法用二分法,因为我们没法改变数值。然而我们可以用二分法获取坐标。

获取marker的x坐标。例子:(为了方便,例子里只容许x坐标为0-8,假设y及z坐标为1)

  1. scoreboard players set @e[name=marker] x 0

  2. # 检查是否x >= 4,是的话就x分数+4然后tp x - 4
  3. scoreboard players add @e[name=marker,x=4,y=1,z=1,dx=4] x 4
  4. tp @e[name=marker,x=4,y=1,z=1,dx=4] ~-4 ~ ~

  5. # 检查是否x >= 2,是的话就x分数+2然后tp x - 2
  6. scoreboard players add @e[name=marker,x=2,y=1,z=1,dx=2] x 2
  7. tp @e[name=marker,x=2,y=1,z=1,dx=2] ~-2 ~ ~

  8. # 检查是否x >= 1,是的话就x分数+2然后tp x - 1
  9. scoreboard players add @e[name=marker,x=1,y=1,z=1,dx=1] x 1
  10. tp @e[name=marker,x=1,y=1,z=1,dx=1] ~-1 ~ ~

  11. # 最后的分数就是该marker的x坐标了,因为范围已经足够精确了。
复制代码

通过代码进行穷举
穷举这活真不是人干的,而且大部分穷举都是有其规律的,我们有时候会使用一些非常简单的代码来进行穷举。

我们这里使用的是JavaScript,这是一种脚本语言,使用还是蛮方便的。读者可以按F12然后找控制台(或者是Console)执行代码。

有兴趣朋友可以去看看相关教程,因为我也不太会,教不了。




我们一般都是先找出命令的模板(用template string),然后看看哪些地方是一个不停改变的数值,找出那数值改变的规律然后计算、放进那模板里。

比如这是上方那对应NBT的代码

  1. //一般也是用for,把一堆选项扔进去

  2. var commands = [];

  3. //生成命令
  4. for (var i = 1; i <= 64; i++)
  5.     commands.push(`scoreboard players set @e[type=item] count ${i} \{Item:\{Count:${i}b\}\}`);
  6. //显示命令
  7. console.log(commands.join('\n'));
复制代码
比如这是上方那二分法的代码

  1. //二分一般都是用2的n次方,从大到小
  2. var commands = [];
  3. for (var i = 2; i >= 0; i--) {
  4.     commands.push(`scoreboard players add @e[name=marker,x=${Math.pow(2,i)},y=1,z=1,dx=${Math.pow(2,i)}] x ${Math.pow(2,i)}`);
  5.     commands.push(`tp @e[name=marker,x=${Math.pow(2,i)},y=1,z=1,dx=${Math.pow(2,i)}] ~-${Math.pow(2,i)} ~ ~`);
  6. }
  7. console.log(commands.join('\n'));
复制代码




记分板分数运算

比较分数大小
比较分数大小其实十分简单, 其核心逻辑就是:
a-=b(a = a - b)

如果a>0,原先的a > b。
如果a<0,原先的a < b。
如果a=0,原先的a = b。

因此用 scoreboard players operation 就可以了(需要注意的是,如果分数a不希望被改变,则可以调换ab的次序,相应逻辑需要被调换。如果两个都不希望被改变,则可以把一个分数赋值到另外一个分数,如c,然后在那边处理)


记分板取最高、最低分数

取最高分数
分数储存在marker的scb里

命令:

  1. scoreboard players set marker scb -2147483648
  2. execute 所有要比较分数的实体 ~ ~ ~ scoreboard players operation marker scb > @e[c=1] scb
复制代码

取最低分数
分数储存在marker的scb里

命令:

  1. scoreboard players set marker scb 2147483647
  2. execute 所有要比较分数的实体 ~ ~ ~ scoreboard players operation marker scb < @e[c=1] scb
复制代码

原理解释
execute部分,是用来确保每个分数都会获得比较
而operation部分,就是当前最高/低分数实体分数比较,如果实体分数更高/低,替换掉当前marker的分数

至于那初始分数, 求最大的时候就设置为记分板下限, 求最小的时候就设置为记分板上限,是为了保证会替换为第一个实体的分数。不替换的可能性只有一个: 等于当前marker的分数。

伪代码

  1. //最大分数初始化为最小分数,那么任何分数都会比这个大,然后替换掉这个分数
  2. 最大分数 = -2147483648
  3. 每一个分数:
  4.     如果当前分数 > 最大分数:
  5.         最大分数 = 当前分数
复制代码

随机分数
以下的方法一般生成比较小的分数, 如果需要比较大的分数则可以运行多次, 获得几个随机分数然后进行移位运算(比如a, b都小于10, 这可以a*10+b这样得出小于100的随机数)。


spreadplayers 命令
基本上就是在一个n*n的平面里(方块, 上方不可以有方块遮挡)放置压力板和命令方块, 然后用 spreadplayers 命令把一个实体随机放在n*n的平面里的某个位置, 接触到压力板然后就让命令方块执行命令( scoreboard players set )

这方法较少使用, 因为比较麻烦


@r
生成n个Marker( type=area_effect_cloud,tag=rnd ), 有不同分数(可以设置多个同一个分数的来调整选中的几率), 选择其中一个的分数。

  1. scoreboard players operation @e[name=储存分数的marker] scb = @r[type=marker的类型,tag=rnd] scb
复制代码
高级用法
你真的想生成上百个AEC来随机0-99么?
当然不用了!只需要10个AEC,随机两次,第一次*10 + 第二次的数值就行了。





逻辑运算---And, or, not
什么是逻辑运算呢?就是对布尔值(boolean,有两个值true/false)进行运算
常用的就是要多个条件同时符合(and),或者是任何一个条件符合(or),或是不能符合某个条件(not)


真值表
以下表格可以拓展至n个不同输入/输出

输入andor
True, TrueTrueTrue
True, FalseFalseTrue
False, TrueFalseTrue
False, FalseFalseFalse
输入not
TrueFalse
FalseTrue

MC实现And

记分板+选择器
选择器的规则就是选择到的实体必须是符合所有条件
由于记分板的选择器参数是 score_XXX 或者是 score_XXX_min ,因此可以同时有多个参数
因此,你可以用多个参数来指定同时拥有多个分数的实体
举个例子, @e[score_a=5,score_a_min=5,score_b=6,score_b_min=6] ,就是选择所有记分板a=5、b=6的实体

需要注意的是,除了记分板以外的参数都不能重复出现( score_ascore_b 实为不同参数,因此可“重复”),详见选择器教程


execute命令
如果是同时需要多个tag,我们可以通过execute+@s来选择。
如我们需要同时有a, b, c tag,我们可以:

  1. execute @e[tag=a] ~ ~ ~ execute @s[tag=b] ~ ~ ~ execute @s[tag=c] ~ ~ ~ 命令
复制代码
也可以做到多反选


Stats
通过检测SuccessCount,我们可以得知之前的命令有没有成功,然后执行别的命令。这样在命令函数里也能做到条件制约。
如果命令a之后的命令需要SuccessCount大于等于1才能继续执行(通过 execute 命令),那就代表了如果前面任何一个命令出现错误,后面的命令就没法执行,这也是and的一种。

例子(命令函数,假设由实体执行并且有一个变量叫scb):

  1. scoreboard players set @s scb 0
  2. stats entity @s set SuccessCount @s scb

  3. # 检测命令
  4. testfor @s[score_test=3]
  5. # 如果不成功的话下面这条命令也不会执行
  6. execute @s[score_scb_min=1] ~ ~ ~ testfor @s[score_test_min=3]

  7. # 如果上面任意一条命令失败了的话这条命令也不会执行
  8. # 如果是第一条检测失败了,那么scb还是0,执行不了
  9. # 如果是第二条检测失败了,那么scb就会变为0,执行不了
  10. execute @s[score_scb_min=1] ~ ~ ~ say test = 3
复制代码

MC实现Or

多条命令
这...简单到我都不知道有啥可以说明的了。
不过注意,要小心重复执行命令,多条命令的条件尽量不要重复。


记分板tag
方法很简单,分别检测不同情况是否符合,符合就给实体一个分数/标签。然后针对那个分数/标签的实体执行命令

例子:

  1. scoreboard players set @a[tag=a] tagScb 1
  2. scoreboard players set @a[tag=b] tagScb 1
  3. execute @a[score_tagScb_min=1,score_tagScb=1] ~ ~ ~ say 我有a/b标签
复制代码
这方法看起来需要的命令多一点,然而有两个好处:

  • 不会重复执行命令。就算条件重复了,被选择到的实体还是那个
  • 重复使用该条件时这方式能节省命令数量。

MC实现Not
not就是当条件不满足的时候才执行命令(为了方便,下面会用!代表not)
举个例子,分数a!=5的时候say hi,就是分数不等于5的时候才say hi


记分板分数
首先给所有实体一个初始分数,然后给符合条件的实体另一个分数
还维持着原来分数的实体就自然是不符合那些条件的实体了

  1. scoreboard players set @e notSlime 1
  2. scoreboard players set @e[type=Slime] notSlime 0
  3. scoreboard players set @e[type=LavaSlime] notSlime 0
  4. execute @e[score_notSlime_min=1] ~ ~ ~ say i am not slime
复制代码
注意: 记分板的好处在于能够在大量逻辑的时候进行简化, 而且如果不止是not一种的话, 记分板就能做到特殊优化


tag
原理就是这样,给符合要求的实体一个tag,然后用=!tag选择没那个tag的实体

  1. scoreboard players tag @e[type=Slime] add Slime
  2. scoreboard players tag @e[type=LavaSlime] add Slime
  3. execute @e[tag=!Slime] ~ ~ ~ say i am not slime
复制代码
注意: tag的话则不能做到and。如果需要复杂逻辑的话建议使用记分板分数,因为那比较方便优化


Stats
和And的部分类似,我们检测SuccessCount=0就好,所以这里就不多加以描述了。

然而注意多个条件,比如

  1. # 绑定stats的就不写了

  2. testfor @s[score_test=0]
  3. execute @s[score_stats=0] ~ ~ ~ testfor @s[score_test_min=10]
  4. execute @s[score_stats=0] ~ ~ ~ say 1-9
复制代码
这是错的。
因为如果满足了第一条命令,第二条命令自然就会失败,而stats就会变为0,满足了第三条命令。
上面的命令其实是 not ((not a) and b),也就是 a or (not b)


命令函数
命令函数能够很方便的做到if 和 unless(else)。
故此复杂的条件可以拆成if else部分放进不同命令函数进行调用。



循环
如果读者曾经学过编程,相信也知道循环对程序是多么重要的了。
通过循环,我们可以为每个实体执行动作,或是执行一些动作到某个条件不成立等等。

这里说的循环是1gt内的循环。我们会使用命令函数。


Foreach——对每个实体循环
对于单一命令,我们可以很简单的使用 execute 命令。因为execute就是让每个实体执行的,而执行顺序是根据选择器选择玩家的顺序。

不过如果我们需要执行多条命令呢?我们可以使用命令函数和 execute 命令。


例子
把每个marker(id分数都不同)传送到id分数相同的玩家身上。

首先我们先写一下每个marker应该执行的命令

  1. # tp:match

  2. # 首先检查分数
  3. scoreboard players operation @a id -= @s id
  4. # tp过去0分的玩家那里
  5. # 使用@p是因为你根本不可能tp到一个死人那里...所以就没差了
  6. tp @s @s[score_id_min=0,score_id=0]
  7. # 重置分数
  8. scoreboard players operation @a id += @s id
复制代码
然后我们可以直接execute所有marker执行这条命令

  1. execute @e[name=marker] ~ ~ ~ function tp:match
复制代码
就是这样,超级简单的说,不过基本上不用function就无法做到了。


While——当条件成立就一直循环
有时候我们需要循环到某个条件成立/不成立,这时候我们就需要while了。

while简单来说就是当某个条件成立,就一直循环某些命令,直至条件不成立。
我们在命令函数里会使用尾递归的方式,类似函数编程。


例子
生成n个marker。我们把条件设置为

当count >= 1的时候就不停生成marker,每次count -1,直至count < 1 (count = 0)

  1. # util:summon_while

  2. scoreboard players remove @s count 1
  3. summon armor_stand ~ ~ ~ {CustomName:marker}
  4. function util:summon_while if @s[score_count_min=1]
复制代码
比如说我们要生成两个marker,我们会执行:

  1. scoreboard players set @s count 2
  2. function util:summon_while
复制代码
第一次调用那命令函数的时候,count会-1,变为1。
由于count >= 1,因此会第二次调用那命令函数

第二次调用的时候,count会-1,变为0。
由于count < 1,因此不会再调用自己,循环停止。



事件
制作地图、原版模组时,我们常常要回应不同的行为、转变。
比如是玩家点击了牌子,玩家使用了某个物品,进入了黑夜等等。
这些我们成为事件(Event)。本部分将会介绍几个常用检测事件的方法。


高频
大部分地图的命令系统都基于高频。
高频就是说,每游戏刻都执行一堆命令一次。一般是一秒20次。

主要原因是除了Advancement以外我们没有别的方法能够在事件发生时调用命令。因为我们没法调用命令,我们只好不停执行命令,当某些事情发生时去进行工作。

除了检测事件以外,也有一个原因需要使用高频:我们需要不停进行某个工作。比如说是设置一个计时器(Timer),我们一般会使用记分板变量来储存计时器的数值,高频+1/-1,达到某个数值的时候停止更改并且进行处理。

对于事件来说,我们一般只会高频执行一些判断的命令,判断到某个事情发生再调用指定的命令函数。


检测数值变化
有时候我们想检测NBT变化,我们会把NBT数值放进记分板变量(怎么放?穷举呗!),然后检测记分板数值变化。
对于某些特殊判据也是如此,比如air,我们要检测变化也是使用这方式。

其核心就是,如果 旧数 - 新数 = 0,那么新数=旧数;否则则不一样,也就是有变化。
我们一般会用一个主要记分板储存新数值,一个记分板储存旧数值,然后后者-=前者。

例子:(old为旧数,new为新数,只有新数会被其他东西改变)

  1. # 高频

  2. execute @e[name=marker] ~ ~ ~ scoreboard players operation @s old -= @s new
  3. execute @e[name=marker,score_old_min=1] ~ ~ ~ say 有改变
  4. execute @e[name=marker,score_old=-1] ~ ~ ~ say 有改变
  5. execute @e[name=marker] ~ ~ ~ scoreboard players operation @s old = @s new
复制代码

检测判据
大部分判据,都是增加分数的。
比如是记分板例子中的 stat.leaveGame ,就是一个例子:当玩家离开游戏时会为其分数+1。
因此我们可以采用上方方式的变种:先把分数设置为0。如果分数不是0就代表发生变化了。

例子:(leaveGame的判据为stat.leaveGame)

  1. # 高频

  2. # 只会加分不会减分,故此不需要检测负数
  3. # 如果stat.leaveGame为1就代表那玩家曾经离开这游戏/服务器。然而由于我们是没法选择离开了的玩家的,所以检测到他就代表他回来了。
  4. execute @a[score_leaveGame_min=1] ~ ~ ~ say 我回来了!
  5. scoreboard players set @a leaveGame 0
复制代码

进度
进度(Advancement)提供了一些方便的方法检测玩家行为,如检测玩家用了什么物品,到了哪里,被谁攻击或攻击了谁等等。
而进度的rewards能够调用命令函数,故进度是一个不错的检测方式。

详情请参见之后的进度教程。

进度的 rewards 是十分有效率的,因为不需要高频执行命令,只是在需要时执行。





标记实体(Marker)
命令里有很多地方都需要使用实体,如 execute 命令,相对坐标(作为参照点),储存数据等等。
我们会使用标记实体(Marker)做到两个主要用途:标记坐标以及储存数据。当然,有时候是同时进行的。


标记坐标
命令系统里一般不会有太多绝对坐标,这是为了让系统更有弹性,能应付各种情况。
然而我们使用相对坐标的时候也需要有一个参照点啊!没法用别的参照点的话就和绝对坐标没分别了,甚至更为复杂。
因此,我们会使用marker标记某些坐标,然后 execute 那个marker执行某些命令,那样相对坐标就是相对于那个marker了。


例子
我们以自定义“合成台”为例子,其实就是个投掷器(Dropper)。

我们如果要检测“合成台”内的物品,我们需要得知该方块坐标,一般我们会在方块的位置放置marker,然后执行以下命令:

  1. execute @e[type=area_effect_cloud,name=craft] ~ ~ ~ testforblock ~ ~ ~ minecraft:dropper * {Items:[]}
复制代码
其实就是让marker检测自己位置的方块。

进阶
有时候我们会让marker移动来检测不同地方,达到类似扫描的效果。


储存数据
虽然假名也能储存分数,然而假名不能批量操作(并且要通过筛选),也不能execute命令。故此有时候我们是会用marker来储存分数的。
比如之前循环的部分,我们很多时是使用一个marker来储存分数的,因为我们能够很方便的选择到那个marker和检测分数。


例子
我们可以利用marker来实现Stack,不停放资料进去,最早放进去的最迟被人移除。(想知道更多有关stack的资料可以去百度/Google)

我们会有个变量叫id,id越大越早放进去,1分就是当前最后一个marker。(这是为了方便)
所有marker都是area_effect_cloud,叫marker

首先我们要写加入数据的

  1. # stack:push
  2. summon area_effect_cloud ~ ~ ~ {CustomName:marker}
  3. scoreboard players add @e[type=area_effect_cloud,name=marker] id 1

  4. # 慢慢为id=1的marker加入数据
复制代码
然后写移除数据的...

  1. # stack:pop
  2. kill @e[type=area_effect_cloud,name=marker,score_id=1,score_id_min=1]
  3. scoreboard players remove @e[type=area_effect_cloud,name=marker] id 1
复制代码
之后的 登录系统例子 就是使用了这个概念。



execute基础
本章教程讲解 execute 命令的一些进阶用法,然而现在已经可以不使用这些用法了,使用命令函数就可以了。
不过使用这些办法有时候还是能减少命令数量的,对于了解选择器及 execute 命令的运作有实际作用,而且颇为有趣,故此就仍然放在命令进阶里。


execute 格式大要
  1. execute <实体> <x> <y> <z> <命令>
复制代码
让<实体>在指定位置(x、y、z坐标。如果是相对坐标则以实体位置为判定点)执行指定命令

  1. execute <实体> <x> <y> <z> detect <方块id> <方块数据值/方块状态> <x2> <y2> <z2> <命令>
复制代码
当实体在指定位置检测到指定方块(x2、y2、z2坐标。如果是相对坐标则以执行位置为准。执行位置即x、y、z坐标的位置。id及数据值/方块状态必须吻合。如果数据值为-1或方块状态为*则代表不检测数据值/方块状态)时,则在执行位置执行命令(执行位置不是检测到方块的位置,而是先前x、y、z坐标的位置)

由于本教程主要是指出execute有什么主要用法, 而detect比较少用(起码我比较少用), 而且那用法一般也比较简单, 因此本教程不会探讨execute detect的特殊用法


注意
  • execute选择到的实体不需要有op权限也可以执行命令(虽然这应该是常识, 然而不知道为什么总会有人问这个问题)
  • execute里面可以嵌套execute(然而得注意, 这可能会让执行次数变得极高)。就好像叫别人去命令别人做事的样子。

例子:
  1. execute pca ~ ~ ~ setblock ~ ~ ~ command_block
复制代码
如果pca的坐标是0 64 0, 则会在0 64 0的位置放置一个命令方块

  1. execute pca ~ ~5 ~ detect ~ ~-5 ~ air 0 setblock ~ ~ ~ command_block
复制代码
如果pca的坐标是0 64 0, 如果0 64 0是空气,则会在0 69 0的位置放置一个命令方块

  1. execute @e ~ ~ ~ execute @e ~ ~ ~ say hi
复制代码
如果有两个实体, 则会让该两个实体say hi(每个两次)


execute的运行方式
execute会让选择到的所有实体一个一个的逐个执行命令。
当那些实体全部执行完命令, 这execute命令才算是执行完成。
(所以大量execute嵌套很可能会导致game tick过长)

实体执行命令的次序就是选择器选择实体的顺序
选择器会优先选择在执行的世界的实体/玩家
(先选择最近的实体,当那些实体的距离一样时,它就会优先选择存在最久的)

execute命令的SuccessCount就是成功让多少实体执行命令。
至于AffectedEntities即使成功也只会是1(只是执行了很多次而已)


例子
  1. execute @e ~ ~ ~ say hi
复制代码
假设实体的排列是 A  B  C  D , 命令方块放在最左边。

执行结果:

[A]hi
[B]hi
[C]hi
[D]hi

  1. execute @e ~ ~ ~ execute @e ~ ~ ~ say hi
复制代码
假设实体的排列是 A  B , 命令方块放在最左边。

执行结果:

[A]hi
[B]hi
[B]hi
[A]hi

(原因和选择器的特性和执行坐标的改变有关, 你们猜猜为什么吧:D)


使用execute改变执行坐标
execute命令能够改变命令的执行坐标, 这对系统的可移植性作出了极大的贡献, 和选择器配合的好甚至能够做到一些原本需要多条命令才能做到的事情。


标记(marker)
为了系统的可移植性, 我们会避免用绝对坐标去标记不同的位置, 甚至连相对坐标也会避免使用。
然而, 有时候我们的命令还是需要知道某些位置才能工作的(比如是要关闭某个命令方块)

这个时候, 我们会把一个特定名字的实体(一般是AreaEffectCloud, 因为即使有大量的AreaEffectCloud对性能的影响是最微小的)放在指定的位置, 然后通过execute该实体去执行需要该坐标的命令(因为execute后执行坐标)


例子
(mark:(名字) 就是在下一个命令方块的位置放置一个指定名字的AreaEffectCloud)

  1. say hi
  2. execute @e[name=1] ~ ~ ~ blockdata ~ ~ ~ {auto:0b}
  3. mark:1
  4. say 这不会执行
复制代码
由于第二条命令(execute ... blockdata ...)先改变了执行坐标(改为第三条命令的命令方块位置), 再把原地的命令方块关掉。因此就能够关闭第三条命令的命令方块, 从而阻止其执行


特殊用法
有一些execute的特殊用法, 通过巧妙地更改执行坐标和使用选择器, 就能够做到一些原本要几条命令才能做好的事情

本部分由于本人难以说清如何想到此脑洞, 所以只能对用法进行解释, 希望能启发诸位想到更多新奇有趣的用法


生成一直线的实体
(本用法和其他章节的增加执行数量的用法配搭可有更佳的结果)
(本用法有一个限制: 命令的执行坐标必须在开始的那端, 否则可能会造成重叠的现象)

  1. execute @e[name=marker,c=-1] ~ ~ ~ summon armor_stand ~(间隔) ~ ~ {CustomName:"marker"}
复制代码
marker就是生成的那些实体的名字, 而间隔部分不一定是要x轴的, 放在任何地方也可以, 甚至三个轴上同时有间隔也并无不可

相信大家也看明白了, 本命令就是通过选择最远的那个marker去生成别的marker, 不停执行则可以生成一串的实体


调换执行
通过execute里让不同实体执行命令的特性, 我们可以做到在实体间调换执行, 以达到某些特殊用途。
相信这样写没人知道我在说啥, 所以接下来会给出两个例子


辗转相除法
就是不停a %= b, b %= a...直至一方为0

按照一般的思路,这个做法得两条命令, 毕竟a %= b, b %= a是两个不同的东西吧。
然而问题来了, 这样子还怎么放在execute里1gt内完成啊?

所以, 就有了一个方法的诞生:

  1. execute @e[tag=num] ~ ~ ~ scoreboard players operation @e[c=1] scb %= @e[c=-1,tag=num] scb
复制代码
这个方法其实就是这样的: 让a和b去分别把自己的分数%=最远的那个(就是另外一个)的分数

至于怎么做到大量执行次数就留待之后的环节继续讲解了


1 game tick分队
这方法和暴力增加执行次数有点关系

1gt内把一堆实体平均分到n队里, 多出来的实体放在随机队伍里。

这问题有几个难题, 首先怎么做到分配在一个队伍里呢? 这个我们可以使用分数(这是优化了的方法)
放置一堆代表队伍的marker, 它们的team分数代表那队伍, 然后让随机实体的分数=那个队伍的分数

  1. /scoreboard players operation @r[score_team=0,type=!LightningBolt] team = @r[tag=teams,c=1,type=armor_stand] team
复制代码
然后, 我们会让所有队伍都有一个实体(由于多出来的实体要放在随机队伍里, 因此我们会使用 @r[c=n] 来随机排序那些队伍)

  1. /execute @r[type=armor_stand,tag=teams,c=n(n就是队伍数)] ~ ~ ~ /scoreboard players operation @r[score_team=0,type=!LightningBolt] team = @e[tag=teams,c=1,type=armor_stand] team
复制代码
然后的部分就是: 让所有需要加入队伍的实体执行一遍上面的部分, 并且加上避免过长执行时间的限制execute(确定还有没有team=0的实体存在)

  1. /execute @e[score_team=0] ~ ~ ~ execute @e[score_team=0,c=1] ~ ~ ~ execute @r[type=armor_stand,tag=teams,c=n(n就是队伍数)] ~ ~ ~ scoreboard players operation @r[score_team=0,type=!LightningBolt] team = @e[tag=teams,c=1,type=armor_stand] team
复制代码

execute暴力增加执行次数
这是一些 execute 嵌套的特殊情况,主要是解释execute的运作原理。如果真的需要增加执行次数,请使用之前的循环


execute嵌套(指数增加执行次数)
实体穷举的办法需要大量实体, 如果不愿意放置大量实体的话可以使用execute嵌套的办法去增加执行次数

  1. execute 实体 ~ ~ ~
复制代码
*n出来的执行次数会是实体数^n那么多, 因此是增加执行次数的一个好办法

注意: 这办法没法确保执行多少次, 请加入一个execute去检查条件是否已经满足(那条件检测建议放在第二个execute大量实体之前的位置, 否则用途可能会比较小, 见上方1gt分队), 否则会让game tick时间大幅延长, 对服务器造成严重负担


暴力增加次数的特殊情况: CBer的execute问题
问题很简单:

execute @e ~ ~ ~ execute @e ~ ~ ~ execute @e ~ ~ ~ summon armor_stand
初始实体为2个, 执行完了之后实体数会是多少? 并且为什么是那个数量

这个问题有很多人认为是8(2^3), 然而之后证实了是2048, 而且也找到一个合理解释了, 以下就来为大家解释一下

execute @e ~ ~ ~ execute @e ~ ~ ~ summon armor_stand
execute @e ~ ~ ~
  [当前实体数为2, 于是下面会执行两次]
    execute @e ~ ~ ~
      [当前实体数为2, 于是下面会执行两次]
          summon armor_stand
          summon armor_stand
    execute @e ~ ~ ~
      [当前实体数为4, 于是下面会执行四次]
          summon armor_stand
          summon armor_stand
          summon armor_stand
          summon armor_stand

从此可以见到, execute的运作就是先选择实体,然后由选择了的实体去执行命令
选择器在选择好了实体之后就不会改变(比如execute @e ~ ~ ~ summon armor_stand,初始实体数为2,那就只会执行2次),所以第一个execute的数目是不会无限增加下去的
不过,后面嵌套的那些execute会根据执行时存在的实体去选择,所以那个执行次数会变大的很快(因此就不是2^嵌套数那么多个实体)

所以如果要通过execute嵌套去生成实体, 请先小心计算之后会生成的实体量, 以免发生以外的情况


execute与stats
execute和stats配搭使用可以做到好像同时使用了一条命令和修改记分板分数的样子, 可以省却大量的穷举。

然而这也是有限制的, 首先: SuccessCount是整个命令执行完毕才改变分数的而不是一边执行一边增加分数。
其次, execute做成的SuccessCount是会被覆盖而不是继续增加的, 以下会说明嵌套execute时SuccessCount的变化

  1. 实体: A, B, C
  2. 命令: execute @e ~ ~ ~ execute @e ~ ~ ~ say hi

  3. 第一个execute:
  4. [执行者SuccessCount=0]
  5.     A: execute:
  6.         A: say hi
  7.         [A SuccessCount=1]
  8.         B: say hi
  9.         [B SuccessCount=1]
  10.         C: say hi
  11.         [C SuccessCount=1]
  12.     [A SuccessCount = 3]
  13.     B: execute:
  14.         B: say hi
  15.         [B SuccessCount=1]
  16.         A: say hi
  17.         [A SuccessCount=1]
  18.         C: say hi
  19.         [C SuccessCount=1]
  20.     [B SuccessCount = 3]
  21.     C: execute:
  22.         C: say hi
  23.         [C SuccessCount=1]
  24.         B: say hi
  25.         [B SuccessCount=1]
  26.         A: say hi
  27.         [A SuccessCount=1]
  28.     [C SuccessCount = 3]
  29. [执行者SuccessCount=3]

  30. 最后不同实体的分数:
  31. A = 1
  32. B = 1
  33. C = 3
  34. 执行者 = 3
复制代码

例子: 1 game tick内给实体增加索引值
这道例子出自CBL新手群问答比赛

索引值可以是1-n, 也可以是0 - (1-n)

方法:

  1. //绑定stats部分(略...)

  2. execute @e[tag=markers] ~ ~ ~ testfor @e[score_SC=0]
复制代码
解释: 当第一个实体执行testfor的时候, 自然所有实体的SC(SuccessCount分数)都是0了, 所以其索引值(也是SC记分板)为n。之后, 第二个实体执行的时候就有一个实体SC分数大于0, 于是其SC分数只有n-1了。如此类推, 到最后就只能选择到自己, 于是SC分数就是1了[groupid=546]Command Block Logic[/groupid]