UnlockMethod 配置指南
本文档专门说明 RhythMC 当前解锁系统的设计思路、手动配置方式、运行时行为、字段含义、旧格式兼容规则、奖励发放方式,以及排查问题时应该看什么。
如果你以后要自己给 Arena、Song、Chart、收藏品、称号、前后缀之类的内容加解锁规则,优先看这份文档。
配套文档:
- 开发者详解:
docs/unlock-method-developer-guide.md - 模板速查版:
docs/unlock-method-templates.md
一句话理解当前系统
RhythMC 现在有两套“配置入口”,但底层只走一套“执行模型”:
- Arena、收藏品等对象,直接写通用
unlockMethod/unlock-method - Song / Chart,当前仍然以
SongManifest里的旧字段为准:unlockSong、unlockWorld、unlockNether、unlockVoid - 不管入口长什么样,最终都会转换成统一的
CommonUnlockMethod,由UnlockService在运行时执行
另外,unlock-method 现在也支持直接写成数组,表示“多种解锁方式并存”。数组模式当前默认按“任意一种满足即可(ANY)”执行。
你可以把它理解成:
- 配置层:你怎么写
- 解析层:系统怎么把它变成规则对象
- 执行层:系统什么时候检查、解锁后发什么
先记住四条原则
1. CommonUnlockMethod 不是 Arena 专属
它只是“通用解锁规则”的名字,不代表它只给 Arena 用。
当前已经适配或可适配的对象包括:
- Arena
- Song
- Chart(也就是具体难度
RawLevel) - 收藏品
- 称号 / Tag
- 前缀 / 后缀
- 以后新增的任何
Unlockable
2. Song / Chart 现在优先使用旧格式
也就是说,给 Song / Chart 配解锁时,请优先写:
unlockSongunlockWorldunlockNetherunlockVoid
而不是把规则写到 .rmcc 顶层。
3. 规则命中不等于只有“可见”
规则命中后系统不只是“判断你能不能用”,还可能触发奖励发放,例如:
- 给玩家加一个收藏品 Key
- 自动设置玩家前缀
- 自动设置玩家后缀
- 自动切换默认场地
4. 运行时判断依赖上下文
同一条规则只有在对应事件发生时才有机会命中。例如:
player.loginplayer.arena.selectgame.result
如果你的规则写得没问题,但根本没有对应事件触发,就不会解锁。
当前系统整体流程
下面是解锁从“写配置”到“实际生效”的完整链路。
通用流程
- 你把解锁规则写到 Arena、SongManifest 或后端收藏品配置里
- 反序列化阶段把这些配置解析成
CommonUnlockMethod - 游戏运行时,
UnlockService在关键节点发布事件上下文 UnlockService遍历当前可检查对象,执行规则匹配- 如果规则命中:
- 该对象判定为已解锁
- 如果配置了
grants,会执行奖励发放
- 一部分奖励会直接落到玩家数据里,例如
player.data.collections
当前已接入的运行时事件
player.loginplayer.arena.selectgame.result
其中最常用的是 game.result,因为它会附带最多上下文,例如:
- 当前
levelId - 当前
songId - 当前场地
arena - 本次成绩
acc - 本次准确率
accuracy - 是否通过
pass - 是否 FC / AP
规则到底写在哪
这一节只解决一个问题:你要改哪个文件。
Arena 的规则写在哪
写在 Arena 的 metadata.yml 里。
字段名支持:
unlock-methodunlockMethod
推荐写法:
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 - 所以需要每一种方式都满足,目标才算解锁
适用场景:
- 某地图要通关某谱面才开放
- 某地图要求玩家达到某个条件后才能选
- 某地图解锁后还要顺便发一个收藏品
补充行为:
ArenaSettingsMenu对hide: 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:
- 如果某首 Song 已解锁,那么它下面所有 level/chart 都视为已解锁
Song / Chart 的规则写在哪
写在 Song 的 manifest.yml 里,不写在 world.rmcc / nether.rmcc / void.rmcc 里。
当前旧字段如下:
unlockSongunlockWorldunlockNetherunlockVoid
示例:
unlockSong:
- type: regular
unlockWorld:
- type: permission
value: rhythmc:chart.world
unlockNether:
- type: money
value: 500
unlockVoid:
- type: songacc
song: thisDef
value: 98
含义:
unlockSong: 整首歌是否解锁,作用于RawSongunlockWorld: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: ALLmode: 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_123VOID_MASTERARENA_UNLOCKCOLLECTION_GATE
name
作用:
- 规则显示名
- 当前主要用于描述或未来 UI 展示
可写可不写。
description
作用:
- 给菜单、调试、日志、提示文案用
- 建议尽量写人能看懂的话
示例:
description: 通关 123 号谱面并达到 98% ACC
补充说明:
- 当前系统支持把规则自动转成中文解锁提示
- 如果你写了
description,它会作为说明语义的重要补充 - 如果你根本不想暴露真实条件,请不要只依赖
description,而是直接配hideCondition: true
mode
可选值:
ALLANY
含义:
ALL: 所有条件都要满足ANY: 任意一个条件满足即可
示例:
mode: ALL
defaultUnlocked
含义:
- 当规则本身没有条件时,是否默认解锁
一般不需要单独写,除非你故意想保留一个“空规则但默认解锁”的对象。
hideCondition / hide-condition
含义:
- 隐藏真实解锁条件
- 开启后,对外的解锁说明会直接显示“我也不知道”
示例:
hideCondition: true
适合场景:
- 隐藏彩蛋条件
- 不希望玩家提前知道具体门槛
- 条件过于复杂,不适合直接展示
条件字段怎么用
通用规则主要用下面几组条件字段:
eventsinfovaluesplayerconditions/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
适合场景:
- 想把不同来源的条件并列得更清楚
- 需要做非常显式的规则配置
支持的操作符
当前支持这些操作符:
EXISTSEQUALSNOT_EQUALSGREATER_THANGREATER_OR_EQUALSLESS_THANLESS_OR_EQUALSCONTAINSINMATCHES
常用简写 / 别名也支持一部分,例如:
EQ->EQUALSGT->GREATER_THANGTE->GREATER_OR_EQUALSLT->LESS_THANLTE->LESS_OR_EQUALSREGEX->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 奖励发放怎么写
规则命中后,如果你不仅想“允许使用”,还想“自动发东西”,就可以写 grant 或 grants。
当前内置支持的发放类型
COLLECTIONPLAYER_PREFIXPLAYER_SUFFIXDEFAULT_ARENACONSUME_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_MONEY、MONEY_COST、COST - 当前实际扣的是
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
命中后会发生:
- 玩家完成条件
- 系统判定规则命中
- 系统把
rhythmc:title.ap-100014加到玩家收藏里 - 系统把该 Key 设为玩家前缀
- 系统把默认场地改为
ancientcity - 系统额外扣除 500
wattHour
SongManifest 旧格式详细说明
这一节很重要,因为 Song / Chart 目前是优先走旧格式的。
旧格式一览
字段如下:
unlockSongunlockWorldunlockNetherunlockVoid
它们的值都是一个列表,每一项是一个 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
支持字段:
valuepermissionkey
如果你以后自己写,推荐只用 value,最稳定。
旧类型:money
含义:
- 玩家
wattHour必须达到阈值
示例:
unlockNether:
- type: money
value: 500
当前实现等价于:
player.wattHour >= 500
说明:
- 这里的“money”历史上是老字段命名
- 现在实际接的是
PlayerData.wattHour
旧类型:songacc
含义:
- 玩家某个谱面的历史最好成绩必须达到阈值
示例:
unlockVoid:
- type: songacc
song: thisDef
value: 98
字段说明:
song: 目标谱面的 levelId,或者thisDefvalue: 百分比阈值,例如98表示 98%
当前实现会转换成:
player.songRecords.<levelId>.acc >= value * 10000
也就是说:
98->98000099.5->995000
song 字段当前支持什么
- 直接写数字 levelId,例如
100014 - 写
thisDef,表示当前这个难度自身
当前暂不建议依赖什么
你在旧示例里可能会看到:
sha1:lvluuid
但当前本地实现并没有真正解析这些形式。现在稳定可用的只有:
- 数字 levelId
thisDef
如果你要稳定落地,请只用这两种。
运行时可用上下文详细说明
这部分是写通用 unlock-method 时最重要的参考。
事件列表
当前已接入:
player.loginplayer.arena.selectgame.result
player.login 时你能用什么
通常适合用:
player.permissions.*player.aptitudeplayer.wattHourplayer.collections.*
典型用途:
- 登录即发 Beta 标识
- 登录时根据玩家历史状态补发收藏品
- 登录时检查权限型解锁
player.arena.select 时你能用什么
常见 info:
arenadisplayName
适合用途:
- 记录玩家是否选过某地图
- 做某些菜单上的联动限制
game.result 时你能用什么
info 常见字段
levelIdsongIdsongNamearenadifficulty
values 常见字段
accaccuracypassfullComboallPerfectmaxCombocomboperfectgreatmissrivalwin
适合拿来做什么
- 通关某谱面解锁
- 达到 98% 准度解锁
- FC / AP 解锁
- 对战获胜解锁
player 快照里常用字段
当前快照里可稳定使用的字段包括:
player.uidplayer.uuidplayer.playerTypeplayer.displayNameplayer.onlineplayer.arenaplayer.languageplayer.speedplayer.volumeplayer.continueEnabledplayer.aptitudeplayer.wattHourplayer.totalGamesplayer.prefixplayer.suffixplayer.permissions.<key>player.collections.<key>player.songRecords.<levelId>.accplayer.songRecords.<levelId>.passplayer.songRecords.<levelId>.fullComboplayer.songRecords.<levelId>.allPerfectplayer.songRecords.<levelId>.playCount
点路径怎么理解
规则里经常会看到这种写法:
permissions.rhythmc:vipcollections.rhythmc:title.betasongRecords.100014.acc
它们本质上是“点路径访问”规则:
songRecords.100014.acc表示去player.songRecords里找键100014,再取它的acc
所以你以后自己写路径时,要遵守这个思路:
- 对象.对象.字段
- MapKey.子字段
- 数字也可以作为中间路径
我以后手动加一个解锁,推荐工作流
这一节按实际操作顺序来写。
场景 A:给 Arena 加解锁
推荐步骤:
- 找到对应 Arena 目录
- 打开
metadata.yml - 新增
unlock-method - 先决定触发事件,一般是
game.result - 再补
info/values/player - 如果命中后要发东西,再补
grants - 重载资源 / 重启后验证
推荐模板:
unlock-method:
type: ARENA_UNLOCK
mode: ALL
events:
- game.result
info:
levelId: 123
values:
pass: true
场景 B:给 Song / Chart 加解锁
推荐步骤:
- 打开该 Song 的
manifest.yml - 整首歌门槛写
unlockSong - world 难度门槛写
unlockWorld - nether 难度门槛写
unlockNether - void 难度门槛写
unlockVoid - 优先使用旧格式支持好的类型:
regular/permission/money/songacc - 如果你要的是 end 难度专属门槛,先不要硬写,当前还没支持
unlockEnd
场景 C:给收藏品 / 称号加解锁
推荐步骤:
- 找到后端该收藏品的定义来源
- 增加或修改
unlockMethod - 先写清楚触发事件
- 再写条件字段
- 如果命中后要自动装备前后缀或默认场地,补
grants - 重拉
/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.yml的unlockSong/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.yml的unlock-method - Song / Chart ->
manifest.yml的旧字段 - 收藏品 -> 后端
/server/collections下发配置
2. 再确认字段名写对了
常见拼错:
unlock-method写成unlock_methodunlockWorld写成unlockworldaccuracy写成accuary
3. 确认事件对不对
例如:
- 你写的是
game.result - 但你拿登录后的状态去测试
那就不会触发。
4. 确认值单位对不对
尤其是:
songacc.value要写百分比,例如98money.value对应的是wattHour
5. 确认目标 Key 是否真实存在
特别是 grants:
COLLECTION/PLAYER_PREFIX/PLAYER_SUFFIX的target最好写完整 Key- 例如
rhythmc:title.beta
6. 确认玩家历史数据里真的有对应字段
例如你写:
player.songRecords.100014.acc
但玩家根本没打过这个谱面,那它当然不满足。
当前实现限制
这部分写清楚,避免你误判是自己配置错了。
SongManifest旧格式目前只内置了regular、permission、money、songaccend.rmcc还没有旧格式专属字段unlockEnd- 收藏品规则仍主要来自后端下发,不是插件本地文件
songacc.song当前稳定支持数字 levelId 和thisDefsha1/uuid这类老注释里的写法,目前本地实现并没有完整支持
以后如果你想扩展,最推荐怎么做
如果后面你还要继续丰富解锁系统,推荐按这个顺序扩展。
第一优先级
- 给
SongManifest补unlockEnd - 给旧格式补更多类型,例如:
aptitudecollectionplaycount
第二优先级
- 给
grants补更多目标类型,例如:TITLENOTE_SKINARENARESOURCE_PACK
第三优先级
- 增加更丰富的运行时事件
- 增加解锁成功提示
- 增加调试日志 / 命令,直接查看某玩家为什么没解锁
最后给一个配置建议
如果你以后只是想“快速加一个能用的解锁”,建议遵循下面这套习惯:
- Arena / 收藏品:优先用通用
unlock-method - Song / Chart:优先用
SongManifest旧字段 - 条件尽量先写简单:
- 先事件
- 再一个主条件
- 最后再补额外限制
description尽量写清楚,后面自己排查会很省时间grants.target尽量写完整 Key,不要写模糊简称
如果你以后不确定某个解锁到底该写哪种格式,就按这条判断:
- 是 Song / Chart -> 写旧字段
- 不是 Song / Chart -> 写通用
unlock-method