跳到正文

共同阵容构建之后,世界仍可以改变方案。摇滚年代替换普通指令;恐龙危机和摩登世界添加独立动作;蒸汽时代则在更晚的设置阶段安装场地系统。要理解最终战斗,需要知道事件安排、每个事件执行的操作,以及这些结果进入 Board 的先后顺序。

先抽取端点,再插值

多个设计器使用同一个辅助函数。对于关卡边界为 L0,L1L_0,L_1 的参数,它先从包含两端的低关卡和高关卡端点范围中抽取整数 aa 与 bb,再计算

α(L)=clamp⁡(L−L0L1−L0,0,1),v(L)=trunc⁡(a+α(L)(b−a)).\begin{aligned} \alpha(L)&=\operatorname{clamp}\left(\frac{L-L_0}{L_1-L_0},0,1\right),\\ v(L)&=\operatorname{trunc}\bigl(a+\alpha(L)(b-a)\bigr). \end{aligned}

原生运算使用 float32 与融合乘加。固定端点仍消耗一次抽取;即使限制后的比例为 0 或 1,也会抽取两个端点。因此,端点范围表定义的是先抽取、再插值,而不是在两个插值后的边界之间均匀抽取。11先抽取,再插值辅助函数进行两次包含边界的整数抽取,限制关卡比例,使用 float32 融合乘加对抽取的端点插值,最后截断为整数。即使端点固定或关卡超出区间,两次抽取仍然发生。重建设计器序列时,需要保留这些随机数消耗。0x101ab6028

摇滚年代:安排音乐并替换指令

DangerRoomJamDesigner 在普通填充之后运行,使用全局 RNG。它在战斗创建这些僵尸之前,就将音乐事件准备到波次属性中。

先选择音乐集合

第 7 关之前,候选为 Pop、Punk、Rap 和 8-bit。Metal 从第 7 关加入。所需的不同音乐数量从第 1 关的 2 插值到第 15 关的 5,并受合格候选数量限制。设计器重复抽取候选,直到拥有足够多的不同类型。重复结果被丢弃,但仍消耗该次随机抽取。

之后,每个事件都从这个完整子集中抽取。连续事件可以选择相同音乐。 子集选择不重复,不代表事件序列也不重复。jam_ballad 出现在更广泛的音乐列表中,但不属于这份无尽候选列表。

再选择事件波次

音乐安排独立于第 1 章中的旗帜间隔。令 wjw_j 表示第 jj 个事件从零开始的波次索引:

w0=Ifirst(L),wj+1=wj+Igap(L).w_0=I_{\mathrm{first}}(L),\qquad w_{j+1}=w_j+I_{\mathrm{gap}}(L).

每个 II 都是一次新的端点抽取插值,使用关卡边界 1–15。在 wj<N(L)w_j<N(L) 时继续添加事件。

参数第 1 关端点第 15 关及以后端点
不同音乐类型数量25
首次事件,从零开始的波次索引3–41–2
后续事件间隔,单位为波4–52–3

因此,低端点的首个事件位于玩家视角第 4–5 波,高端点则位于第 2–3 波。事件间隔按波计数,不按秒;每个事件之后的间隔都会重新抽取。

一个事件改变什么

对于一次事件,设计器从下表选择一种替换类型。Rap 为整个事件选择一次,而不是为每条被替换指令单独选择。

通知事件替换类型
jam_popeighties_glitter
jam_punkeighties_punk
jam_rapeighties_mc 或 eighties_breakdancer
jam_8biteighties_arcade
jam_metaleighties_gargantuar_danger

替换数量使用独立的关卡范围 1–50,端点范围为 1–2 和 5–7。结果不超过当前指令数。每次移除时,设计器从剩余索引中选择一个,保存该指令的等级,将最后一条指令移入其槽位,再缩短向量。之后,使用保存的等级,追加选定替换类型的新指令。

只有等级被带入新指令。新指令的其他字段采用默认值,包括为假的首领标记。未选中指令保留自身字段,但交换移除可以改变其顺序。最终数量与等级多重集保持不变,总成本和首领标记则可能变化。流程不重新执行普通预算检查,因此 Metal 可以加入巨人,即使替换成本超过原始额度。

事件名被追加到 NotificationEvents。运行时,动作先创建并放置整批实体,再发出这些通知。替换早已在生成阶段决定;通知送达,才是音乐事件之后进入 Board 的时点。22Jam 在创建前替换指令DangerRoomJamDesigner 通过移除和追加替换指令来修改生成属性向量。每个事件选择一种替换类型,保留被替换指令的等级与条目总数,不重新执行普通预算检查。AdditionalPlantfood 不变。WaveManager 的生命值采样在最终波次动作创建实体之后执行。0x101c79bec0x101ce76d8

