定义灵气与境界
本篇教程搭建 MiXianTu 能跑起来的最小修炼循环:一个由玩家填充的、被存储的 resource,一个把这个数字变成带境界链的灵气的 aura 定义,一个把灵气送进它的修炼行为,以及一个很小的灵气区域,让世界真的含有灵气。做完之后,玩家可以按下修炼按键,看着资源条涨起来,并突破到自己的第一个境界。
这里没有任何内容是绑定某个世界观的:下面的数字都是占位值,你应该把它们替换掉。
开始之前
如果还没读过,请先读数据包开发总览。它说明了文件放在哪里、定义 ID 如何工作、为什么一个缺失的 Holder 引用会让整次加载失败,以及你的修改什么时候生效。
核心循环的形状
aura_zone / block_aura the world supplies aura per chunk
↓
cultivation the player absorbs it while cultivating
↓
aura example:qi the definition that turns the stored number into an aura
↓
resource example:qi the bar fills; the overflow becomes progress
↓
realm_stage chain progress + conditions + costs → next realm还没有进入境界链的玩家是凡人。凡人状态没有自己的定义,也没有自己的阈值:它读的是 first_realm 指向的那一档,用那一档的 breakthrough_exp / max_experience 当自己的门槛与上限。
你要搭建什么
| 文件 | 注册表 | 用途 |
|---|---|---|
element/common.json | element | qi 灵气所指的元素,以及它的关系和颜色。 |
resource/qi.json | resource | 被存储的数字、它的边界和它的 HUD 条。 |
aura/qi.json | aura | 加在那个数值上的灵气定义:元素标记、自然恢复、境界入口。 |
realm_stage/qi_condensation.json | realm_stage | 第一个境界,以及对第二个境界的要求。 |
realm_stage/foundation.json | realm_stage | 第二个境界。 |
realm_stage/core_formation.json | realm_stage | 第三个境界,链条的终点。 |
cultivation/meditation.json | cultivation | 玩家修炼时做的事。 |
aura_zone/common_land.json | aura_zone | 主世界里平原级别的灵气供给。 |
assets/example/lang/en_us.json | — | 上面这些 ID 的名称。 |
第 1 步 —— 元素
定义的灵气是一个独立的 aura 条目,而它通过 aura_type 指向的正是 element 注册表。一个 element 只持有关系和一个颜色:它 overcomes 哪些元素、adapted_to 哪些元素、每条关系值多少伤害,以及用于灵气消耗文本的颜色。先从一个最小的开始。
// data/example/mxt/element/common.json
{
"color": "#66CCFF"
}overcomes 与 adapted_to 是指向其他元素的可选关系。每个条目写出它指向的元素(elements,单个 ID、数组或 # 标签)以及这条关系值多少(multiplier,有限非负数),伤害系统会分别在攻击方与受击方读取它们;在你拥有不止一个元素之前,可以先不写。完整字段表见 element(元素)。
第 2 步 —— 灵气与它的数值
resource 只是一个带边界和资源条的存储数值;aura 则给这个数值以灵气身份与修炼行为,并通过 resource 字段一对一地指回它。先把两个文件都写出来,再看下面的表。
// data/example/mxt/resource/qi.json
{
"default_value": 0,
"max": "50 + realm_rank * 50 + absorbed_aura * 0.2",
"particle_color": "#66CCFF",
"bars": [
{
"context": "mxt:self_hud",
"anchor": "left",
"order": 0,
"renderer": {"type": "mxt:boss_bar", "bar_index": 1},
"value_display": "current_and_maximum"
}
]
}// data/example/mxt/aura/qi.json
{
"resource": "example:qi",
"regen": "0.05 + realm_rank * 0.05",
"aura_type": "example:common",
"first_realm": "example:qi_condensation"
}resource 字段 | 它在这里的作用 |
|---|---|
default_value | 新玩家从空开始。必填。 |
max | 资源条的上限。写成公式,因为 max 是在该数值的灵气上下文中求值的,那里有 realm_rank 和 absorbed_aura。必填。 |
particle_color | 该数值发射的灵力射线颜色。 |
bars | 左列中的一条自身 HUD 条;renderer 必填,且每条资源条都需要一个 anchor。 |
aura 字段 | 它在这里的作用 |
|---|---|
resource | 该定义描述哪个存储数值。必填,且一个数值最多只能有一个灵气定义。 |
regen | 缓慢的细流,让池子在打坐之外也能回填。 |
aura_type | 该灵气所指的元素;环境与灵气燃料用它做类型判定,/mxt aura query 会把它显示在名字旁边。 |
first_realm | 首次突破的目标。没有它,凡人永远离不开凡人状态。 |
本篇教程其他地方用到的 aura 字段还有 cultivation_to_resource 与 resource_to_cultivation(修为与存储数值之间的换算)、burst_amount(一发灵力射线的数值)、start_cultivate_conditions(开始修炼前检查)和 show_cultivation_info(角色信息面板是否显示境界与修为进度)。完整列表见 aura(灵气)。
min 默认为 0,use_condition 默认为恒真,所以凡人也能看到这条资源条。如果你更想让玩家进入境界链之前隐藏这条资源条,就把它加到 aura 上:
"use_condition": {"type": "mxt:has_realm", "aura": "example:qi"}use_condition 只控制显示和玩家的主动消耗。它从不阻止修炼、吸收或突破。完整字段表见 aura(灵气)。
公式
任何数值字段都接受普通数字、公式字符串或带类型的提供器对象。公式用 exp4j 求值,除上下文变量外还可以使用 round、clamp、min、max、pi 和 e。见公式变量。
第 3 步 —— 境界链
每个 realm_stage 文件是一个阶段。一个阶段只指向一个 aura,next_realm 只指向一个阶段,因此链条是一条直线:一个 aura 只有一条链,而且只能向前。真正被填满的是 aura 所指的那个数值。
// data/example/mxt/realm_stage/qi_condensation.json
{
"aura": "example:qi",
"next_realm": "example:foundation",
"breakthrough_exp": 800,
"max_experience": 1600,
"passive_modifiers": [
{
"attribute": "minecraft:max_health",
"id": "example:realm/qi_condensation",
"amount": 2,
"operation": "add_value"
}
],
"costs": [{"id": "example:qi", "amount": 50}],
"auto_breakthrough": false
}// data/example/mxt/realm_stage/foundation.json
{
"aura": "example:qi",
"next_realm": "example:core_formation",
"breakthrough_exp": 2000,
"max_experience": 4000,
"breakthrough": {
"conditions": [
{"type": "mxt:resource_compare", "resource": "example:qi", "min": 200}
]
},
"costs": [{"id": "example:qi", "amount": 200}],
"auto_breakthrough": false
}// data/example/mxt/realm_stage/core_formation.json
{
"aura": "example:qi",
"max_experience": 8000,
"passive_modifiers": [
{
"attribute": "minecraft:max_health",
"id": "example:realm/core_formation",
"amount": 6,
"operation": "add_value"
}
]
}从 A 突破到 B,读的是 B 的 JSON
一步突破要读的东西全写在 B 那一档上:门槛 breakthrough_exp、上限 max_experience、条件 breakthrough、消耗 costs、自动开关 auto_breakthrough、寿元 lifespan。这些字段写在 A 上对这一步没有任何影响,所以新增一档时写新档自己的门槛,别回头去改上一档。
把三个文件放在一起读,下面每一条都是"从上一档突破到本档"时读的那一份:
breakthrough_exp是突破到本档所需的修为,max_experience是突破到本档前允许持有的修为上限。两者不能交叉:常量breakthrough_exp大于常量max_experience会在加载期被拒绝。- 于是
qi_condensation.json里那个800是"凡人突破到炼气期要 800",foundation.json里的2000是"炼气期突破到筑基要 2000"。 costs与breakthrough.conditions也写在突破的终点那一档:costs从突破者身上扣、conditions与修为一起检查。所以筑基那一档的200点灵气是"突破到筑基要付的",而不是"从炼气期出发要付的"。- 凡人没有上一档,所以读的是首境界那一份:凡人阶段的
800门槛与1600上限都来自qi_condensation这一档,别的地方没有可写的凡人阈值。 - 可选的
minor_stages只是一个数量:写一个整数 N 就是「这么多重」(默认0,范围0..1024),没有写名字的途径——每一重的名字一律按本境界的名字键生成,为realm_stage.mxt.<命名空间>.<路径>.minor_stage.<下标>,下标从0起。要改写成整数,写成名字数组会让整份定义解码失败。它影响三处:信息面板在境界名后写出当前这一重、公式里多一个从0起的minor_stage、以及minor_stage_abilities用这个下标按层解锁能力(累计,解锁过就永久保留)。重数取自你此刻站着的这一档,切分用的是正爬向的那一档的breakthrough_exp,不改变阈值、消耗或任何结算。 auto_breakthrough默认为false:玩家到达阈值后要自己等。想让修炼模式自行尝试突破就设为true。foundation写true表示"突破到筑基可以自动"。passive_modifiers是在该阶段被持有期间授予的原版属性修正。value是可选的公式,声明了它的条目每 tick 重新计算。- 最后一个阶段只是没有
next_realm,链条就在这里结束。
境界条件不是 caster_level
在数值、境界或突破公式里,level、realm 和 realm_rank 都是链内序号。在技能数值这样的实体公式里,caster_level 是原版经验等级,而根本没有境界序号。在数值与境界字段里用 realm_rank,让意图显而易见。
第 4 步 —— 修炼行为
cultivation 是一个具名活动。玩家选择其中一个,它按固定间隔结算;多条法门并存时,先在整张表里筛出此刻适用的(start_condition 与 cultivate_condition 都要成立),再取 priority 最大的一条,同分按注册表顺序;一条都不适用就报「没有一门当下能修的法门」。
// data/example/mxt/cultivation/meditation.json
{
"priority": 1,
"tick_interval": 20,
"absorb_amount": 1.5,
"costs": [{"type": "mxt:aura", "aura": "example:qi", "amount": 1}],
"cooldown": 100
}| 字段 | 效果 |
|---|---|
priority | 多条法门并存时的先后:数值大者先,相同则按注册表顺序。它只在此刻适用的法门里排序。默认 0。 |
tick_interval | 结算间隔,单位 tick,范围 1..72000;20 表示每秒一次。默认 20。 |
absorb_amount | 当前境界数值自然恢复的倍率;先填满资源条,溢出量成为修为。默认 1。 |
costs | 每 tick 的消耗:mxt:aura 条目从修炼者所在位置的共享灵气池扣([{"type": "mxt:aura", "aura": "example:qi", "amount": 1}]),其余条目从修炼者自己账上扣。多人同区块修炼时,灵气那些条目先按池子分配份额缩放,再由池子全有或全无地扣。 |
cooldown | 停止后再次开始修炼前的冷却 tick。默认 0。 |
start_condition 与 tick_condition 决定修炼能否开始与继续;两者默认恒为真,并且都能读取环境。没有“灵气种类”字段:只应在正确地点运行的行为会直接要求那个地点,例如用 mxt:aura_range(某一门灵气的浓度区间,每一项都必填 max)或 mxt:dimension。剩下的字段是 costs(每 tick 从修炼者身上扣的消耗,Cost 数组、全有或全无)、aura_gains(每 tick 额外增加的灵气)、tick_action(每个 tick 都运行的实体行为)和 cultivate_action(结算成功那一拍才运行的实体行为)。
第 5 步 —— 一个最小的灵气区域
没有灵气区域,世界里就不含灵气,打坐行为也就没有东西可以吸收。
// data/example/mxt/aura_zone/common_land.json
{
"aura": {
"example:qi": {
"amount": 200,
"max": {"type": "mxt:initial_multiplier", "multiplier": 2},
"regen_per_tick": 0.05,
"color": "#66CCFF"
}
},
"distribution": "equal",
"biomes": ["#minecraft:is_overworld"]
}aura是按灵气分组的环境库存,按区块存储,由该区块里的所有人共享。它的键集合就是该区域的全部词汇:一个环境拥有且仅拥有它列出的那些灵气。它们各自属于哪个元素来自那门灵气自己的aura_type,所以这里不需要额外声明什么。amount是模板的基础灵气,不是 HUD 显示的数字:没有配置噪声时,初始浓度是max(0, amount / 10 - 5),因此200从大约15开始,而max从那个初始值解析——这里是initial_multiplier: 2,所以该区块最多能容纳约30。regen_per_tick随时间回填区块库存。distribution决定库存不足时多名玩家如何分配:random、equal或realm_weighted。biomes与dimensions决定模板在哪里生效;#minecraft:is_overworld覆盖主世界的全部群系。维度级绑定优先于群系级,两者都低于手动区域和阵法。cultivate_condition是环境自己给修炼加上的条件;它默认恒为真,想让某处要求浓度就在区域里写mxt:aura_range。行为自己的start_condition/tick_condition是同一个判定的另一侧。
灵气环境的深度值得单独一篇页面——那就是定义灵气环境,你会在那里加入更浓的区域、方块来源、物品燃料以及客户端的雾效和 HUD。
第 6 步 —— 名称
显示名称一律由定义 ID 自动生成,你没有地方把名字写进 JSON:定义里没有 name / description 字段,给了也读不到。包唯一能给一个定义配词的办法就是翻译生成的键。把这些键加到你自己的语言文件里:
// assets/example/lang/en_us.json
{
"mxt.resource.example.qi": "Spirit Qi",
"mxt.aura.example.qi": "Spirit Qi",
"mxt.realm_stage.example.qi_condensation": "Qi Condensation",
"mxt.realm_stage.example.foundation": "Foundation Establishment",
"mxt.realm_stage.example.core_formation": "Core Formation",
"mxt.element.example.common": "Common Aura",
"mxt.cultivation.example.meditation": "Meditation"
}规则始终是 <registry namespace>.<category>.<namespace>.<path>,其中 category 是注册表自己的 path,而注册表命名空间恒为 mxt(所以键总以 mxt. 开头),resource 里的 example:qi 是 mxt.resource.example.qi,同一个 ID 在 aura 里则是 mxt.aura.example.qi。含有 / 的 path 会保留斜杠:example:realm/qi 是 mxt.realm_stage.example.realm/qi。没有键的定义依然可用;游戏只会显示原始键名。
描述用的是名字键再接 .description:quality 里 mxt_test:poor 的名字键是 mxt.quality.mxt_test.poor、描述键是 mxt.quality.mxt_test.poor.description;realm_stage 的子境界名键是 realm_stage.mxt.<命名空间>.<路径>.minor_stage.<下标>。会把描述画出来的是 quality(品质名下面那一行)、contract_type(契约卷轴的提示框)、pill(认领丹药的物品提示框)与 technique(手册的提示框,以及被读时聊天栏那一行),而且只在这个键被翻译时才画;其余注册表的描述键没有任何界面或提示框去读,属于「生成了但没人画」。
在游戏里验证
数据包注册表在世界加载时读取,所以只执行 /reload 不够:退回标题界面重新打开世界(或重启服务器),并留意日志里的 Codec 错误。无法解码的文件会让世界加载不了,所以如果世界打不开,先读日志里最后一条错误并修好那个文件。
(重新打开世界)
/mxt registries validate → 注册表已加载,没有错误
/mxt registries list → mxt:resource=1, mxt:aura=1, mxt:realm_stage=3, mxt:element=1, mxt:cultivation=1, mxt:aura_zone=1, …(本模组注册的每张表都会列出,多数为空)
/mxt resource example:qi → 0
/mxt aura query example:qi → 你所在区块的灵气库存
/mxt cultivate status → 选中的法门与各灵气下的进度然后在游戏里:
- 在主世界某处按下修炼按键(默认
C)。资源条出现在左列并开始填充。 - 继续修炼直到条满;从那时起溢出量成为修为。
/mxt cultivate status与/mxt attachment status会显示当前进度。 - 修为到
800——目标档qi_condensation的breakthrough_exp——时突破就成为可能了(进度还能继续涨到那一档的max_experience1600,但过线即可尝试)。因为目标档的auto_breakthrough是false,你自己用/mxt breakthrough example:qi触发(需要gamemaster权限),或者给它设auto_breakthrough: true让修炼自己完成。 - 成功后你就处于炼气期:
/mxt attachment status显示新境界,50 + realm_rank * 50的上限里realm_rank一项从凡人时的0变成1(上限因此抬高50),+2 max health修正也被应用。 - 用同样的方式爬到筑基。你需要池子里同时有
200点灵气,因为那个阶段的breakthrough.conditions要求它,而costs会花掉200点灵气。
更快地测试
/mxt resource example:qi set 500(同样需要 gamemaster)会立刻填满池子,让你不用等待就能检查消耗与条件门槛。/realm set example:foundation(= /mxt realm set …)直接把链条跳到某个阶段,在调后续阶段时很有用;/realm chain example:foundation 会打印这一档所在的整条链,当前那一档是绿色、它之前灰色、之后白色,写错 next_realm 时一眼就能看出来。
常见错误
| 现象 | 原因 |
|---|---|
| 谁都无法离开凡人 | aura 上缺少 first_realm,没有可以突破到的阶段。 |
| 修炼从不开始 | 行为的 start_condition 或 tick_condition 不满足——为你想要的地点写一个 mxt:aura_range 或群系条件,因为没有可匹配的“灵气种类”词汇。 |
| 资源条从不增长 | 共享灵气池付不出 costs 里的灵气条目(池子本身不够,或那份被同区块的其他修炼者分掉),或者 use_condition 为假。 |
| 世界拒绝加载 | 有定义解码失败:某个必填 Holder 指向不存在的 ID,或某个字段形状不对。整次加载都会失败,而不只是那个文件。 |
breakthrough_exp 大于 max_experience | 这一档无法被突破到;两者都是常量时,Codec 会在加载期拒绝。门槛要写在当前档(突破的终点)上,别写到上一档。 |
| 境界条件从不通过 | breakthrough 写在目标档上、求值时身体还在上一档;mxt:realm 比的又是当下所在,所以在目标档自己的条件里点名它自己永远过不了(凡人是唯一例外)。要么点名下面那一档,要么用 "comparison": ">=" 或改查数值。 |
接下来
- 编写双修功法 —— 在同一个包上再加一条"身边有人才出成果"的修炼法门。
- 定义灵气环境 —— 让浓度随地点、方块和时间变化,并把它放到 HUD 上。
- resource(资源)与 realm_stage(境界阶段) —— 所有剩下的字段,包括资源条、换算和天劫。
- 定义元素反应 —— 第二种元素怎么认领伤害类型,附着攒够之后又能做什么。