跳到主要内容

UnlockMethod 配置指南

本文档专门说明 RhythMC 当前解锁系统的设计思路、手动配置方式、运行时行为、字段含义、旧格式兼容规则、奖励发放方式,以及排查问题时应该看什么。

如果你以后要自己给 Arena、Song、Chart、收藏品、称号、前后缀之类的内容加解锁规则,优先看这份文档。

配套文档:

  • 开发者详解:docs/unlock-method-developer-guide.md
  • 模板速查版:docs/unlock-method-templates.md

一句话理解当前系统

RhythMC 现在有两套“配置入口”,但底层只走一套“执行模型”:

  • Arena、收藏品等对象,直接写通用 unlockMethod / unlock-method
  • Song / Chart,当前仍然以 SongManifest 里的旧字段为准:unlockSongunlockWorldunlockNetherunlockVoid
  • 不管入口长什么样,最终都会转换成统一的 CommonUnlockMethod,由 UnlockService 在运行时执行

另外,unlock-method 现在也支持直接写成数组,表示“多种解锁方式并存”。数组模式当前默认按“任意一种满足即可(ANY)”执行。

你可以把它理解成:

  • 配置层:你怎么写
  • 解析层:系统怎么把它变成规则对象
  • 执行层:系统什么时候检查、解锁后发什么

先记住四条原则

1. CommonUnlockMethod 不是 Arena 专属

它只是“通用解锁规则”的名字,不代表它只给 Arena 用。