本关各波与音乐事件

12Metal345Rap67Metal89

事件之前

  1. 摇滚铁桶僵尸L6
  2. 摇滚僵尸L5
  3. 摇滚铁桶僵尸L6
  4. 摇滚僵尸L6
  5. 摇滚僵尸L61
  6. 摇滚僵尸L64
  7. 摇滚铁桶僵尸L6
  8. 摇滚小鬼僵尸L5
  9. 街机僵尸L62
  10. 说唱僵尸L6
  11. 摇滚铁桶僵尸L6
  12. 摇滚僵尸L63

交换移除之后

  1. 摇滚铁桶僵尸L6
  2. 摇滚僵尸L5
  3. 摇滚铁桶僵尸L6
  4. 摇滚僵尸L6
  5. 说唱僵尸L6
  6. 摇滚铁桶僵尸L6
  7. 摇滚铁桶僵尸L6
  8. 摇滚小鬼僵尸L5

追加之后

  1. 摇滚铁桶僵尸L6
  2. 摇滚僵尸L5
  3. 摇滚铁桶僵尸L6
  4. 摇滚僵尸L6
  5. 说唱僵尸L6
  6. 摇滚铁桶僵尸L6
  7. 摇滚铁桶僵尸L6
  8. 摇滚小鬼僵尸L5
  9. 说唱僵尸L6
  10. 说唱僵尸L6
  11. 说唱僵尸L6
  12. 说唱僵尸L6
图版 3.1摇滚年代第 49 关,示例种子 3:设计器选择了 8-bit、Metal、Punk、Rap、Pop,在 9 波的安排中放置了 3 个事件。事件 2 在第 5 波播放 Rap,把该波 12 条指令中的 4 条替换为 说唱僵尸:每次移除都把最后一条指令移入空出的位置,新指令沿用被移除指令的等级。

AdditionalPlantfood 保持不变。其携带者请求之后才根据修改后的顺序和类型分配。一般掉落分配也在之后运行,使用替换后的成本。最后,波次生命值快照在位置处理后测量最终符合条件的实体。这是同一个替换流程对三个后续环节产生的不同影响。

报告的快照还存在整数上限。替换可能改变实际合格 HP,而已经饱和的报告值仍保持不变。

恐龙危机:在阵容之外追加事件

候选类型为 raptor、stego、ptero、tyranno 和 ankylo。设计器通过重复抽取不同类型形成子集,再为每个恐龙事件选择一种类型。端点插值使用关卡边界 1–20。

参数第 1 关端点第 20 关及以后端点
选中的恐龙类型25
首次事件,从零开始的波次索引3–42–3
后续事件间隔,单位为波3–41–2
事件中的恐龙数量1–23–4

首次索引与后续间隔共同定义事件序列,方式与上文相同。事件发生的波次中,请求数量的恐龙动作被追加到普通僵尸动作旁。行号从 0–4 的可消耗列表中抽取;若剩余条目不足以供下一个事件使用,就补充列表。这些选择消耗全局 RNG,不花费普通僵尸预算。33独立的恐龙动作设计器选择类型子集,并向波次属性添加恐龙动作。事件数量、首波索引与间隔使用共享的端点抽取插值函数。每个事件选择一种类型;行号从可消耗的向量中抽取,需要时补充。这些动作在普通僵尸预算填充之外添加。0x10172986c

摩登世界:事件创建带有独立队列的传送门

回退候选包含十个家族:摇滚年代、恐龙危机、失落之城、冰河世界、巨浪沙滩、黑暗时代、未来世界、狂野西部、古埃及和海盗港湾。它们的初始选择权重均为 1.0。设计器不放回地选择家族,子集大小从第 1 关的 1 增长到第 30 关的 4。每个传送门事件波从这个子集中选择一个家族。

事件参数第 1 关端点第 30 关及以后端点
首次事件,从零开始的波次索引42–3
后续事件间隔,单位为波42
一次事件中的传送门数量13–4
生成列,从零开始85–6
计算出的每门僵尸数量1–23–4
计算出的列表起始索引00
计算出的列表结束索引03

行号从 0–4 中不放回抽取,并按需补充。家族与行选择使用传入的设计器 RNG;端点插值消耗全局 RNG。

最后三个计算值没有被普通 SpawnModernPortalsWaveAction 传递。它只传递传送门家族和位置,让运行时数量/起始/结束覆盖值保持默认。这些被丢弃的计算仍消耗了随机抽取。

