Minecraft 20w49a 新内容特性+代码探析

20w49a出来了,更改了很多东西。那么直奔主题,开始说说这一个版本干啥了。
首先给出一下这个版本的类更改:
20w46a->20w48a类更改表 https://paste.ubuntu.com/p/DN2qYNZbjF (上周快照的)
20w48a->20w49a类更改表 https://paste.ubuntu.com/p/W3FwCbT2jG/
一.滴水石锥的bug修复
上一个版本中,钟乳石在50格以上敲掉就不会掉落(MC-206578),这个版本修复了,原因是findTip修改回了直接一查到底(寻找范围最大是int最大值2147483647)。

所以这回牛顿可以安心地火化了(
同时,钟乳石砸死增加了自己的伤害来源FALLING_STALACTITE(代码太短小了,不放出来了),并且提示是XX被坠落的钟乳石刺穿了(新的死亡方式!)。
然后是生电玩家比较在意的东西:石笋的伤害计算方式修改了,不再是以前的直接乘4倍了,现在的公式是:
(fallDistance + 2) * 2

最后,它现在可以自然生成,新加入了溶洞地下生物群系。由于地形生成就是一大堆繁琐公式,就不在这里提及了。
二.Sculk Sensor(暂无中文译名)
这是一种新的红石元件,可以探测振动(说明白就是声音)。它能检测8格之内的有效振动(8格这里指欧几里得距离而不是曼哈顿距离)
首先说说它的方块定义,它有三种属性:
PHASE 阶段,是一个枚举值,包括COOLDOWN(冷却),ACTIVE(激活),INACTIVE(未激活)
POWER 充能,也就是红石信号直接输出值,一个0-15的整数值
WATERLOGGED 含水,含水方块通用的属性

说完方块定义,说一下它的其他特性:
第一.这是一个方块实体(BlockEntity,在MCP中也叫TileEntity)
和普通的漏斗、熔炉一样,它是一种方块实体,对应的类是SculkSensorBlockEntity。
第二.方块更新
这种新方块的更新规律是:先以自身方块向周围发出更新,之后在它的下方方块上发出再一次更新,类似充能铁轨。

这种更新在它激活时被破坏/激活/取消激活时出现。

介绍完简单特性,下面是监听声音的原理(可能比较长,请耐心阅读)
首先来说激活阶段,也就是上文的PHASE属性。当它被激活时,它会进入40tick的ACTIVE(激活)阶段,之后是1tick用于COOLDOWN(冷却),最后回到INACTIVE(未激活)。这里的所有操作都基于计划刻。只有当它是未激活状态时才能接受振动信号。
P.S.为啥有1tick不应期?猜测是防止轮回吧。

那么方块这边的事情说完了,看看发出声音的源头是怎么通知的吧。
之前我在动态中做过预测:这个探测声音绝不是用了POI。没错,它确实没有用。不但没有用,还增加了一个新的通知机制:GameEvent。
首先看看GameEvent是什么吧:

这个新的事件机制对象包含了事件名称和事件通知范围。目前的通知范围都是16。当你查看现在都有什么GameEvent的时候,你会发现现在只有所有的可接收振动的事件。

这些事件可以通过Level的postGameEventInRadius方法和ServerLevel、Entity的gameEvent方法发送,这个就是我们寻找的事件触发。

那么这里我们还是给一个例子吧。假如说现在打雷了,那么系统就会对闪电束实体进行执行tick,那么这里我们就可以看到调用gameEvent的影子。

可以看到这里发送了GameEvent。这里就是监听的起点。接下来来说事件的通知。
在刚才的发送GameEvent的代码中,我们看到了它调用了LevelChunk的方法,如下:

这里我们看到了它创建了一个EuclideanGameEventDispatcher。等下,这名字翻译过来...欧几里得?继续向下看,我们会发现它调用的好像还真是以欧几里得距离查找的方式。。

那这样看来,发送器这一方面已经了解的差不多了,下面来看看监听器部分。
在寻找之后,发现GameEvent有一个子包vibrations,在里面有一个VibrationListener实现了GameEventListener,看来就是它了。
看看它是怎么创建出来的。之前说过Sculk Sensor是一种BlockEntity,在它的代码里面就写到了VibrationListener的初始化,并且将自己作为了设置传入到内部。

这个方块实体内部定义了是否应该接受这个振动事件和对振动事件的处理。

在onSIGNALReceive中,我们可以看到激活的红石信号强度是怎么计算的:它通过了一个名字叫getRedstoneStrengthForDistance的方法计算,它的定义如下:

那么简化的公式如下:SIGNAL = max{1,15 - floor(distance / 8.0 * 15.0)},其中distance是向下取整的欧几里得距离值。

可是这个公式是有问题的:我们可以发现distance就是一个整数,它的合理范围是0-8,也就是说,在计算之后只会出这几种红石信号(下面的前一个数字是距离,后一个是红石信号强度):
0-15,1-14,2-12,3-10,4-8,5-6,6-4,7-2,8-1
也就是说,真正利用的红石信号也就是这几个,看来这是一个bug。
同时,这个监听器可以存储振动来源的类型,因为它可以保存GameEvent数据,之后可以进行判断。这种红石信号需要用比较器去获取。


现在我们已经差不多了解完它的触发了,现在来看看最后的触发条件。
首先,振动来源不能是破坏其他的Sculk Sensor,并且这个Sculk Sensor必须处于未激活状态。(代码部分:SculkSensorBlockEntity#shouldListen)

第二,以本Sculk Sensor为起点到振动源的方块的射线上不能有任何带有OCCLUDES_VIBRATION_SIGNALS标签的方块(也就是羊毛),这个射线是以方块中心点开始计算的。(代码部分:VibrationListener#isOccluded)


第三,来源是合法的振动来源,并且实体不是观察者模式,在一些特定振动事件中潜行会导致取消监听。(代码部分:VibrationListener#isValidVibration)
在这些条件满足之后,监听器会计算出振动到达Sculk Sensor的时间,计算公式就是向下取整的欧几里得距离值,也就是和距离是一样的。这个时间通过tick倒数,同时这个也和振动的粒子效果有关系。
到这里Sculk Sensor的介绍也就差不多了,接下来看看其他的:限高修改。
三.限高修改
从远古时期开始,建筑高度限制一直是0-255。这个版本中通过height(总体高度)和min_y(最低建筑高度)两种属性值可以修改这个东西。由于这个东西牵扯了太多的东西,不打算细讲(也许等到稳定了之后再去细讲)
首先,Level中增加了新的方法getMinBuildHeight,这是获取最低建筑高度的。并且很有意思的是,在isInSpawnableBounds这个方法中,写到了Y轴处在正负20000000内返回true。
在HeightMap中,不少东西也改成了getMinBuildHeight和getHeight的混合。
生成这样的世界,你需要导入设置,指定height和min_y。在读取之后会被保留在NoiseSettings(噪声生成设置)里面。

那么这个高度现在可以扩展到多少呢?在DimensionType里面,我们找到了答案。

这段代码中,我们得知:
height和min_y是必须可以被16整除的数值(保持单位区块是完整的)
最大高度(height+min_y)要小于MAX_Y,这个值是2047(2^11-1)。
最低建筑高度(min_y)好像没限制??
由于我的电脑连256格都费劲,所以可能需要大家验证一下。
这个内容等下个版本快照继续写,这里太难解析了。
P.S.为什么最大高度是2047?这里是解释:MC用一个long保存世界的范围数据,由于X轴Z轴最大是30000000,由于不具有符号,所以只需要26位就可以保存,所以只剩下12位保存Y轴的大小,因为有符号,所以只能把它设为-2048~2047。

代码:Mojang官方混淆表+反混淆+反编译
反混淆器:MCDynamicExchanger beta 6(正在开发)
beta 5 版本在GitHub上可用,
网址https://github.com/Nickid2018/MCDynamicExchanger
反编译器:Eclipse插件, FernFlower/CFK
文章中若有错误请大家指出,我将修改专栏。