跳到主要内容

定义技能

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)。不写时该条目用名字绘制。
costsCost 对象列表,在行为执行前支付,整份数组全有或全无。四种条目与 {"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 扇轮盘(技能与灵气共用的那个,主盘 + 随装备出现的从盘):

  1. 聊天栏打 /wheel(客户端命令,等同于按键「打开轮盘配置」,默认未绑定)打开轮盘配置界面:左边 6 列是能发射的灵气,右边 6 列是学到的技能与 Curios 槽位上法器声明的技能(只由主手 / 副手物品声明的技能不在这里,它们只在随装备出现的从盘上),下面一排 12 格就是主盘的 12 格(1 在正上方,顺时针)。左键点右边池子里的灵力弹选中它,再点某一格放进去;Esc 保存并关闭。你刻意留空的格子会保持为空;已保存但定义已不存在的格子会显示红色 ?,不会被别的条目顶替。
  2. 按住「轮盘选择」(默认 R)让指针指向那一格——指针决定选中哪一格,而选中永远不会空着:轮盘打开时就已经选中第一个有内容的格子(金框一开始就画在那里),指针停在空格子上不会改变已有选择。松开 R 只关掉轮盘,不会施放。按「轮盘使用」(默认 V)才施放,而且轮盘不关,可以接着换一格再按;轮盘关着时按 V 会施放你上次选的那个编号此刻代表的那一格(页没了就落到最后一个有东西的格子,编号本身不改),鼠标左键等同于 V。屏幕左侧那块「轮盘格」是四列、行数随页数往下长的整张轮盘一览,其中金色边框那一格就是"现在按 V 会放什么"。
  3. 一切都是服务端权威:客户端只发送"用了哪一类的哪个 id",授予、条件、消耗、冷却、时长与效果都由服务端决定。

在游戏里验证​

技能是数据包注册表,所以要重新加载世界,而不是执行 /reload:

(重新打开世界)
/mxt registries validate → 没有 Codec 错误
/mxt attachment status → 列出这个实体持有的技能
/saturn ability cast example:qi_bolt → 强制施放一次(需要 gamemaster 权限)
  1. 在进入境界链之前,/saturn ability cast example:qi_bolt 会失败:condition 拒绝了它。
  2. 突破到筑基并查看 /mxt attachment status。现在灵力弹已被持有,source 显示它来自境界。
  3. 用轮盘施放它(按住 R,指针指向放它的那一扇,再按 V)。会扣除 10 点灵气,冷却开始,附近的实体会受到伤害。对比施放前后 /mxt resource example:qi 显示的值。
  4. 用 /mxt resource example:qi set 5 把池子调得过低再施放:因为付不起消耗,施放被拒绝,且不会扣除任何东西。
  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)。这种落空在付款之前判,所以不花钱;点名的技能不能作用在目标身上时报的是另一条原因「指定的技能不能作用在目标身上」。

接下来​

  • ability(技能) —— 完整字段表,包括 charges 与引导技能的维持消耗。
  • 定义功法与晋级 —— 把技能挂到一条会往上爬的进度链上,让熟练度决定什么时候解锁哪一个。
  • 行为类型与条件类型 —— 技能能做和能检查的一切。
  • 战利品与进度条件 —— 对突破与技能使用做出反应的奖励和进度。
  • 编写消耗 —— costs 数组里那四种类型各自扣什么,以及一次动作里的几笔什么时候整笔回退。
  • 放出一道剑气 —— 用 mxt:spawn_sword_aura 让技能甩出一个会飞的剑气。