因此,传送门复制家族的完整四槽列表,再通过导入的 C++ RNG 机制打乱。重复名称仍然是独立槽位。古埃及的源列表为 ra、explorer、explorer、pharaoh:包含两个 Explorer 槽位,而非一个。下方视图可以检查全部十个家族。

开启、释放与关闭

首次请求截止时间为创建时刻加上随机偏移;该偏移在零与一个从 10–12 秒区间抽出的值之间抽取。释放还要等待开启完成。每个后续间隔加在前一次计划截止时间上,而不是简单地加在延迟发生的实际释放时刻上。

每个释放出的子僵尸获得波次标记 −5-5。它不计入普通当前波次生命值,也不获得初始场外加速,但仍参与独立的胜利和失败谓词。其请求等级边界来自共享 DangerRoom 曲线,但使用全局浮点抽取进行选择,而非普通指令的整数余数比较。

最后一个槽位释放后,传送门仍会安排下一次到期检查。之后的检查发现空队列,才开始关闭;关闭动画回调随后请求移除。在移除之前,传送门自身仍是完成阻塞因素,与存活的子僵尸相互独立。44传送门动作的实际载荷设计器计算数量与列表范围值,但普通传送门动作并未将它们传给创建的网格物件。其创建后辅助函数直接返回。运行时初始化复制配置中的完整家族列表,保留重复槽位,然后打乱。释放时创建的子僵尸带有波次标记 -5;默认间隔边界为 10 和 12 游戏秒。0x100739c580x101b044480x100120f800x100121a00

各家族的四个槽位;选择其一

  • 古埃及太阳神僵尸探险家僵尸探险家僵尸法老僵尸
  • 海盗港湾海鸥僵尸海盗船长僵尸海盗船长僵尸滚桶僵尸
  • 狂野西部斗篷僵尸鸡贼僵尸斗篷僵尸钢琴僵尸
  • 未来世界喷射器僵尸电磁盾僵尸喷射器僵尸机甲路障僵尸
  • 黑暗时代小丑僵尸魔法师僵尸小丑僵尸骑士僵尸
  • 巨浪沙滩浮潜僵尸冲浪僵尸冲浪僵尸章鱼僵尸
  • 冰河世界渡渡鸟骑士僵尸冰鼬僵尸渡渡鸟骑士僵尸冰砖僵尸
  • 失落之城失落昆虫僵尸失落发掘者僵尸失落昆虫僵尸失落考古学家
  • 摇滚年代eighties_8bit_armor1录音机僵尸eighties_8bit_armor2录音机僵尸
  • 恐龙危机侏罗纪小鬼僵尸侏罗纪野人僵尸侏罗纪小鬼僵尸侏罗纪龙骨化石僵尸

打乱后的队列,附各项原槽位

  1. 1探险家僵尸槽位 3
  2. 2太阳神僵尸槽位 1
  3. 3探险家僵尸槽位 2
  4. 4法老僵尸槽位 4
安排的截止时间
D1 · 3D2 · 14D3 · 25D4 · 36
传送门
已开启探险家僵尸 · 5太阳神僵尸 · 14探险家僵尸 · 25法老僵尸 · 36队列已空:关闭
图版 3.2古埃及传送门:列表从库引擎位置 2 打乱,得到上方的队列。首个截止时间为 3 秒,开启在 5 秒完成,之后每个截止时间都在前一个截止时间上加 11 秒,而不是加在释放时刻上:子僵尸在 5、14、25、36 秒离开,47 秒的到期检查发现队列已空,开始关闭。

蒸汽时代:固定场地系统与独立时钟

蒸汽时代使用更晚的设置钩子。令 α=clamp⁡((L−1)/99,0,1)\alpha=\operatorname{clamp}((L-1)/99,0,1),其数量为

nsmoke=⌊3α⌋,npipe=⌊5α⌋.n_{\mathrm{smoke}}=\lfloor3\alpha\rfloor,\qquad n_{\mathrm{pipe}}=\lfloor5\alpha\rfloor.

烟雾孔数量在第 34、67、100 关首次达到 1、2、3;管道数量在第 21、41、61、81、100 关达到 1–5。位置固定:玩家视角中,烟雾位于第 6 列,从第 2 行起;管道从第 7 列通往第 5 列,从第 1 行起。

首次烟雾启动参数从 15 插值到 30 秒。烟雾伤害与管道堵塞伤害都从 30 插值到 100,使用管道命名的伤害字段。烟雾启动调用使用字段的顺序,与 Min/Max 名称暗示的顺序相反。

烟雾等待事件

开始调用时,首次预警截止时间为 t+3t+3;首次喷发截止时间则独立设为 t+StartTime⁡t+\operatorname{StartTime}。喷发动画完成才激活烟雾。活动期间,累计值严格大于 1 秒时执行一次伤害检查,再减去 1 秒。检查在烟雾孔所在格及周围八格中,各选择一个植物目标,而非独立伤害每一个叠放层。