当前已经适配或可适配的对象包括:

  • Arena
  • Song
  • Chart(也就是具体难度 RawLevel
  • 收藏品
  • 称号 / Tag
  • 前缀 / 后缀
  • 以后新增的任何 Unlockable

2. Song / Chart 现在优先使用旧格式

也就是说,给 Song / Chart 配解锁时,请优先写:

  • unlockSong
  • unlockWorld
  • unlockNether
  • unlockVoid

而不是把规则写到 .rmcc 顶层。

3. 规则命中不等于只有“可见”

规则命中后系统不只是“判断你能不能用”,还可能触发奖励发放,例如:

  • 给玩家加一个收藏品 Key
  • 自动设置玩家前缀
  • 自动设置玩家后缀
  • 自动切换默认场地

4. 运行时判断依赖上下文

同一条规则只有在对应事件发生时才有机会命中。例如:

  • player.login
  • player.arena.select
  • game.result

如果你的规则写得没问题,但根本没有对应事件触发,就不会解锁。


当前系统整体流程

下面是解锁从“写配置”到“实际生效”的完整链路。

通用流程

  1. 你把解锁规则写到 Arena、SongManifest 或后端收藏品配置里
  2. 反序列化阶段把这些配置解析成 CommonUnlockMethod
  3. 游戏运行时,UnlockService 在关键节点发布事件上下文
  4. UnlockService 遍历当前可检查对象,执行规则匹配
  5. 如果规则命中:
    • 该对象判定为已解锁
    • 如果配置了 grants,会执行奖励发放
  6. 一部分奖励会直接落到玩家数据里,例如 player.data.collections

当前已接入的运行时事件

  • player.login
  • player.arena.select
  • game.result

其中最常用的是 game.result,因为它会附带最多上下文,例如:

  • 当前 levelId
  • 当前 songId
  • 当前场地 arena
  • 本次成绩 acc
  • 本次准确率 accuracy
  • 是否通过 pass
  • 是否 FC / AP

规则到底写在哪

这一节只解决一个问题:你要改哪个文件。

Arena 的规则写在哪

写在 Arena 的 metadata.yml 里。

字段名支持:

  • unlock-method
  • unlockMethod

推荐写法:

unlock-method:
type: RESULT_GATE
mode: ALL
events:
- game.result
info:
levelId: 123
values:
pass: true
accuracy:
operator: GTE
value: 98

也支持数组写法:

unlock-method:
- type: VIP_GATE
events:
- player.login
player:
permissions.rhythmc:vip:
value: true
- type: PURCHASE_GATE
events:
- player.login
player:
wattHour:
operator: GTE
value: 500
grants:
- type: COLLECTION
target: rhythmc:title.shop-vip
- type: CONSUME_WATT_HOUR
target: 500

这表示:

  • 只要满足 VIP 权限解锁
  • 或者满足购买条件并扣 500 wattHour
  • 任意一种成立即可

还支持显式分组写法:

unlock-method:
mode: ALL
methods:
- type: PASS_GATE
events:
- game.result
values:
pass: true
- type: ACC_GATE
events:
- game.result
values:
accuracy:
operator: GTE
value: 98

这表示:

  • methods 里有多种解锁方式
  • 总层级 mode: ALL
  • 所以需要每一种方式都满足,目标才算解锁

适用场景:

  • 某地图要通关某谱面才开放
  • 某地图要求玩家达到某个条件后才能选
  • 某地图解锁后还要顺便发一个收藏品

补充行为:

  • ArenaSettingsMenuhide: true 的场地采用“已拥有才显示”:玩家若已拥有对应 rhythmc:assets.arena.<arenaName> Key,则仍会显示;否则不显示
  • 未解锁场地在 GUI 中会显示为“可解锁”或“可购买”
  • 点击未解锁场地时,GUI 会先尝试触发一次 player.arena.unlock_attempt 解锁;成功则直接解锁并选中,失败才会提示条件说明
  • Arena GUI 现在会按多行显示 toStringList() 结果;如果规则里包含 CONSUME_WATT_HOUR,还会单独高亮花费数值
  • 每个 Arena 解锁后都会自动写入一个收藏 Key:rhythmc:assets.arena.<arenaName>;之后只要玩家已有这个 Key,就视为该 Arena 已永久解锁
  • Song/Chart 也会按同样思路持久化:
    • Song: rhythmc:assets.song.<songId>
    • Chart: rhythmc:assets.chart.<songId>.wd / nr / ed / vd
  • 如果某首 Song 已解锁,那么它下面所有 level/chart 都视为已解锁

Song / Chart 的规则写在哪

写在 Song 的 manifest.yml 里,不写在 world.rmcc / nether.rmcc / void.rmcc 里。

当前旧字段如下:

  • unlockSong
  • unlockWorld
  • unlockNether
  • unlockVoid

示例:

unlockSong:
- type: regular

unlockWorld:
- type: permission
value: rhythmc:chart.world

unlockNether:
- type: money
value: 500

unlockVoid:
- type: songacc
song: thisDef
value: 98

含义:

  • unlockSong: 整首歌是否解锁,作用于 RawSong
  • unlockWorld: world.rmcc 这张 chart 是否解锁
  • unlockNether: nether.rmcc 这张 chart 是否解锁
  • unlockVoid: void.rmcc 这张 chart 是否解锁

注意:

  • 当前代码里还没有 unlockEnd
  • 所以 end.rmcc 现在没有旧格式专属字段
  • 如果你需要给 end.rmcc 单独控制解锁,需要继续补代码

收藏品 / 称号 / Tag 的规则写在哪

当前收藏品规则主要来自后端 /server/collections

也就是说:

  • 你不是在插件本地 YAML 里给收藏品写规则
  • 而是在后端收藏品定义里写 unlockMethod
  • 插件拉取后缓存并在本地执行

如果你要“手动新增一个收藏品规则”,通常要改的是:

  • 后端数据库里的收藏品配置
  • 或后端构造 /server/collections 响应的那部分数据

unlock-method 能不能写成 Array

可以。

当前支持下面两种写法:

单条写法

unlock-method:
type: CLEAR_LEVEL
events:
- game.result
values:
pass: true

数组写法

unlock-method:
- type: VIP_GATE
player:
permissions.rhythmc:vip:
value: true
- type: COST_GATE
player:
wattHour:
operator: GTE
value: 500
grants:
- type: COLLECTION
target: rhythmc:title.shop-item
- type: CONSUME_WATT_HOUR
target: 500

数组模式当前语义是:

  • 多个 method 组合成一个复合规则
  • 默认按 ANY 处理
  • 也就是任意一条 method 满足,即判定目标已解锁

分组写法

unlock-method:
mode: ALL
methods:
- type: METHOD_A
...
- type: METHOD_B
...

分组模式语义:

  • methods 里每一项都是一个完整的 unlock-method
  • 外层 mode 控制这些 method 之间如何组合
  • 支持:
    • mode: ALL
    • mode: ANY

推荐理解:

  • 数组写法 = 简写版,默认 ANY
  • mode + methods = 完整版,可显式指定组合方式

如果你想表达“多种途径任意一种都能解锁”,数组写法就很适合。

如果你想表达“同一种解锁方式里有多个子条件都要满足”,继续在单条 method 里用 mode: ALL 即可。

简单理解:

  • 一个 unlock-method 对象 = 一种解锁方式
  • 一个 unlock-method 数组 = 多种解锁方式
  • 一个 unlock-method: { mode, methods } 对象 = 多种解锁方式的显式组合

Arena / Collection 通用 unlockMethod 规则格式

这一节讲的是“新通用格式”,也就是 Arena、收藏品、以及以后任何走 CommonUnlockMethod.fromRaw(...) 的对象都能用的规则结构。

最简单的例子

unlock-method:
type: CLEAR_123
events:
- game.result
info:
levelId: 123
values:
pass: true

它表示:

  • 只在 game.result 事件里检查
  • 要求 levelId == 123
  • 要求 pass == true

完整结构示例

unlock-method:
type: VOID_MASTER
name: 虚空大师解锁
description: 在 100014 谱面达成 AP 后解锁
mode: ALL
defaultUnlocked: false
events:
- game.result
info:
levelId: 100014
arena: ancientcity
values:
allPerfect: true
accuracy:
operator: GTE
value: 99
player:
aptitude:
operator: GTE
value: 1200
grants:
- type: COLLECTION
target: rhythmc:title.void-master
- type: PLAYER_PREFIX
target: rhythmc:title.void-master

通用字段逐项说明

这一节尽量写得详细一些,方便你以后查字典。

type

作用:

  • 规则类型名 / 分类名
  • 更多是给人看、给日志看、给后续扩展看
  • 当前不会直接决定逻辑分支

推荐写法:

  • CLEAR_123
  • VOID_MASTER
  • ARENA_UNLOCK
  • COLLECTION_GATE

name

作用:

  • 规则显示名
  • 当前主要用于描述或未来 UI 展示

可写可不写。

description

作用:

  • 给菜单、调试、日志、提示文案用
  • 建议尽量写人能看懂的话

示例:

description: 通关 123 号谱面并达到 98% ACC

补充说明:

  • 当前系统支持把规则自动转成中文解锁提示
  • 如果你写了 description,它会作为说明语义的重要补充
  • 如果你根本不想暴露真实条件,请不要只依赖 description,而是直接配 hideCondition: true

mode

可选值:

  • ALL
  • ANY

含义:

  • ALL: 所有条件都要满足
  • ANY: 任意一个条件满足即可

示例:

mode: ALL

defaultUnlocked

含义:

  • 当规则本身没有条件时,是否默认解锁

一般不需要单独写,除非你故意想保留一个“空规则但默认解锁”的对象。

hideCondition / hide-condition

含义:

  • 隐藏真实解锁条件
  • 开启后,对外的解锁说明会直接显示“我也不知道”

示例:

hideCondition: true

适合场景:

  • 隐藏彩蛋条件
  • 不希望玩家提前知道具体门槛
  • 条件过于复杂,不适合直接展示

条件字段怎么用

通用规则主要用下面几组条件字段:

  • events
  • info
  • values
  • player
  • conditions / rules

events

作用:

  • 限定这条规则在哪种事件里才检查

示例:

events:
- game.result

也可以多个:

events:
- player.login
- game.result

如果写了多个,在 mode: ALL 下通常不太合理,因为一次上下文只会有一个当前事件。多数情况下:

  • events 最好只写一个
  • 如果写多个事件,搭配 mode: ANY 更符合直觉

info

作用:

  • 匹配当前事件附带的描述信息
  • 更适合“当前对象是谁”“当前关卡是谁”“当前场地是谁”这种上下文

示例:

info:
levelId: 123
arena: ancientcity

values

作用:

  • 匹配当前事件中的数值型或结果型字段
  • 常用于成绩、准确率、FC、AP、Combo 等

简单写法:

values:
pass: true
fullCombo: true

带操作符写法:

values:
accuracy:
operator: GTE
value: 98
maxCombo:
operator: GTE
value: 500

player

作用:

  • 匹配玩家当前快照信息
  • 用来做“与当前玩家状态相关”的限制

示例:

player:
aptitude:
operator: GTE
value: 1200
permissions.rhythmc:vip:
value: true

conditions / rules

作用:

  • info / values / player 这种分组写法不够用时,可以直接写显式条件数组

示例:

conditions:
- trigger: EVENT
key: event
operator: EQUALS
value: game.result
- trigger: INFO
key: levelId
operator: EQUALS
value: 123
- trigger: VALUE
key: accuracy
operator: GTE
value: 98

适合场景:

  • 想把不同来源的条件并列得更清楚
  • 需要做非常显式的规则配置

支持的操作符

当前支持这些操作符:

  • EXISTS
  • EQUALS
  • NOT_EQUALS
  • GREATER_THAN
  • GREATER_OR_EQUALS
  • LESS_THAN
  • LESS_OR_EQUALS
  • CONTAINS
  • IN
  • MATCHES

常用简写 / 别名也支持一部分,例如:

  • EQ -> EQUALS
  • GT -> GREATER_THAN
  • GTE -> GREATER_OR_EQUALS
  • LT -> LESS_THAN
  • LTE -> LESS_OR_EQUALS
  • REGEX -> MATCHES

示例 1:数值比较

values:
accuracy:
operator: GTE
value: 98

示例 2:字符串匹配

player:
playerType:
operator: EQUALS
value: ONLINE

示例 3:正则匹配

player:
displayName:
operator: MATCHES
value: ".*Master.*"

示例 4:集合包含

player:
collections:
operator: CONTAINS
value: rhythmc:title.beta

不过对于当前系统,更推荐直接写路径形式:

player:
collections.rhythmc:title.beta: true

grants 奖励发放怎么写

规则命中后,如果你不仅想“允许使用”,还想“自动发东西”,就可以写 grantgrants

当前内置支持的发放类型

  • COLLECTION
  • PLAYER_PREFIX
  • PLAYER_SUFFIX
  • DEFAULT_ARENA
  • CONSUME_WATT_HOUR

COLLECTION

作用:

  • 把某个收藏品 Key 放进玩家 PlayerData.collections

示例:

grants:
- type: COLLECTION
target: rhythmc:title.clear-123

PLAYER_PREFIX

作用:

  • 自动把某个收藏品 Key 设为玩家前缀
  • 同时也会确保这个 Key 已经加入玩家收藏

示例:

grants:
- type: PLAYER_PREFIX
target: rhythmc:title.clear-123

PLAYER_SUFFIX

作用:

  • 自动把某个收藏品 Key 设为玩家后缀
  • 同时也会确保这个 Key 已经加入玩家收藏

DEFAULT_ARENA

作用:

  • 命中后直接把某个场地设置为玩家默认 Arena

示例:

grants:
- type: DEFAULT_ARENA
target: ancientcity

CONSUME_WATT_HOUR

作用:

  • 命中后扣除玩家当前 wattHour
  • target 直接写要扣掉的数值

示例:

grants:
- type: CONSUME_WATT_HOUR
target: 500

说明:

  • 也支持别名:CONSUME_MONEYMONEY_COSTCOST
  • 当前实际扣的是 PlayerData.wattHour
  • 为避免同一对象在同一会话里重复扣费,系统会按“对象 + grant”做一次性会话去重
  • 如果你要做长期永久购买,建议配合某种持久化结果一起使用,例如同时发一个 COLLECTION 作为拥有凭证

一个完整发奖例子

unlock-method:
type: AP_REWARD
mode: ALL
events:
- game.result
info:
levelId: 100014
values:
allPerfect: true
grants:
- type: COLLECTION
target: rhythmc:title.ap-100014
- type: PLAYER_PREFIX
target: rhythmc:title.ap-100014
- type: DEFAULT_ARENA
target: ancientcity
- type: CONSUME_WATT_HOUR
target: 500

命中后会发生:

  1. 玩家完成条件
  2. 系统判定规则命中
  3. 系统把 rhythmc:title.ap-100014 加到玩家收藏里
  4. 系统把该 Key 设为玩家前缀
  5. 系统把默认场地改为 ancientcity
  6. 系统额外扣除 500 wattHour

SongManifest 旧格式详细说明

这一节很重要,因为 Song / Chart 目前是优先走旧格式的。

旧格式一览

字段如下:

  • unlockSong
  • unlockWorld
  • unlockNether
  • unlockVoid

它们的值都是一个列表,每一项是一个 Map,最少要有 type

例如:

unlockWorld:
- type: permission
value: rhythmc:vip.world
- type: money
value: 500

当前系统会把这些旧配置转换成统一规则,并默认按“全部满足”处理。

也就是说,上面的例子表示:

  • 需要同时有权限 rhythmc:vip.world
  • 并且 wattHour >= 500

旧类型:regular

含义:

  • 直接解锁
  • 等于“这条对象没有门槛”

示例:

unlockSong:
- type: regular

适用场景:

  • 你想保留字段结构,但暂时不做限制
  • 或者只是为了保持 manifest 各难度结构一致

旧类型:permission

含义:

  • 玩家必须拥有某个权限 Key

通常写法:

unlockWorld:
- type: permission
value: rhythmc:chart.world

当前实现等价于:

  • player.permissions.rhythmc:chart.world == true

支持字段:

  • value
  • permission
  • key

如果你以后自己写,推荐只用 value,最稳定。

旧类型:money

含义:

  • 玩家 wattHour 必须达到阈值

示例:

unlockNether:
- type: money
value: 500

当前实现等价于:

  • player.wattHour >= 500

说明:

  • 这里的“money”历史上是老字段命名
  • 现在实际接的是 PlayerData.wattHour

旧类型:songacc

含义:

  • 玩家某个谱面的历史最好成绩必须达到阈值

示例:

unlockVoid:
- type: songacc
song: thisDef
value: 98

字段说明:

  • song: 目标谱面的 levelId,或者 thisDef
  • value: 百分比阈值,例如 98 表示 98%

当前实现会转换成:

  • player.songRecords.<levelId>.acc >= value * 10000

也就是说:

  • 98 -> 980000
  • 99.5 -> 995000

song 字段当前支持什么

  • 直接写数字 levelId,例如 100014
  • thisDef,表示当前这个难度自身

当前暂不建议依赖什么

你在旧示例里可能会看到:

  • sha1:lvl
  • uuid

但当前本地实现并没有真正解析这些形式。现在稳定可用的只有:

  • 数字 levelId
  • thisDef

如果你要稳定落地,请只用这两种。


运行时可用上下文详细说明

这部分是写通用 unlock-method 时最重要的参考。

事件列表

当前已接入:

  • player.login
  • player.arena.select
  • game.result

player.login 时你能用什么

通常适合用:

  • player.permissions.*
  • player.aptitude
  • player.wattHour
  • player.collections.*

典型用途:

  • 登录即发 Beta 标识
  • 登录时根据玩家历史状态补发收藏品
  • 登录时检查权限型解锁

player.arena.select 时你能用什么

常见 info

  • arena
  • displayName

适合用途:

  • 记录玩家是否选过某地图
  • 做某些菜单上的联动限制

game.result 时你能用什么

info 常见字段

  • levelId
  • songId
  • songName
  • arena
  • difficulty

values 常见字段

  • acc
  • accuracy
  • pass
  • fullCombo
  • allPerfect
  • maxCombo
  • combo
  • perfect
  • great
  • miss
  • rival
  • win

适合拿来做什么

  • 通关某谱面解锁
  • 达到 98% 准度解锁
  • FC / AP 解锁
  • 对战获胜解锁

player 快照里常用字段

当前快照里可稳定使用的字段包括:

  • player.uid
  • player.uuid
  • player.playerType
  • player.displayName
  • player.online
  • player.arena
  • player.language
  • player.speed
  • player.volume
  • player.continueEnabled
  • player.aptitude
  • player.wattHour
  • player.totalGames
  • player.prefix
  • player.suffix
  • player.permissions.<key>
  • player.collections.<key>
  • player.songRecords.<levelId>.acc
  • player.songRecords.<levelId>.pass
  • player.songRecords.<levelId>.fullCombo
  • player.songRecords.<levelId>.allPerfect
  • player.songRecords.<levelId>.playCount

点路径怎么理解

规则里经常会看到这种写法:

  • permissions.rhythmc:vip
  • collections.rhythmc:title.beta
  • songRecords.100014.acc

它们本质上是“点路径访问”规则:

  • songRecords.100014.acc 表示去 player.songRecords 里找键 100014,再取它的 acc

所以你以后自己写路径时,要遵守这个思路:

  • 对象.对象.字段
  • MapKey.子字段
  • 数字也可以作为中间路径

我以后手动加一个解锁,推荐工作流

这一节按实际操作顺序来写。

场景 A:给 Arena 加解锁

推荐步骤:

  1. 找到对应 Arena 目录
  2. 打开 metadata.yml
  3. 新增 unlock-method
  4. 先决定触发事件,一般是 game.result
  5. 再补 info / values / player
  6. 如果命中后要发东西,再补 grants
  7. 重载资源 / 重启后验证

推荐模板:

unlock-method:
type: ARENA_UNLOCK
mode: ALL
events:
- game.result
info:
levelId: 123
values:
pass: true

场景 B:给 Song / Chart 加解锁

推荐步骤:

  1. 打开该 Song 的 manifest.yml
  2. 整首歌门槛写 unlockSong
  3. world 难度门槛写 unlockWorld
  4. nether 难度门槛写 unlockNether
  5. void 难度门槛写 unlockVoid
  6. 优先使用旧格式支持好的类型:regular / permission / money / songacc
  7. 如果你要的是 end 难度专属门槛,先不要硬写,当前还没支持 unlockEnd

场景 C:给收藏品 / 称号加解锁

推荐步骤:

  1. 找到后端该收藏品的定义来源
  2. 增加或修改 unlockMethod
  3. 先写清楚触发事件
  4. 再写条件字段
  5. 如果命中后要自动装备前后缀或默认场地,补 grants
  6. 重拉 /server/collections 后验证

速查表

这一节是给以后复制粘贴用的。

通关某谱面解锁 Arena

unlock-method:
type: CLEAR_LEVEL
mode: ALL
events:
- game.result
info:
levelId: 123
values:
pass: true

达到 98% 准度解锁 Arena

unlock-method:
type: HIGH_ACC
mode: ALL
events:
- game.result
info:
levelId: 123
values:
accuracy:
operator: GTE
value: 98

拥有权限时解锁某个 chart

unlockWorld:
- type: permission
value: rhythmc:vip.world

玩家电池值达到 2000 时解锁 chart

unlockNether:
- type: money
value: 2000

玩家本难度达到 99% 时解锁 void

unlockVoid:
- type: songacc
song: thisDef
value: 99

AP 后发称号并自动装备前缀

unlock-method:
type: AP_REWARD
mode: ALL
events:
- game.result
info:
levelId: 100014
values:
allPerfect: true
grants:
- type: COLLECTION
target: rhythmc:title.ap-100014
- type: PLAYER_PREFIX
target: rhythmc:title.ap-100014

登录后按权限补发收藏品

unlock-method:
type: LOGIN_REWARD
mode: ALL
events:
- player.login
player:
permissions.rhythmc:beta:
value: true
grants:
- type: COLLECTION
target: rhythmc:title.beta

常见误区

误区 1:把 Song / Chart 的规则写到 .rmcc 顶层

当前不推荐。

正确做法:

  • 写回 manifest.ymlunlockSong / unlockWorld / unlockNether / unlockVoid

误区 2:想给 end.rmcc 配旧格式,却直接写 unlockEnd

当前代码还没支持 unlockEnd

误区 3:songacc.value 直接写 980000

不需要。

旧格式里应该写百分比:

  • 98
  • 不写 980000

误区 4:写了规则却从来没有触发对应事件

例如你写:

  • events: [game.result]

但你只在登录后检查,那当然不会命中。

误区 5:把收藏品解锁理解成“只是显示可用”

不是。

当前收藏品命中后会直接把 Key 写进:

  • player.data.collections

后续规则还可以继续依赖:

  • player.collections.<key>

排查问题时怎么查

如果你发现“规则写了但是不生效”,建议按下面顺序排查。

1. 先确认写对位置了

  • Arena -> metadata.ymlunlock-method
  • Song / Chart -> manifest.yml 的旧字段
  • 收藏品 -> 后端 /server/collections 下发配置

2. 再确认字段名写对了

常见拼错:

  • unlock-method 写成 unlock_method
  • unlockWorld 写成 unlockworld
  • accuracy 写成 accuary

3. 确认事件对不对

例如:

  • 你写的是 game.result
  • 但你拿登录后的状态去测试

那就不会触发。

4. 确认值单位对不对

尤其是:

  • songacc.value 要写百分比,例如 98
  • money.value 对应的是 wattHour

5. 确认目标 Key 是否真实存在

特别是 grants

  • COLLECTION / PLAYER_PREFIX / PLAYER_SUFFIXtarget 最好写完整 Key
  • 例如 rhythmc:title.beta

6. 确认玩家历史数据里真的有对应字段

例如你写:

  • player.songRecords.100014.acc

但玩家根本没打过这个谱面,那它当然不满足。


当前实现限制

这部分写清楚,避免你误判是自己配置错了。

  • SongManifest 旧格式目前只内置了 regularpermissionmoneysongacc
  • end.rmcc 还没有旧格式专属字段 unlockEnd
  • 收藏品规则仍主要来自后端下发,不是插件本地文件
  • songacc.song 当前稳定支持数字 levelId 和 thisDef
  • sha1 / uuid 这类老注释里的写法,目前本地实现并没有完整支持

以后如果你想扩展,最推荐怎么做

如果后面你还要继续丰富解锁系统,推荐按这个顺序扩展。

第一优先级

  • SongManifestunlockEnd
  • 给旧格式补更多类型,例如:
    • aptitude
    • collection
    • playcount

第二优先级

  • grants 补更多目标类型,例如:
    • TITLE
    • NOTE_SKIN
    • ARENA
    • RESOURCE_PACK

第三优先级

  • 增加更丰富的运行时事件
  • 增加解锁成功提示
  • 增加调试日志 / 命令,直接查看某玩家为什么没解锁

最后给一个配置建议

如果你以后只是想“快速加一个能用的解锁”,建议遵循下面这套习惯:

  • Arena / 收藏品:优先用通用 unlock-method
  • Song / Chart:优先用 SongManifest 旧字段
  • 条件尽量先写简单:
    • 先事件
    • 再一个主条件
    • 最后再补额外限制
  • description 尽量写清楚,后面自己排查会很省时间
  • grants.target 尽量写完整 Key,不要写模糊简称

如果你以后不确定某个解锁到底该写哪种格式,就按这条判断:

  • 是 Song / Chart -> 写旧字段
  • 不是 Song / Chart -> 写通用 unlock-method