欢迎光临散文网 会员登陆 & 注册

关于强转抑制器的笔记

2023-06-13 15:36 作者:Void0  | 我要投稿

一、强转抑制器的理论

这部分的代码来源是 1.19.2 Yarn。

更新抑制,是技术生存玩法中的一项非常常用且重要的技术。它的本质原理是通过抛出一个异常,来打断代码的执行。在常用的包含一个铁轨阵列的抑制器中,我们用递归的方块更新,抛出了一个栈溢出异常,实现了打断代码的目的。但是,栈溢出异常这个具体的异常类型并不是本质的,只要是个异常就可以了。在 1.19,栈溢出更新抑制被修复了。但是我们有内存溢出更新抑制,这就是基于内存溢出错误而实现更新抑制的一种方法。

ClassCastException 类型转换异常,简称 CCE,是 Java 的一个运行时异常,当一个对象被强制类型转换到一个它不符合的类型的时候被抛出。因此,如果我们可以通过玩家操作抛出一个 CCE,也可以用来实现更新抑制。一般而言,我们并不会有这样的机会。但是方块实体替换科技的发展给了我们这样的希望。

(方块实体替换请看 www.bilibili.com/video/BV1B8411W7Di

比如说,如果将一个箱子的方块实体换到一个发射器上,当发射器执行发射动作的时候,会运行下面的代码:

nm.block.DispenserBlock#dispense(...), Yarn 1.19.2

它会获取自己位置的方块实体,然后不加判断地将其强制转换为一个发射器方块实体。但是我们换了一个箱子的方块实体上去,就抛出了 ClassCastException,导致游戏崩溃……于是就崩档了……

这个 CCE 的位置太危险了,但是也给了我们新的希望——也许代码里面有别的类似地不加判断的强制类型转换,可以让我们通过玩家操作瞬时地抛出一个 CCE?于是我查找了游戏代码里面的所有 getBlockEntity() 的调用,大概有一百处的样子,经过一处处地翻找有了发现:

nm.block.ShulkerBoxBlock#getComparatorOutput(...), Yarn 1.19.2

当一个潜影盒计算自己的比较器输出的时候,它会获得自己的方块实体,强制类型转换为一个物品栏,然后丢进 calculateComparatorOutput() 这个函数。实际上,函数 calculateComparatorOutput() 里面已经包括了安全的类型转换的逻辑,麻将写在这里的这一处不加判断的类型转换是完全没有必要的,也是危险的。甚至潜影盒之外的别的容器,比如箱子计算自己的比较器输出的时候都没有这个转换,这一处代码是麻将的祖传代码,从 1.11 潜影盒加入传到现在都没变过……

但是这一处强制类型转换是可以利用的——我们如果能够让一个潜影盒拥有一个非物品栏的方块实体,那么每当比较器想要计算它的输出的时候,就会抛出 CCE。那么如果我们将一个比较器从这样的潜影盒里面输出,那么每次更新这个比较器,比较器就会重新计算一次自己该输出的能量以判断是否应该更新自己的状态,于是就会计算这个潜影盒的输出,然后就抛异常了!这个潜影盒加上这个比较器,就构成了一个没有状态、不需要重置的更新抑制器

由于它是基于强制类型转换异常工作的,这样的抑制器我们把它叫做 强转抑制器,英文名叫做 cast suppressor 或者 CCE suppressor;另外,因为这个潜影盒是打不开的,又具有更新抑制的能力,有时我们也称这个潜影盒为 魔法盒

二、强转抑制器的制作

这部分的代码来源是 1.15.2 Yarn。

为了实现方块实体替换,我们要找到合适的带有方块实体的方块,它被破坏的时候,在删除自己的方块实体之前会发出一次更新,方便我们用别的更新抑制方法打断逻辑,跳过删除方块实体这一步。同时,为了潜影盒可以发出 CCE,我们需要换上去的方块实体不是一个物品栏。通过查阅代码我们可以知道,讲台(1.14+)和唱片机(1.11 - 1.13)可以满足我们的要求。鉴于大部分读者都是高版本玩家,我就只讲讲台的方法就好了。

关于方块实体替换的原理,我在前面是给了一个链接的,你自己去看就好了。为了破坏掉讲台而不破坏它的方块实体,我们要在讲台的 onRemove() 方法这里发出一次可以被抑制的更新,那么我们来看一下逻辑:

nm.world.level.block.LecternBlock#onRemove(...), 1.15.2 Yarn

原版有一个特性是,当讲台上面的书被翻页的时候,会发出一个 2gt 长的红石信号,这个时候讲台处于启动状态。我们读代码知道,当启动状态的讲台被破坏的时候,会更新自己下方的毗邻。因此,我们在视频中,tick freeze 了之后先翻书,然后打开中继器下的活板门,让它浮空,然后我们打掉讲台,讲台在这里更新中继器,中继器碎裂,更新到一个栈溢出抑制器的 bud 链,触发更新抑制,后续逻辑被打断了。我们就成功破坏掉了讲台,而保留了它的方块实体。

这个方法需要 tick freeze,并不是很生存友好。我们有下面两个替代的方法:第一个方法是这样的,请看下面动图:

我们翻书的时候,下面的铁轨亮起,触发了更新抑制。这是早于讲台启动状态解除的计划刻被计划的,那么这个计划刻就不再有了,讲台变成了一个常亮的状态,这个时候我们等抑制器复位,然后直接拆掉讲台就可以了。这种方法适合 1.18- 的版本,因为栈溢出更新抑制仍然适用,我们可以承担一次额外的更新抑制。

在 1.15 和 1.18 之间的某个版本之后,放了书的讲台被拆掉,会在删掉自己的方块实体之前发出一次比较器更新。因此,在至少 1.18(1.15 不行,之间的版本没测试过),还可以这样做:

1.18,直接拆掉对着更新抑制器的浮空比较器边上的有书的讲台也可以

现在,我们在保留方块实体地拆掉讲台之后,在那个位置放一个潜影盒上去,就可以得到一个有讲台方块实体的潜影盒了。这就是我们的强转抑制器的核心,更新一个从它这里取输出的比较器,就会触发基于 CCE 的更新抑制!

三、强转抑制器对物品分身的应用、强转抑制器的关闭

我们说过,强转抑制器的触发,是在从魔法盒中取输出的比较器计算自己的能量的时候触发的。但是,比较器收到比较器更新,也会计算自己的能量啊,也会触发更新抑制。也就是说,我们可以在 1.11-1.17 这段版本,不需要放置浮空比较器,而快速地制作物品分身!请看下面视频:https://www.bilibili.com/video/BV1dh411K7uC

当物品被放进投掷器,投掷器物品栏发生变化,发出比较器更新,抑制器中的那个比较器重新计算自己的能量,更新抑制触发!我们就完成了一次物品分身。连续这样操作,我们就可以以极为快速的方式制作物品分身,这是目前已知的最好的物品分身制作的方法。

我之前说过,强转抑制器是无状态的,更新到比较器就抛异常,但是这也不绝对。异常的触发是在计算潜影盒输出的时候发生的,那么如果比较器受到更新可以不计算这个输出,就不会抛出异常了。怎么做呢?我们可以对比较器后面的固体方块用红石粉施加 15 的能量,这样的话,比较器看到 15 就被覆写了,不再会检测容器,就不会抛异常了,强转抑制器就被关闭了。

如图所示,灯亮,抑制器启动,灯灭,抑制器关闭。另外,强转抑制器的工作是需要用比较器从潜影盒取输出的,所以说如果把比较器拆了,只有潜影盒在那里,也是安全的,不会触发更新抑制。

四、强转抑制器对 1.19 的影响

1.19 更新,最受技术玩家诟病的改动,就是栈溢出更新抑制的修复了。但是,我们有办法将更新抑制夺回来!潜影盒的那段代码是麻将的祖传代码,从 1.11 到现在的最新版本 1.20.1 从未变过,这意味着强转抑制器直到最新版本都可用!

这可不是一台更新跳略器,这是一台货真价实的更新抑制器!

在 1.19,我们已经有了基于内存溢出的更新抑制器,但是它极其不友好,抑制一次,服务器需要卡顿五分钟,如果用来切门的话显然力不从心。但是强转抑制器不一样,它运行起来几乎没有卡顿,和以前的栈溢出抑制器一样丝滑,这意味着 1.19 也可以大规模使用更新抑制,进行雕花下界门之类的应用。

但是建造强转抑制器在 1.19 并非易事,因为用到的方块实体替换需要一台真正的更新抑制器。为了在 1.19 的服务器使用强转抑制器,你有两个选择——其一,使用一次内存溢出更新抑制实现方块实体替换,建造第一台强转抑制器,然后用已有的强转抑制器进行更新抑制,制造更多的强转抑制器。其二,在 1.18- 的版本用栈溢出抑制器建造好你的强转抑制器,然后升版本升到 1.19。内存溢出更新抑制较为麻烦,但是你只需要实施它一次,所以可以接受,况且伊鸽纳他们也即将发布更便宜的内存溢出更新抑制的方法了。

等到那时候,1.19 的服务器可以花几个小时的时间操作第一次内存溢出,解锁强转抑制器的科技,然后更新抑制将像以前的版本一样,满血复活。


关于强转抑制器的笔记的评论 (共 条)

分享到微博请遵守国家法律