quality(品质)
文件位置:data/<namespace>/mxt/quality/<path>.json
一个 quality 是一档品质。它叫什么是给界面看的,value_multiplier / forging_modifier / alchemy_modifier 是给经济、锻造与炼丹结算用的。它同时是品质链的一个环节:next 指向它上面那一档,quality 是这条链的名字,写在入口档上。
| 字段 | 类型 | 默认 | 说明 |
|---|---|---|---|
color | Color | 无 | 品质颜色,写 #RRGGBB(整数或 [r,g,b] 浮点数组也接受),一律按不透明处理。 |
value_multiplier | Modifier | 1 | 货币价值修正。 |
forging_modifier | Modifier | 1 | 锻造修正。 |
alchemy_modifier | Modifier | 1 | 炼丹修正。 |
condition | EntityCondition | mxt:always | 使用该品质的条件。 |
next | quality id | 无 | 指向它上面那一档,也就是升级的目标档;最高一档省略。 |
upgrade_costs | Cost 数组 | [] | 升到这一档要付的代价(写在目标档上),与技能消耗走同一套事务:plan → commit 整组原子,付不出就一步都不动、也不写档。 |
upgrade_condition | EntityCondition | mxt:always | 升到这一档这一步能不能走,在扣费之前判。 |
quality | Identifier | 无 | 这条链的名字,写在入口档上(入口档=没有任何一档把它写成 next 的那一档)。链名只从入口档读一次,写在别的档上不会被当成链名。 |
这一档叫什么由注册 id 现算:名字键是 quality.mxt.<定义命名空间>.<路径>,描述键在它后面加 .description。定义里没有可写的文本字段,要配词就翻译这两个键;描述画在品质名下面。
condition 不满足时,解析到这一档的物品用不了:使用、攻击、主手武器的周期行为,以及它给的原版属性修正都会停下。
color 的规矩与上面那两个键不同:写了就以它为准(连译文文本里自带的颜色也会被它盖过),不写就一个字都不改。它不是「默认白色」,没写颜色的品质照旧显示原版稀有度色。上色的地方只有四处:物品提示框的物品名那一行、提示框里「品质:<名>」那一行、/picker 里品质分类的条目名、锻造台蓝图 tooltip 的档位表。物品名那一行还能由玩家自己关掉:客户端配置「提示框 → 物品名跟随品质」(默认开),其余三处不受它影响。品质描述那一行始终是灰的。
Modifier 是三个可选对象,每个对象里的 description 与 modifier 也都可以省略:modifier 省略等于 1,description 省略就不画那一行(修正照样生效)。它的文案完全由数据包自己指定,没有自动生成:
{
"value_multiplier": {
"description": "mxt.quality.example.refined.value_multiplier",
"modifier": 1.25
},
"forging_modifier": {
"modifier": "1 + level * 0.01"
}
}上面这份只写了三个修正,forging_modifier 连 description 都省略了。
value_multiplier 结算时按 modifier 乘在该物品的货币单位面值上并四舍五入到整数,因此看得到的就是付得出的(Tooltip 显示的也是结算值)。modifier 缺失、非有限或 ≤0 时按 1 处理;乘积非有限、小于 1、超出 long 时退回声明面值,不会凭空造出一个数据包没写过的数。
forging_modifier 结算时用 有效额外步数 = 实际额外步数 ÷ modifier 去查品质档,也就是 >1 表示同样的手法能拿到更好的档。取值来自这场会话锁定的材料:多种材料取其中最低的一档品质,没有品质解析结果的材料跳过。modifier 为 1 时行为完全不变。
alchemy_modifier 决定开炉时长,时长 = 声明时长 ÷ (原料侧修正 × 核心侧修正),>1 炼得更快,结果写进会话快照。两处各取一次:作为材料时取开炉那一刻炉内原料栈里最低的那一档(同样取最低品质、跳过无品质者);作为丹炉核心那一档时,它直接缩短这台炉子炼任何丹方的时长(核心没有档时算 1.0)。它不参与炉温上限,任何档位的 alchemy_modifier 公式也读不到 furnace_rank。
品质链
品质的高低不写在别处:每一档用 next 指向上一档,顺序、入口与链身份由运行时沿这些指针走一遍得出。链名是字段 quality,写在入口档上:
// data/example/mxt/quality/common.json
{
"quality": "example:pill",
"next": "example:refined"
}
// data/example/mxt/quality/refined.json
{
"next": "example:flawless",
"upgrade_costs": [{ "id": "example:qi", "amount": 20 }]
}
// data/example/mxt/quality/flawless.json
{}- 这三档属于同一条链
example:pill:链名写在入口档common上,运行时再顺着next传到refined与flawless。想给链换名字就改这一处。 - 入口档自动是「没有任何一档指向它」的那一档(这里就是
common)。它只决定链的顺序,不给没有品质的物品补默认档;链条不需要写default,最低一档也就不需要写next。 - 链名只从入口档读一次。 把
quality写到中间档上,入口档拿不到名字,于是整条链都没有名字(/quality chain显示的链名是null)。这不报错:写在中间档上的那份只会参与下面「一档收到两个不同链名」的检查。 - 升级的代价与条件写在升到的那一档上。
common → refined读的是refined的upgrade_costs与upgrade_condition。写在来源档上代表「升进它自己」,而入口档没有任何一档能升进去,那笔代价永远收不到——那一步实际免费,也不报错。 - 想把某一档当成顶端,就不写
next。 写了next却没写upgrade_costs时那一步仍然存在,代价是空数组;反过来,一档没有next却写了upgrade_costs或非mxt:always的upgrade_condition会被报出来,因为那份数据永远读不到。 - 一条链是一条直线。 一档只写一个
next,所以每档最多一个「下一档」;两档把同一个next写成自己(分叉)会被报出来,点名那一档跟着哪两档——分叉之后「这一档下面是谁」本来就没有唯一答案,所以走进分叉点的那几条链整条不索引(不是按注册表顺序挑一条),与成环同一个口径。next指向一个当前包没有的档不是「链不索引」,而是整个数据包加载失败(世界直接拒绝加载),那条路走不到运行期。上一档与下一档都是从走出来的顺序里查表,两个方向对称。 - 一条链上一个名字。 一档同时收到两个不同的链名(它自己写一个、上方的档又写了另一个)会被报出来;成环、或整条链接不到入口同样会报出来(
/reload会重跑一遍)。 - 不属于任何链的档也能用:孤零零一档既没有
next也没有quality时它自成一体,能显示、能被mxt:quality组件与三个修正读到,只是没有顺序、不能升级。
两个同名的东西
quality 是这一档上的字段(这条链的名字,一个字符串),mxt:quality 是物品上的组件(装整份品质对象)。两个名字像,但不是同一个东西。
品质是哪一档
一条物品堆是哪一档,按固定顺序取第一个能拿到的答案。一共三层,先命中的赢。
第一层,堆上的 mxt:quality 组件(整份品质对象)。每个写档的地方写的都是它:/quality set 与 MxtQuality,一次成功的 upgrade,以及锻造台结算与画符铭刻。它按注册表读回,所以当前包不再提供的那一档答不出东西,这一层落空后继续往下走,不会留下一个死的档位。
第二层,这一堆携带的定义自己声明的 quality。 一类定义共用一件内置物品——每本功法手册都是 mxt:technique_book、每颗丹都是 mxt:pill、每份炉型规格都是方块物品 mxt:alchemy_furnace、灵根石是 mxt:spirit_root、体质石是 mxt:physique——物品本身说不清是哪一档,只有堆上携带的这份定义说得清。这个字段一律可选,为了从堆上读到它,那一类定义要有一个已登记的载体组件(模组在类加载时登记自己的几个;附属模组登记自己的载体是另一页的事)。九个定义声明 quality,各由一个已登记的载体读到:
| 定义 | 载体组件 |
|---|---|
technique | mxt:technique,值就是这份定义 |
alchemy_furnace | mxt:alchemy_furnace,值就是这份定义 |
alchemy_wall_material | mxt:alchemy_wall_material |
spirit_root | mxt:spirit_root |
physique | mxt:physique |
pill | mxt:pill,组件是一个 record,定义在它里面的 pill 下 |
formation | mxt:formation_plate,定义在 formation 下 |
secret_realm | mxt:secret_realm_token,定义在 realm 下 |
contract_type | mxt:contract_scroll,定义在 contract_type 下 |
当前包不再提供那一档时(那份定义被删掉,或引用没绑定)这一层答空,解析继续往下走。
spirit_root 与 physique 没有自由文本的档位字段,档位就是它们这个可选的 quality 引用;也没有 mxt.rarity.<值> 这类按文本值查档位的键。列一档时显示的是那一档自己的名字。
第三层,注册表 default_quality(条目用 items 认领物品,quality 写这一堆的档)。它是第三层,也是最后一层,恰好在这一堆没有定义可问的时候给出答案:裸的创造模式 / /give 物品,以及不携带物品身份的定义——artifact 命中一件物品就算数、spirit_herb 则由它的果实命中一件物品就算数,这两种堆上都没有任何装定义身份的组件。
三个定义刻意没有 quality 字段:
artifact与spirit_herb都不携带物品身份(artifact按物品认领、spirit_herb按它的果实认领),堆上没有装定义身份的组件,没有可问的对象。它们的档写在default_quality里;一件物品只意味着一份定义时,这就是准确答案。灵植多一条路:定义写了quality_by_age时,采收那一刻按药龄把档写进第 1 层组件,没写这条阶梯的草照旧兜底到default_quality。talisman的载体组件mxt:talisman装的是一列铭刻,同一堆上可能有好几张符,没有单份定义可问。它的档由画符配方的grades[].quality定,铭刻时写进mxt:quality组件;写不上才兜底到default_quality。
装列表的载体一律不登记:mxt:forging_methods、mxt:forging_blueprints、mxt:element、mxt:talisman——一列东西答不出「这一堆是哪一档」。
链看这一堆解析出的那一档:链名写在档位自己身上,所以绑定表与组件都不必声明链。三层都没有答案时这一堆就是没有品质,不会去补某条链的入口档。
/quality clear 摘掉组件之后,这一堆退回携带的定义那一档,再退回注册表。堆上的 mxt:pill 组件里那些效果字段(on_consume、toxicity_gain 之类)只改写这一堆服用时做什么,不参与定档:定档读的是它里面 pill 那份定义自己的 quality。
四个看起来像默认档、其实不是的东西:
forging_blueprint.quality_by_extra_steps[].quality—— 锻造曲线上的目标档,按额外步数查出来;- 画符配方里的
grades[].quality—— 完成时命中的目标档,铭刻时写进组件; spirit_herb.quality_by_age[].quality—— 采收时按药龄查出来的目标档,写进组件;灵植定义自己没有quality字段;quality条目自己的quality字段 —— 那是链名,写在入口档上。
这四个都不参与「这一堆是哪一档」的解析。
按档位筛物品
三个口子共用同一份要求,另外两处槽位仍是硬判据:quality(成员判定,条目 / #品质标签 / 数组)与 min_quality(至少到这一档)。min_quality 按这一档所在的那条链比位置(与升级、/quality chain 同一份实现),跨链一律答否——不同链的序号没有可比性;两个都写就要都满足;只写 min_quality 时没有档的物品答否。两个都没写的要求在加载期被拒绝(空要求会静默恒真)。判据只有一处实现,三个口子只是把要求挂上去的不同方式:
- 物品条件
mxt:item_quality(quality与新增的可选min_quality):任何跑物品条件的地方都能用,例如货币的unavailable_when、绑定表的conditions;见物品条件。 - 匹配器条目
mxt:quality(items必填,再加上面两个字段):凡是用 ItemMatcher 的地方都能用——Cost的mxt:item、artifact/pill_binding/technique_binding的items、spirit_herb的blocks、mxt:item_matcher/mxt:item_id/mxt:item_tag条件、灵植的种子、拾取器。 - 自定义材料
mxt:quality(neoforge:ingredient_type+items+ 上面两个字段):凡是用原版Ingredient的地方都能用——原版与别的模组的配方、灵气合成、画符、mxt:ingredient条件、锻造蓝图的input(那一项就是 sized ingredient),以及画符配方的paper(那一项就是 ingredient);见灵气合成。
还有一条窄路只对"材料条目"成立:匹配器条目 mxt:ingredient 能把一整个原版材料(含 mxt:quality)写进任何接受 ItemMatcher 的地方,画符那次取纸的代价走的就是它。
丹方还有两个字段挂在机器侧,不是物品自己的匹配条目:furnace_quality 要求丹炉核心的档位,input_quality 逐格要求输入仓里每一格非空物品(主药、辅药、药引都算;炉壁与多方块结构不看品质)。两份形状相同,都至少写一个字段、写空对象会被拒;不过就拒绝开炉、不扣料、不动供热方块。核心的档位还能作为变量 furnace_rank 进丹方自己的公式字段(药力公式与任何 alchemy_modifier 公式读不到它);核心那一档自己的 alchemy_modifier 也进这一炉的时长换算,所以更高级的炉子炼得越快。
两处槽位自己的谓词今天仍读不到档:丹炉输入仓(认"被哪条 spirit_herb 认领 + 那味药的药力";要按档筛投进去的药材就写在丹方的 input_quality 上,它在开局时逐格判,不是槽位谓词)与符笔颜料(mxt:brush_pigment 标签 + 服务端配置)。画符符纸格已经从硬 id 放宽成 mxt:talisman_paper 标签(内容把自己的纸加进标签即可),而"至少某档"这类要求现在写在符方自己的 paper 字段上——那一格答的是"这算不算纸"(客户端也答得出),"这份符方收不收"在开局时判。
客户端答得出 min_quality:连上服务器之后,客户端手里有同一份同步过来的品质注册表与标签,档位顺序就是从它自己走出来的,所以按档筛物品在客户端一样成立。只有完全没有关卡时(比如还在主菜单)才只剩成员判定可答——那时没有注册表,也就没有位置可比。落在服务端的匹配(配方、扣费、槽位)不受影响。
条件 mxt:component 也读不到品质——它要求 nbt 是复合标签并做偏序比较,而品质序列化成一个字符串。
原版标签不参与品质解析:group/<name> 标签不读,品质注册表上也没有别的标签读它——列档位的顺序就是注册表自己的顺序。color 只影响画品质名的地方,同样不参与解析。
名称与翻译
类别就是注册表自己的 path:example:refined 查 mxt.quality.example.refined,描述再加 .description(注册表命名空间仍是 mxt)。名字与描述都只能靠翻译这两个键,定义里没有可写的文本字段;会把描述画出来的是品质(画在品质名下面)、contract_type(契约卷轴的提示框)、pill(认领丹药的物品提示框)与 technique(手册的提示框,以及被读时聊天栏那一行);其余注册表的描述键没有任何界面或提示框去读。JSON 里没有 translation_key 字段;路径里的 / 和其它注册表一样原样保留在键中。