定义技能
ability 定义一个通用的技能,可以用于玩家或者法器的主动技能、被动技能、触发技能、定向施放、持续施法、间隔施法、灵气效果、储物格子、飞行开关等。它们的行为由动作字段定义,消耗与条件由声明字段定义。
本篇教程给示例包加上两条技能:
- 放上轮盘施放的主动灵力弹;
- 对伤害做出反应的触发恢复。
你要搭建什么
| 文件 | 用途 |
|---|---|
data/example/saturn/ability/qi_bolt.json | 主动灵力弹:带消耗、冷却与条件,按下时跑自己的四个动作字段。 |
data/example/saturn/ability/qi_recovery.json | 触发恢复:对受伤做出反应,命中时跑自己的动作字段。 |
data/example/mxt/realm_stage/foundation.json | (编辑) 突破时授予这发灵力弹。 |
data/example/mxt/item_binding/root_pellet.json | (编辑) 同时授予恢复技能。 |
data/example/mxt/technique/azure_breath.json | (编辑) 学会后同时授予两者。 |
第 1 步 —— 一个主动技能
// data/example/saturn/ability/qi_bolt.json
{
"type": "mxt:active",
"icon": "example:textures/gui/ability/qi_bolt.png",
"costs": [
{"type": "mxt:resource", "resource": "example:qi", "amount": 10}
],
"cast_time": 10,
"cooldown": 40,
"condition": {"type": "mxt:has_realm", "aura": "example:qi"},
"target_selector": {"type": "mxt:area", "radius": 6, "include_actor": false},
"bi_entity_action": {
"type": "mxt:target_action",
"action": {"type": "mxt:damage", "amount": "6 + caster_level * 0.5"}
}
}
这条技能读到的字段(cooldown 与下面那四个动作字段都不是通用字段——前者只有会付款的类型读,后者只有会跑动作的类型才声明):
| 字段 | 作用 |
|---|---|
type | 从内置的 ability_type 注册表中选择生命周期,详见技能类型。 |
icon | 可选。裸字符串是 16x16 的 GUI 贴图;对象是物品堆模板({"id": ...},可带 count 与 components)。不写时该条目用名字绘制。 |
costs | Cost 对象列表,在行为执行前支付,整份数组全有或全无。四种条目与 {"id": ..., "amount": ...} 简写见消耗类型。 |
cast_time | 施法时长,单位 tick。 |
cooldown | 冷却时间,单位 tick,会回报给客户端,让轮盘在那一扇的图标上压一层冷却白幕并在中间写「冷却中 4.3s」(剩余秒数,永远一位小数)。 |
condition | 技能可用前必须满足的实体条件。它在这里的唯一职责是不让凡人丢出灵力弹。对 mxt:modifier 或 mxt:aura 这类被动技能,同一字段在施放之后仍然生效:每 tick 重算一次,不满足时被动效果被撤下。 |
mxt:active 自己还会声明这四个动作字段——它们不是技能的通用字段,只有会跑动作的类型才声明它们:
| 字段 | 作用 |
|---|---|
target_selector | 双实体行为作用于哪些实体,默认 mxt:self 只选施法者;各选择器的形状、limit 与 order 见技能目标选择器。例子用的是 mxt:area——方盒不是球,include_actor 决定施法者是否算在集合里。 |
bi_entity_action | 对每个选中的目标执行,某一个失败不会阻止其余目标。mxt:target_action 把一个实体行为转交给目标——这里是 6 点伤害加上施法者经验等级的一半。 |
entity_action | 对施法者执行。默认为 mxt:no_op;用于自我增益、粒子爆发或灵气变化。 |
target_condition | 每个目标都要过的双实体条件,默认为 mxt:always。 |
四个动作字段的执行顺序永远是:entity_action(先跑)→ target_selector 取目标 → 每个目标过 target_condition → 通过才跑 bi_entity_action。完整的按类型时机表见 技能类型 · 四个动作字段。
还有几个字段值得了解:
charges是唯一留在定义上的状态参数:写{"maximum": ..., "recharge_ticks": ...}(两个都必填)就让这条技能按次数花——每次付款扣一次,扣到 0 就拒付(失败原因NO_CHARGES),剩下的次数是状态而不是声明。只有走施放管线的付款扣它;走共用闸门的按键(mxt:flight_control/mxt:storage)不扣充能。能存哪些状态种类由type在代码里声明,不用也不该再写components(写了与未知键一样被静默忽略);六种状态与读取它们的条件见技能施放。- 元素亲和不写在这条技能上:它是自己的一张数据包注册表,见 ability_affinity。在那边点名之后,它既是施放门槛,也会并进公式变量
damage_multiplier,所以写伤害数字时不用、也不该自己乘亲和倍率。 hidden只在法器的提示框里被跳过,照常生效、照常授予;它不参与轮盘那道筛选(轮盘池筛的是按键型),所以写了它一条按键技能照样占轮盘。
技能公式运行在实体上下文中。它提供 caster_health、caster_max_health、caster_level(原版经验等级)、caster_<resource> 和 caster_<attribute>——但没有 realm_rank,后者只存在于按某个数值求值的公式里。技能要随施法者灵气缩放时,用 caster_example_qi。
第 2 步 —— 一个触发技能
触发技能在世界对它的持有者做了某件事时发动。触发器还会注入几个描述刚发生了什么事的变量。它自己那部分是"什么信号、多大概率",跑什么就写在自己的动作字段上:
// data/example/saturn/ability/qi_recovery.json
{
"type": "mxt:triggered",
"triggers": [{"type": "mxt:hurt"}],
"chance": 1,
"cooldown": 100,
"condition": {"type": "mxt:has_realm", "aura": "example:qi"},
"costs": [
{"type": "mxt:resource", "resource": "example:qi", "amount": 5}
],
"entity_action": {"type": "mxt:heal", "amount": "2 + damage * 0.5"}
}
triggers是内置匹配器的列表,每项只写类型 id、不带自己的字段;全部条目见触发器。chance是数值提供器,默认1,每命中一次触发器掷一次:公式抛异常或算不出有限值时这一次不放行,≤0不放行,≥1必放行,中间值按实体随机数掷。别与saturn/trigger规则表里同名的chance混起来——那边算不出数按1处理。hurt触发器注入damage——实际造成的伤害——所以治疗量可以随这一击缩放;其他触发器注入的变量同样能被这条技能自己的动作字段读到,注入表见公式变量。
完整的变量表见公式变量。
第 3 步 —— 授予技能
定义技能本身不起任何作用:得有实体持有它。mxt:grant_ability 实体行为负责这件事,它的 source 字段记录是谁授予的。
从境界授予。 编辑玩家到达的那个境界:
// data/example/mxt/realm_stage/foundation.json
"success_action": {
"type": "mxt:grant_ability",
"ability": "example:qi_bolt",
"source": "example:foundation"
}
success_action 在玩家刚进入的那个阶段上运行,所以到达筑基就会教会这发灵力弹。阶段上的 ability_requirements 是它的镜像:列出突破前必须已经持有的技能。
从物品授予。 把行为写进 item_binding 注册表里那条认领该物品的定义:
// data/example/mxt/item_binding/root_pellet.json
{
"items": "kubejs:root_pellet",
"actions": [
{"type": "mxt:grant_spirit_root", "spirit_root": "example:fire_root"},
{"type": "mxt:grant_ability", "ability": "example:qi_recovery", "source": "example:root_pellet"}
]
}
从灵根、体质或功法授予。 那些定义有一个 granted_abilities 列表,在它们被持有期间生效:
// data/example/mxt/technique/azure_breath.json
"granted_abilities": ["example:qi_bolt", "example:qi_recovery"]
让 source 保持稳定且有意义——用授予该技能的定义 ID 就是好选择。来自不同来源的技能分开记账,来源也是之后撤销时能被追溯的依据,因此两件物品可以授予同一个技能,而不会有一件悄悄把另一件的那份撤掉。
第 4 步 —— 将技能放入轮盘并使用
主动技能可以放进 12 扇轮盘(技能与灵气共用的那个,主盘 + 随装备出现的从盘):
- 聊天栏打
/wheel(客户端命令,等同于按键「打开轮盘配置」,默认未绑定)打开轮盘配置界面:左边 6 列是能发射的灵气,右边 6 列是学到的技能与 Curios 槽位上法器声明的技能(只由主手 / 副手物品声明的技能不在这里,它们只在随装备出现的从盘上),下面一排 12 格就是主盘的 12 格(1在正上方,顺时针)。左键点右边池子里的灵力弹选中它,再点某一格放进去;Esc保存并关闭。你刻意留空的格子会保持为空;已保存但定义已不存在的格子会显示红色?,不会被别的条目顶替。 - 按住「轮盘选择」(默认
R)让指针指向那一格——指针决定选中哪一格,而选中永远不会空着:轮盘打开时就已经选中第一个有内容的格子(金框一开始就画在那里),指针停在空格子上不会改变已有选择。松开R只关掉轮盘,不会施放。按「轮盘使用」(默认V)才施放,而且轮盘不关,可以接着换一格再按;轮盘关着时按V会施放你上次选的那个编号此刻代表的那一格(页没了就落到最后一个有东西的格子,编号本身不改),鼠标左键等同于V。屏幕左侧那块「轮盘格」是四列、行数随页数往下长的整张轮盘一览,其中金色边框那一格就是"现在按V会放什么"。 - 一切都是服务端权威:客户端只发送"用了哪一类的哪个 id",授予、条件、消耗、冷却、时长与效果都由服务端决定。
在游戏里验证
技能是数据包注册表,所以要重新加载世界,而不是执行 /reload:
(重新打开世界)
/mxt registries validate → 没有 Codec 错误
/mxt attachment status → 列出这个实体持有的技能
/saturn ability cast example:qi_bolt → 强制施放一次(需要 gamemaster 权限)
- 在进入境界链之前,
/saturn ability cast example:qi_bolt会失败:condition拒绝了它。 - 突破到筑基并查看
/mxt attachment status。现在灵力弹已被持有,source显示它来自境界。 - 用轮盘施放它(按住
R,指针指向放它的那一扇,再按V)。会扣除10点灵气,冷却开始,附近的实体会受到伤害。对比施放前后/mxt resource example:qi显示的值。 - 用
/mxt resource example:qi set 5把池子调得过低再施放:因为付不起消耗,施放被拒绝,且不会扣除任何东西。 - 在持有
qi_recovery时挨一次打:治疗量随受到的伤害缩放,消耗5点灵气,100 tick 的冷却让它不会立刻再次发动。
常见错误
| 现象 | 原因 |
|---|---|
| 技能从不显示在轮盘的可选池里 | 池子里列的是需要按键的技能;触发、被动类技能没有自己的轮盘条目。 |
| 消耗从未被扣除 | 一项消耗要么写带 type 的完整形状(例如 {"type": "mxt:resource", "resource": ..., "amount": ...}),要么写 {"id": ..., "amount": ...} 简写;不要写成没有 type 的 {"resource": ...}。 |
技能公式永远是 0 | 它用了自己的上下文不提供的变量,例如在实体公式里用 realm_rank。求值时会报出这个名字:开发环境记录完整错误,生产环境对每条不同消息记一行警告,两者都按 0 继续。 |
mxt:word 什么都不做 | 它是终端效果、由代码白名单限定(self_heal、purge_self_curses),并且默认需要管理员权限。它不是执行命令的手段。 |
| 所有人一开始就有这个技能 | 它是被某个灵根、体质或功法上的 granted_abilities 列表授予的,而所有人都满足那个定义——这些列表在定义被持有期间生效。 |
| 动作完全没有发生,也不报错 | 四个动作字段被写在了一个不跑动作的类型上(mxt:modifier / mxt:mount / mxt:storage 这类)。它们只由会跑动作的五个类型读取,写在别的类型上是没人读的键(不报错也不生效)——把这条技能换成会跑动作的类型,或把这些键搬到真正要跑它们的那条技能上。类型与字段的对应表见技能类型 · 四个动作字段。 |
写了 "effect": "..." 但什么也没发生 | 技能引用的 effect 字段没人读:把四个动作字段直接写在发动型自己身上,不要写 effect 一行;mxt:word 的 effect 是另一回事(它自己的效果枚举),不受影响。 |
| 定向技能按下去只报「没有符合条件的目标」 | mxt:targeted 的 target_selector 一个实体都没圈到,或者圈到的都被载荷技能的 target_condition 挡掉了:检查选择器的距离(mxt:area 的 radius、mxt:ray / mxt:cone 的 length)。这种落空在付款之前判,所以不花钱;点名的技能不能作用在目标身上时报的是另一条原因「指定的技能不能作用在目标身上」。 |