活动烟雾不会自动在五秒后到期。必须先完成 blow_off 或 absorb 等清除路径。其移除事件开始冷却:下次预警为清除时刻 + 5 秒,下次喷发再晚 3 秒,尚未计入额外延迟。所检查的移除事件没有携带所属烟雾孔参数,而是广播给已注册的烟雾孔处理器。

管道保留同一批实体

进入要求实体位于入口格、X 坐标与入口距离严格小于 0.5、运行时体型为 small,并通过包含 CanBeLaunchedByPlants 在内的共享能力/状态检查。蒸汽时代矿工为 mid,巨人为 big,因此不能通过这个体型条件。

普通路径的两列距离产生 2 秒时长参数,但实体截止时间分别为 t+1t+1、t+2t+2 和 t+3t+3。它从地下阶段截止时间起使用出口位置,只有严格超过最终截止时间才完成。多个僵尸可以同时处于运输中;这并非一次只服务一个对象的队列。

作为阻挡植物的平顶菇(flattenedshroom)可以延长地下与完成截止时间;其随包 BlockTime 为 12 秒。只要仍有运输对象引用,每次更新就对每株阻挡植物施加一次 DamagePerSecond 乘以游戏时间增量的伤害,不按运输僵尸数量重复施加。运输保留原等级与波次标记,基础当前波次生命值查询也不排除 ZS_StuckInGround 状态。55蒸汽时代的设置与运行时钟较晚的设置钩子根据限制后的关卡比例计算烟雾孔与管道数量。烟雾使用清除触发的冷却,以及对相邻格子的伤害更新。管道的时长参数与状态截止时间不同:基线参数为 2,截止时间则为 t+1、t+2、t+3,最后一次转换使用严格比较。阻挡植物受到的伤害按每株、每次更新施加。0x100b980c00x101ec8840

草坪,按玩家视角计列与行

1
管道,第 7 列入口,第 5 列出口
2
烟雾孔管道,第 7 列入口,第 5 列出口
3
烟雾孔管道,第 7 列入口,第 5 列出口
4
5
一次管道行程
出口 · 2地下完成 · 3
图版 3.3蒸汽时代第 67 关,α = 0.667:第 6 列有 2 个烟雾孔,第 7 列通往第 5 列有 3 条管道;首次烟雾启动为 25 秒,烟雾与堵塞伤害为 76。下方是一个小型僵尸在 0 秒进入管道:自 1 秒起位于地下,2 秒到达出口,严格超过 3 秒后完成。

注释

每条论断的依据:代码路径,以及追踪到它的原生地址。

  1. 1

    先抽取,再插值

    辅助函数进行两次包含边界的整数抽取,限制关卡比例,使用 float32 融合乘加对抽取的端点插值,最后截断为整数。即使端点固定或关卡超出区间,两次抽取仍然发生。重建设计器序列时,需要保留这些随机数消耗。

    0x101ab6028
  2. 2

    Jam 在创建前替换指令

    DangerRoomJamDesigner 通过移除和追加替换指令来修改生成属性向量。每个事件选择一种替换类型,保留被替换指令的等级与条目总数,不重新执行普通预算检查。AdditionalPlantfood 不变。WaveManager 的生命值采样在最终波次动作创建实体之后执行。

    0x101c79bec0x101ce76d8
  3. 3

    独立的恐龙动作

    设计器选择类型子集,并向波次属性添加恐龙动作。事件数量、首波索引与间隔使用共享的端点抽取插值函数。每个事件选择一种类型;行号从可消耗的向量中抽取,需要时补充。这些动作在普通僵尸预算填充之外添加。

    0x10172986c
  4. 4

    传送门动作的实际载荷

    设计器计算数量与列表范围值,但普通传送门动作并未将它们传给创建的网格物件。其创建后辅助函数直接返回。运行时初始化复制配置中的完整家族列表,保留重复槽位,然后打乱。释放时创建的子僵尸带有波次标记 -5;默认间隔边界为 10 和 12 游戏秒。

    0x100739c580x101b044480x100120f800x100121a00
  5. 5

    蒸汽时代的设置与运行时钟

    较晚的设置钩子根据限制后的关卡比例计算烟雾孔与管道数量。烟雾使用清除触发的冷却,以及对相邻格子的伤害更新。管道的时长参数与状态截止时间不同:基线参数为 2,截止时间则为 t+1、t+2、t+3,最后一次转换使用严格比较。阻挡植物受到的伤害按每株、每次更新施加。

    0x100b980c00x101ec8840