定义天劫
天劫是突破的代价:一场按时间线消费的考验,扛过去就是下一个境界,扛不过去由你自己写的失败行为收场。它由两部分拼成——一场天劫的定义,以及某次突破引用它。触发它的从来不是天劫自己。
本篇给示例包的筑基阶段挂上一场「雷劫」。
你要搭建什么
| 文件 | 用途 |
|---|---|
data/example/mxt/tribulation/heavenly_gate.json | 这场天劫:前摇、时间线、成功与失败。 |
data/example/mxt/realm_stage/foundation.json | (编辑) 把这场天劫挂到这条突破上。 |
第 1 步 —— 一条最小的时间线
// data/example/mxt/tribulation/heavenly_gate.json
{
"windup": 60,
"timeline": [
{"type": "mxt:idle", "duration": 20},
{"type": "mxt:action", "action": {"type": "mxt:spawn_lightning", "damage": 6}},
{"type": "mxt:idle", "duration": 40},
{"type": "mxt:action", "action": {"type": "mxt:spawn_lightning", "damage": 10}}
],
"success_action": {"type": "mxt:add_resource", "resource": "example:qi", "amount": 50},
"fail_action": {"type": "mxt:damage", "amount": 20}
}| 字段 | 作用 |
|---|---|
timeline | 必填,天劫要复制进附件、再用游标逐拍走完的时间线,至少一拍。 |
windup | 启动前摇:第一拍开始之前先空转的 tick 数。 |
success_action | 时间线走空之后执行,默认 mxt:no_op。 |
fail_action | 某一拍报错、天劫无法继续时执行,默认 mxt:no_op。 |
节拍有四种,写一场天劫就是把它们排成一队:
type | 字段 | 说明 |
|---|---|---|
mxt:action | action(EntityAction,必填) | 执行一次行为,并在同一个 tick 内结束。相邻的几个 mxt:action 会挤在同一 tick 里依次跑完,所以「一 tick 里发生的一串事」不需要拆成多拍。 |
mxt:idle | duration(NumberProvider,必填) | 空等若干 tick。 |
mxt:wait_for | condition(EntityCondition,必填)、timeout(ticks,可选)、on_timeout(fail 默认 / finish,可选) | 每 tick 求值一次,条件成立才结束这一拍;写了 timeout 就是限期等待,到点按 on_timeout 失败或直接过。 |
mxt:branch | condition(EntityCondition,必填)、if_true / if_false(绝对下标,可选) | 按条件把运行游标移到另一拍,并在同一个 tick 结束;不写的分支照常前进一拍。 |
时间线是运行游标:启动时整条被复制进实体的附件,之后每 tick 消费游标所在那一拍,跑完就前进一拍。游标是存下来的(mxt:branch 可以把它移到任意一拍,也可以回跳),所以 /reload 改动定义不会影响一场已经在进行的天劫——时间线已经拷走了;而难度倍率与成败行为仍然从定义实时读取。if_true / if_false 的下标从这条被复制的时间线的第一拍算起(0 起),越界会在启动时被拒绝。
mxt:wait_for 可以限期
不写 timeout 时,条件永不成立,天劫就永远停在这一拍:不推进,也不失败——作者要保证条件可达(例如用 mxt:exposed_to_sky 这种迟早会真的条件)。
写了 timeout(ticks)就是限期:到点按 on_timeout 走——fail(默认)判失败,finish 直接过了这一拍。它是限期而不是这一拍的时长,所以只按 timeout 结算,不乘 difficulty_scale、也不看环境灵气:难度不该决定玩家有多少时间达标。timeout 解不出正数时整场启动被拒绝。
第 2 步 —— 加入前摇等待
mxt:idle 与 windup 都按同一条式子换算:
时长 × difficulty_scale × max(0, 1 + aura_tribulation_modifier)difficulty_scale默认1,是整场天劫的总倍率。aura_tribulation_modifier是环境给的,取所在位置灵气区的rules.tribulation_modify(默认0):灵气越浓,劫数越难熬。它是这条式子里唯一的公式变量,乘数下限是max(0, …)——修正小于-1时乘数变成0,时长解不出正数,整场启动被拒,而不会算成负时长。- 换算只在节拍开始时做一次:随机时长只掷一次,环境灵气中途变化也不会把一个已经开始的等待拉长或缩短。玩家看到的倒计时因此是真正会走完的 tick 数。
启动前每一拍都会被问一次「现在能不能跑」,mxt:idle 的时长解不出来、mxt:wait_for 的 timeout 解不出来、mxt:branch 的下标越界时,整场启动直接被拒——坏定义不该在玩家已经付掉突破代价之后才出问题。
第 3 步 —— 分支为两条支线
mxt:branch 让时间线不必是直线:它在同一个 tick 里把游标搬到 if_true 或 if_false 指定的那一拍(绝对下标,0 起),两个都不写就照常前进一拍。下面这段先是限期等玩家站到天空下(timeout: 600 刻内没做到就判失败),然后按血量分流——血少的人被 if_true: 3 直接送到第 4 拍,让过第 3 拍那道较轻的雷:
"timeline": [
{"type": "mxt:wait_for", "condition": {"type": "mxt:exposed_to_sky"}, "timeout": 600, "on_timeout": "fail"},
{"type": "mxt:branch", "condition": {"type": "mxt:health", "comparison": "<", "compare_to": 10}, "if_true": 3},
{"type": "mxt:action", "action": {"type": "mxt:spawn_lightning", "damage": 4}},
{"type": "mxt:action", "action": {"type": "mxt:spawn_lightning", "damage": 12}}
]下标越界在启动时就被拒绝,所以跳错位置的分支不会等到玩家付掉突破代价才出问题;回跳也允许(做一个「血量低就回去再挨一轮」的循环没有限制,条件要靠自己收得住)。
前摇与等待的差别只在于前摇不消费节拍、不写现场,但这场天劫已经算在进行:重复启动照样被拒,/mxt tribulation status 报「还在前摇,还剩 N tick」,darken_sky 也照常生效。剩余 tick 跟着附件一起存档、一起同步,所以退出重进是接着倒计时。
第 4 步 —— 降下雷击
mxt:spawn_lightning 是一个普通 EntityAction:除了颜色和粗细,它就是原版闪电——伤害、引燃、避雷针充能、铜氧化、雷声与天空闪光,以及村民→女巫、猪→僵尸猪灵、苦力怕充能这些转化全部照旧。所以它能用在任何 EntityAction 槽位里,不只是天劫。
| 字段 | 类型 | 默认 | 说明 |
|---|---|---|---|
damage | NumberProvider | 5 | 雷击伤害,默认与原版一致。 |
color | 颜色 | #737380 | 雷的 RGB 颜色,写 #RRGGBB(整数或 [r,g,b] 浮点数组也接受),一律按不透明处理。默认即原版那身冷白。 |
palette | 颜色[] | [] | 渐变:从天空侧到落地点,最多 16 项;给了它就替换 color。 |
alpha | Float 0..1 | 0.3 | 雷的亮度,不是透明度:原版闪电用加法混合,RGB × alpha 就是发光强度,调高更刺眼。 |
thickness | Float 0.1..4 | 1 | 雷柱粗细倍率。 |
visual_only | Boolean | false | 只打雷,不结算伤害、不引燃——做氛围用。 |
cause | Boolean | true | 施法者是玩家时记为其引发的雷击(可触发原版 channeled_lightning 进度)。 |
offset_x / offset_y / offset_z | NumberProvider | 0 | 落点相对施法者的偏移。 |
渐变色写错或超过 16 项会在解码期直接失败,而不是被静默丢弃;palette 的每一项之间线性过渡,四个叠加层与两条分叉读同一组接缝,所以分叉在离开主干的高度上颜色与主干一致。
把第二道雷换成渐变,就能一眼看出「这一击比上一击重」:
{"type": "mxt:action", "action": {
"type": "mxt:spawn_lightning",
"palette": ["#7A5CFF", "#66CCFF"],
"alpha": 0.45,
"thickness": 1.6,
"damage": 10
}}第 5 步 —— 结算成功与失败
- 成功:游标走出时间线末尾即执行
success_action。上面写的是补 50 点灵气——扛过雷劫,池子反而更满。 - 失败:某一拍判定无法继续(
mxt:idle的现场丢了、节拍自己返回失败、mxt:wait_for的timeout到点且on_timeout是fail)时执行fail_action。劫数没有任何内置惩罚——不掉境界、不扣修为,全部由你写在fail_action里。 - 死亡不是失败:天劫附件是
copyOnDeath()的,所以死亡不会清掉天劫,重生之后接着跑(表现为死亡之后天劫仍在继续)。想让它随死亡结束,就要在fail_action之外另做安排,或者把惩罚写成"死亡即判失败"。 /mxt tribulation stop也不结算:它只清场,success_action与fail_action都不跑——调试时清楚这一点就不会以为"停掉也算成功"。- 两者都可以什么都不写(
mxt:no_op)——那时天劫只是一段演出,惩罚留给别的系统。
想把惩罚做成「渡不过就掉境界」,用现成的行为组合即可,例如 fail_action 里先 mxt:damage 再 mxt:add_resource 扣修为:
"fail_action": {
"type": "mxt:sequence",
"actions": [
{"type": "mxt:damage", "amount": 20},
{"type": "mxt:add_resource", "resource": "example:qi", "amount": -500}
]
}第 6 步 —— 挂接到突破
天劫不会自己开始。决定「要不要开一场天劫」的是引用它的地方,最常见的就是境界阶段:
// data/example/mxt/realm_stage/foundation.json
"tribulation": "example:heavenly_gate"再补一道启动闸门——condition 在天劫被启动的那一刻求值一次,不通过则本次不启动:
// data/example/mxt/tribulation/heavenly_gate.json
"condition": {"type": "mxt:aura_range", "min": 30, "max": 1000}于是「灵气太薄的地方渡不了劫」这句话就成了数据包的一条规则,而不是代码里的特例。闸门不成立时突破照常发生,只是没有天劫——想让「必须有劫」也由 realm_stage 的 breakthrough.conditions 去要求。
在游戏里验证
先退回标题界面重开世界(数据包注册表在世界加载时读取,/reload 不会重读),然后:
/mxt registries validate
/mxt tribulation start example:heavenly_gate
/mxt tribulation status
/mxt tribulation stopvalidate应报告没有 Codec 错误。start会立刻进入 60 tick 前摇:action bar 上出现「天劫将至:3.0 秒」逐秒递减,天空同时开始变暗(约 1 秒淡入)。前摇一过,第一拍
mxt:idle 20开始;status这时报「第 1 拍」并打出这一拍的现场,例如{"remaining":12,"type":"mxt:idle_countdown"}。调时序时这是最直接的观测口。两道雷落下,
success_action给你 50 点灵气,天空慢慢转亮。想不想登录也能试,就对手边的一只实体下手(天劫挂在活体实体上、由它的 tick 推进):
text/execute positioned 0 100 0 run mxt tribulation start example:heavenly_gate @e[tag=probe,limit=1] /execute positioned 0 100 0 run mxt tribulation status @e[tag=probe,limit=1]最后走一次真实流程:
/mxt resource example:qi set 500把池子填够,/mxt breakthrough example:qi触发突破——应该看到和前摇完全一样的过程。
常见错误
| 现象 | 原因 |
|---|---|
| 突破时什么都不发生 | realm_stage 上没有写 tribulation,或者天劫的 condition 不成立。 |
start 报「已有天劫在进行」 | 这个实体身上已经挂着一场,包括还在前摇的那场;stop 清掉再来。 |
start 报「某个节拍现在跑不了」 | 启动前的逐拍校验失败,最常见的是 mxt:idle 的 duration 解不出来(公式用了空上下文里没有的变量),也可以是 mxt:wait_for 的 timeout 解不出正数、mxt:branch 的 if_true / if_false 超出时间线范围。 |
| 天劫永远停在某一拍 | 那一拍的 mxt:wait_for 条件不可达,而且没有写 timeout——不推进也不判失败;补一个 timeout(配合 on_timeout)就能给它一个尽头。 |
mxt:branch 跳错了地方 | 下标是绝对的(从这条被复制的时间线的第 1 拍算起、0 起),不是相对当前拍的偏移;越界的值在启动时就被拒绝。 |
| 天劫忽然不动了 | 推进靠实体 tick,所以它在未加载的区块里会暂停(附件已存档),区块重新加载后从中断处继续。 |
difficulty_scale 写了 0 或负数 | 加载期不报错,但等待时长会算出 -1:时间线里只要有 mxt:idle,启动就会被拒(invalid_entry);只有 mxt:action 的时间线则会"看起来成功"地瞬间跑完。 |
公式里写裸 level 得到 0 | 天劫公式跑在实体上下文里,level/realm_rank 这类需要资源上下文的变量在这里读不到。要用施法者的原版经验等级就写 caster_level。 |
| 时间线改了但进行中的天劫没变 | 时间线在启动时就复制进了附件,/reload 只影响下一场。 |
用 mxt:modify_ability_storage 写 mxt:entry_began / mxt:idle_countdown 什么也没发生 | 这两个种类归天劫自己的时间线状态用,没有任何技能类型声明它们,所以 mxt:modify_ability_storage 写不进去,日志里只有一条 WARN。 |
| 天空不变暗 | darken_sky 写了 false,或者附近没有正在渡劫的玩家——变暗与倒计时都读玩家身上那份附件:自己的天劫永远算,别人的只在 160 格内算,挂在生物身上的天劫不参与。 |
接下来
- tribulation(天劫) —— 完整字段表与时间线细节。
- realm_stage(境界阶段) ——
tribulation与突破条件。 - 实体行为 —— 成败行为能用的全部动作,包括
mxt:spawn_lightning。 - 命令 ——
/mxt tribulation与/mxt lightning的其余参数。