物品匹配器
物品匹配器(matcher)是 CoreLib 提供的通用输入判定块,用来回答一个问题:玩家放进来的这枚物品,算不算这条配方要的材料。
物品来源系统解决的是"这是哪个插件的哪个物品",匹配器则在此之上追加条件:附魔够不够高、名字里有没有某个字、是不是没有被摔过、耐久还剩多少。两者可以单独用,也可以组合用。
所有支持匹配器的模块都使用同一套语法,本页是唯一的权威说明。各模块页只列出自己的落位路径和特有边界。
判定位由两个同级字段合成
判定位(判断"这一枚够不够格"的位置)由两个同级并行的字段共同决定,两者取 AND:
item_sources:只表达"允许哪些物品源"。省略 = 不限来源。matcher:只表达组件、PDC、Lore、变量等非物品源条件。省略 = 不加额外条件。
两者都省略时,该条目永不命中。
matcher 内部禁止写任何物品源条件。 type: item_source、item_sources、source、sources 这四个 type 值会被解析器拒绝并打 warning,该 matcher 恒不匹配。物品源条件请一律写到同级的 item_sources。
「只按物品源判定」就只写 item_sources,不写 matcher;「只按组件判定」就只写 matcher,不写 item_sources。
只有输入匹配点支持 matcher
配置文件里长得像"物品源"的字段其实有三类,语义完全不同。只有第三类支持 matcher。
| 类别 | 作用 | 典型字段 | 支持 matcher |
|---|---|---|---|
| T1 构造底材 | 用哪个原版物品构造出这个自定义物品 | EmakiItem 的 item.source、Gem 的 base_item_source | 否 |
| T2 GUI 渲染 | 界面上显示什么装饰物、占位物 | GUI 模板里的物品定义 | 否 |
| T3 输入匹配 | 判定玩家放进来的物品算不算材料/输入 | materials[].item_sources、ingredients[] 等 | 是 |
DANGER
不要给 T1 字段写 matcher。 item.source 是"拿什么物品当模具",不是"匹配什么物品"。在那里写 matcher 会让物品无法生成。
T1 和 T3 经常出现在同一个文件里,形状还很像。以 EmakiItem 为例:item.source 是 T1,不可改;repair.materials[].matcher 是 T3,可以加。判断依据只有一条——这个字段是在造物品,还是在认物品。
物品源用连字符,组件 ID 用冒号
这是配错率最高的一处形状差异,两种写法不能互换。
| 场景 | 分隔符 | 正确示例 |
|---|---|---|
| 物品源简写 | 连字符 - | minecraft-diamond_sword |
| 组件 ID | 冒号 :(或省略命名空间) | minecraft:enchantments / enchantments |
| 组件内部的资源 ID | 冒号 :(或省略命名空间) | minecraft:sharpness / sharpness |
DANGER
物品源写成 minecraft:diamond_sword 会被解析器判为无效并静默丢弃,不是报错——原版标识符不允许出现冒号。整个 item_sources 列表被丢空后,等同于"没写 item_sources",也就是不限来源。
组件 ID 反过来:写 minecraft-enchantments 会被当成一个不存在的命名空间,永远取不到值。
# 正确
item_sources:
- minecraft-diamond_sword # 物品源:连字符
matcher:
type: component
component: minecraft:enchantments # 组件 ID:冒号
path: minecraft:sharpness # 资源 ID:冒号
operator: '>='
value: 3匹配器类型
| type | 别名 | 说明 |
|---|---|---|
pdc_match | pdc | 按 PDC 键值匹配。 |
lore_match | lore | 按 Lore 文本匹配。 |
component | component_match | 按 Minecraft 物品组件及其内部取值匹配。 |
variable_expr | expr、expression | 表达式或 PAPI 条件。 |
compare_target | target | 与目标装备比较 PDC 数值。 |
all_of | all、and | 子条件全部成立。 |
any_of | any、or | 子条件任一成立。 |
none_of | none、not | 子条件全部不成立。 |
at_least | 无 | 至少 required_count 条成立。 |
exactly | 无 | 恰好 required_count 条成立。 |
类型名不区分大小写。
type 必填,且不能是物品源
- 省略
type不再默认成物品源判定,而是加载时被拒绝并打 warning,该 matcher 恒不匹配。 type: item_source/item_sources/source/sources这四个值同样被拒绝并打 warning。物品源条件写到同级的item_sources字段。- 未知
type也被拒绝,恒不匹配(不再是过去的"恒为真、放行所有物品")。
这条限制对组合类型的 matchers 子列表同样生效:all_of / any_of / none_of / at_least / exactly 里也不能塞物品源条件。
type: component
按 Minecraft 物品组件匹配。
matcher:
type: component
component: enchantments
path: sharpness
operator: '>='
value: 5| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
component | string | 无(必填) | 组件 ID。可省略 minecraft: 命名空间。 |
path | string | "" | 组件内部取值路径。缺省表示取组件整体值。 |
operator | string | 见下 | 比较方式。 |
value | any | 无 | 期望值。除 exists / absent 外必填。 |
operator 缺省时:未写 value 取 exists,写了 value 取 ==。
component 会被规范化——转小写、空格转下划线、缺命名空间时补 minecraft:。因此 custom_name、Custom_Name、minecraft:custom_name 三种写法等价。
operator 全集
| operator | 别名 | 需要 value | 说明 |
|---|---|---|---|
exists | present | 否 | 组件存在。 |
absent | missing | 否 | 组件不存在。 |
== | equals、= | 是 | 相等。数值按数值比,布尔按布尔比,其余按文本比。 |
!= | not_equals | 是 | 不相等。 |
> | greater_than | 是 | 数值大于。 |
>= | greater_or_equal | 是 | 数值大于等于。 |
< | less_than | 是 | 数值小于。 |
<= | less_or_equal | 是 | 数值小于等于。 |
contains | 无 | 是 | 文本包含。 |
starts_with | 无 | 是 | 文本前缀。 |
ends_with | 无 | 是 | 文本后缀。 |
regex | pattern | 是 | 正则查找(find 语义,不要求整串匹配)。 |
has_key | 无 | 是 | 映射结构含指定键。比较时两侧都会去掉 minecraft: 前缀。 |
has_value | 无 | 是 | 列表或映射的值里含指定值。 |
size | 无 | 是 | 列表长度 / 映射条目数 / 字符串长度等于指定数值。 |
数值比较对两侧都做数值解析,解析不出数字就判否。>、>=、<、<= 只接受数值,不做文本字典序比较。
path 语法
path 用来深入组件内部取值。
| 写法 | 含义 | 示例 |
|---|---|---|
. | 分隔层级 | levels.sharpness |
[n] | 数组下标,从 0 起 | floats[0] |
[*] | 通配全部元素 | modifiers[*].amount |
"..." | 引号包裹含特殊字符的键 | "minecraft:sharpness" |
[*] 展开列表的全部元素或映射的全部值,任一元素满足即算命中。
资源 ID 键会自动补全命名空间,两个方向都通:path: sharpness 能命中实际键 minecraft:sharpness,path: minecraft:sharpness 也能命中实际键 sharpness。
# 任一属性修饰符的数值大于 5
matcher:
type: component
component: attribute_modifiers
path: modifiers[*].amount
operator: '>'
value: 5matcher 与 item_sources 的关系
判定位上这两个字段同级并行,结果取 AND:
| 写了什么 | 判定结果 |
|---|---|
只写 item_sources | 只看物品源。 |
只写 matcher | 只看组件 / PDC / Lore / 变量等条件,不限来源。 |
| 两者都写 | 物品源命中且 matcher 条件成立才命中。 |
| 两者都不写 | 永不命中。 |
想表达"某种物品 且 满足某组件条件"时
两个字段各写一半,不要往 matcher 里塞物品源:
item_sources:
- minecraft-diamond_sword
matcher:
type: component
component: enchantments
path: sharpness
operator: '>='
value: 1slot_groups、stats_any、source_patterns 也是独立的顶层字段,同样与 item_sources、matcher 取 AND(全部成立才命中)。
除了判定,item_sources 在部分位置还兼着与判定无关的差事,下表列出这些额外职责:
输出节点使用单值 item_source
Cooking、Forge、Station 的产出节点已经使用单值 item_source 作为规范字段:它只接受一个来源值,不能写列表,也不能与 item_sources 同时出现。旧的单项 item_sources 仍由对应加载器兼容读取并告警;多项来源或带 matcher 的产出节点会被拒绝。产出是构造物品,不属于本页的 matcher 判定位。
| 模块 / 位置 | item_sources 的额外职责 |
|---|---|
Forge materials[] | 仅在未显式指定身份时参与派生材料身份;material_id 用于选择、计数索引和 lookup,count_key 用于数量聚合/消费,audit_id 用于成品 PDC 审计与刷新。三者可独立指定,详见 Forge 图纸与材料。 |
Forge blueprint_requirements[] | id 用于同一图纸多种形态的聚合与 API/反向索引;item_sources 与 matcher 仍只负责判定。 |
Gem gems/*.yml 顶层 | 无。构造底材是独立字段 base_item_source;旧的顶层 item_sources 仅作为构造底材回退读取。 |
Cooking recipes/fermentation_barrel 的 inputs[] | slot_id 是持久化槽位身份,count_key 是数量聚合与消费身份;两者必须稳定且在同一配方内唯一。 |
Station materials[] | material_id 用于材料选择身份,requirement_id/count_key 用于分配和消费记录。仓库只按物品源计数;声明 matcher 的材料走背包栈分配,详见 Station 配方定义。 |
Skills materials[]、Item repair.materials[] | 物品源路径本身就是扣除实现。 |
Cooking moisture_rules[] 根部的 item_sources | 产出空容器的构造字段,不是判定位。判定写在子节点 input.{item_sources,matcher} 上。 |
Storage deposit_filter.matcher | 这里是过滤器不是判定位:entries 命中或 matcher 命中都算命中。 |
Strengthen recipes/*.yml 顶层 | 顶层物品源条件用既有的 source_patterns(正则),不是 item_sources。阶段材料使用 material_id 作为规则/选择身份,count_key 作为聚合身份。 |
关键语义
这几条是配置时最容易踩的地方,建议在写第一个 matcher 之前读完。
缺失不等于 0
组件不存在时,任何值比较一律判否。匹配器不会把缺失的组件当成 0、"" 或空列表。
# 这条不会命中"没有附魔的剑",它只命中"有附魔组件且锋利等级为 0"
matcher:
type: component
component: enchantments
path: sharpness
operator: '=='
value: 0想表达"没有这个组件",必须显式用 absent:
matcher:
type: component
component: enchantments
operator: absent同理,!= 也不会因为组件缺失而成立。要表达"要么没有,要么不等于某值",用 any_of 把两种情况都列出来。
exists 与值比较的取值范围不同
exists / absent 判断组件在不在,值比较读取组件的数据,两者的取值范围不一样:
exists会认原版默认组件(例如任何工具都自带max_damage)。- 值比较只看物品上显式设置的组件数据。
也就是说,一个从未被修改过的原版钻石剑,max_damage 的 exists 成立,但拿 max_damage 做数值比较不会命中。想按"服主实际写进去的值"筛选,用值比较;想按"这个物品有没有这类属性"筛选,用 exists。
WARNING
这条语义的运行期实际形态未在真实 Paper 服务器上验证过,详见未验证项。
单位组件只支持 exists / absent
unbreakable、glider、intangible_projectile 这三个组件没有值,只是一个标记。给它们写值比较会在加载时被拒绝并打 warning,该条件恒不命中。
# 正确
matcher:
type: component
component: unbreakable
operator: exists
# 错误:加载时被拒绝,恒不命中
matcher:
type: component
component: unbreakable
operator: '=='
value: true路径求值失败判否,不抛异常
path 写错不会导致报错刷屏,也不会中断配方加载——条件只是永远不成立。这意味着配错了没有直接反馈,要靠日志和实测排查。
regex 编译失败判否
regex 的模式串由服主提供。编译失败时打 warning 并判否,不影响其他条件。
类型写错会恒不命中
type 写了一个不存在的值、或者干脆没写 type 时,加载期打 warning,该 matcher 恒不匹配。这条与旧版行为相反——过去是"退化成空 all_of、恒为真、放行所有物品",现在是收紧成永不通过。
DANGER
拼错 type 的表现是"配方怎么都做不出来",而不是"什么都能做出来"。加了 matcher 之后请正反两面各实测一次,并检查启动日志里有没有 matcher 相关 warning。
配置不是映射结构时恒不命中
matcher 必须是一个映射(键值结构)。写成标量会在加载时打 warning,该条件恒不命中。
# 错误:标量
matcher: 'foo'
# 正确:映射
matcher:
type: component
component: unbreakable
operator: exists组合与嵌套
all_of、any_of、none_of、at_least、exactly 的子条件写在 matchers 列表里。子列表里同样不能出现物品源条件。
matcher:
type: all_of
matchers:
- type: component
component: enchantments
path: sharpness
operator: '>='
value: 3
- type: component
component: damage
operator: absentat_least 和 exactly 需要额外的 required_count:
matcher:
type: at_least
required_count: 2
matchers:
- type: component
component: enchantments
path: sharpness
operator: '>='
value: 5
- type: component
component: custom_name
operator: contains
value: 炎
- type: component
component: rarity
operator: '=='
value: epic| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
matchers | list | [] | 子条件列表。 |
required_count | integer | 1 | 仅 at_least / exactly 生效。 |
组合类型可以任意嵌套,嵌套项的写法与顶层完全一致:
# 钻石剑或下界合金剑,且没有被摔过,且锋利 >= 3
# 「哪几种剑」交给同级的 item_sources,matcher 只管组件条件
item_sources:
- minecraft-diamond_sword
- minecraft-netherite_sword
matcher:
type: all_of
matchers:
- type: none_of
matchers:
- type: component
component: damage
operator: '>'
value: 0
- type: component
component: enchantments
path: sharpness
operator: '>='
value: 3item_sources 列表本身就是"任一命中",所以过去用 any_of 包多个 item_source 的写法直接列进 item_sources 即可。
空列表的行为
matchers 为空时各类型的结果不一致,配置时注意不要留空列表:
| 类型 | 空列表结果 |
|---|---|
all_of | 真(恒命中) |
any_of | 假(恒不命中) |
none_of | 真(恒命中) |
at_least / exactly | 取决于 required_count,required_count: 0 时为真 |
未验证项
以下 6 项属于真实 Paper 服务器上的运行期形态,本轮开发未在实际服务器上验证,只经过本地 JVM 的解析与求值验证。涉及这些细节的配置请先在测试服实测再上生产。
| 未验证项 | 影响 |
|---|---|
| 组件字符串是否包含原版默认组件 | 影响 exists 与值比较的取值范围 |
| 附魔组件的真实键形态 | levels 子键是否存在、命名空间是否带前缀,影响 path 怎么写 |
attribute_modifiers 的真实结构 | modifiers 数组的字段名 |
custom_name 的真实序列化形态 | 纯文本还是 JSON 文本组件,影响文本类 operator |
| 嵌套物品栈的字段名 | bundle_contents、charged_projectiles 内部结构 |
| NBT 类型化数组的实际出现形式 | [I;...] 这类写法在组件里怎么呈现 |
本页中所有 path 示例(levels.sharpness、modifiers[*].amount 等)的语法是确定的,但具体键名取决于上述形态,可能需要按实测调整。
NOTE
已确定的部分:SNBT 数值后缀剥离(3.0f 按 3.0 比、5b 按 5 比)、类型化数组解析、path 求值(含 [*] 通配与资源 ID 双向补全)、五种组合类型与任意层级嵌套的配置解析。这些来自本地 JVM 测试;本页没有 Paper 或 Folia 实机通过结论。Paper/Folia 的实际组件序列化、线程时序和旧 YAML/旧存档兼容性仍需在目标服务端单独验证。
各模块的落位
| 模块 | 支持 matcher 的配置路径 |
|---|---|
下表中每个 matcher 位置都可以在同级写 item_sources(Strengthen 顶层用 source_patterns),两者取 AND。 |
| 模块 | 支持 matcher 的配置路径 |
|---|---|
| Strengthen | enhancement_recipes/*.yml 的 target.filter 与 materials[];recipes/*.yml 顶层 matcher(物品源用 source_patterns);stars.*.materials[] |
| Gem | config.yml 的 socket_openers.*;items/*.yml 顶层;gems/*.yml 顶层(构造底材另写 base_item_source) |
| Item | repair.materials[] |
| Forge | blueprint_requirements[];materials[] |
| Station | recipes/*.yml 的 materials[];recipes_dismantle/*.yml 顶层 |
| Cooking | ingredients[]、inputs[]、input、container、stations.chopping_board.tool、stations.wok.spatula、stations.juicer.container、stations.oven.fuels[]、stations.steamer.fuels[]、stations.steamer.moisture_rules[].input、nutrition.food_sources[] |
| Level | sources/*.yml 的 rules[] |
| Storage | behavior.deposit_filter.matcher |
| Skills | upgrade.levels.<n>.materials[] |
两处模块特有的边界需要注意:
- Station:材料只要声明了
matcher,这一项就只能从背包供料,仓库检索会跳过它(仓库只按物品源统计库存,看不到真实物品的组件)。需要走仓库的材料只写item_sources。详见 Station 配方定义。 - Forge:材料按配方内声明顺序首命中,先声明的宽条件会遮蔽后声明的窄条件。纯 matcher 材料建议显式写
material_id。详见 Forge 图纸与材料。
不支持 matcher 的两类位置
matcher 的唯一能力是 boolean test(MatchContext) —— 它只回答"这一枚够不够格",不能构造物品,也读不到方块。因此下面两类配置位置一律只认 item_sources,写 matcher 不会生效:
一、产出侧(要构造物品)。 这些位置需要凭配置造出一个 ItemStack 交给玩家,而 matcher 只能判定既有物品:
| 模块 | 不支持的路径 |
|---|---|
| Cooking | result.*.outputs[].item_sources、moisture_rules[].item_sources(产出的空容器)、stations.wok.invalid_result_item_sources、failure.item_sources |
| Forge | outputs[].item_sources |
| Station | outputs[].item_sources |
| Level | upgrade.rewards.items[].item_sources |
| Gem | inlay_cost、extract_cost |
Level 的奖励物品是真实发放的,走 ItemSourceService.createItem 再 giveOrDrop(PlayerLevelService.java:513-524),这条路径没有任何 matcher 参与的余地。
Gem gems/*.yml 的构造底材现在是独立字段 base_item_source(标量),与判定位彻底分开:base_item_source 负责造,同级的 item_sources + matcher 负责认。旧的顶层 item_sources 仍被解析器作为构造底材回退读取,但新配置请写 base_item_source。
二、方块侧(要判方块)。 MatchContext 只承载 ItemStack / ItemSourceRef / Player,没有 Block 字段,所以工位方块识别类配置无法用 matcher:Cooking 的 block_item_sources、stations.wok.heat_levels[]、lit_ / unlit_ 系列、stations.steamer.heat_item_sources。
GUI 渲染侧(gui/*.yml 的 slots.*.source)同理不支持 —— 它要按配置取出图标物品,不是判定。
配置示例
只要没摔过的钻石剑
item_sources:
- minecraft-diamond_sword
matcher:
type: component
component: damage
operator: absent排除已经附了魔的装备
matcher:
type: none_of
matchers:
- type: component
component: enchantments
operator: exists名字里带指定字样
matcher:
type: component
component: custom_name
operator: contains
value: 传说附魔种类数达标
matcher:
type: component
component: enchantments
path: levels
operator: size
value: 3三个条件里满足任意两个
matcher:
type: at_least
required_count: 2
matchers:
- type: component
component: enchantments
path: sharpness
operator: '>='
value: 5
- type: component
component: unbreakable
operator: exists
- type: component
component: rarity
operator: '=='
value: epic排查建议
matcher 配错时不中断加载,只是那一条恒不命中,所以要靠主动验证。
- 正反两面都测:既测"该通过的能过",也测"不该通过的会被拒"。
- 看启动日志:加载期被拒绝的条件都会打 warning,包括缺
type、未知type、在 matcher 内写物品源条件、未知 operator、单位组件写值比较、matcher不是映射结构。 - 逐条拆开:组合条件不命中时,把子条件一条条单独试,定位到具体哪条不成立。
- 先用 exists 探形状:不确定组件在物品上是什么形态时,先写
operator: exists确认组件在不在,再逐层加path。 - 开 Debug:CoreLib 的
debug.global_all或/corelib debug all on会输出更多匹配过程信息。