JudgmentAdjuster
这一页是签名与行为参考。在计分前改写判定结果。直接做一遍:教程 · 添加判定等级。
判定结果改写钩子:在判定已产生、计分/连击/特效之前介入,可整体替换 JudgeResult。在 onPreInit 内经 ctx.registries().judgmentAdjusters() 注册,多个调整器按注册顺序链式执行,前一个的输出作为后一个的输入。
这是官方唯一的「判定干预」通道;NoteJudgedEvent / ComboBreakEvent 在判定落地后才投递,且不可取消。
方法速览
| 方法 | 用途 |
|---|---|
adjust(JudgeResult, GameplayContext) | 改写本次判定结果,返回新结果 |
完整签名
public interface JudgmentAdjuster {
JudgeResult adjust(JudgeResult input, GameplayContext ctx);
}adjust(JudgeResult input, GameplayContext ctx)
签名:
JudgeResult adjust(JudgeResult input, GameplayContext ctx);行为:input 是上一步的判定结果(链首为判定引擎产出的原始结果),ctx 是计分前的游玩状态只读快照。返回改写后的结果;返回 null 表示本调整器不生效——宿主跳过它并限频告警(日志明示 SHALL NOT 返回 null),链用上一步的结果继续。抛异常同样被隔离跳过,链继续,一个坏调整器不影响其它模组。
链按注册顺序执行,每次判定一次(HOLD 起点与终点各一次)。链结束后若最终判定仍是内置 seecmd:none,宿主用 JudgeResults.withJudgment 自动改写为内置 seecmd:miss;自定义判定等级不受此转换影响。
改写 judgeTime() 会触发宿主重录准度分布(撤销旧时刻、按新时刻记账),因此把时间改成 ctx.musicTimeMs() 等值不会出现同一次判定算两次。改写 judgment() 为自定义等级时,内部枚举会回退 NONE,但注册的 JudgmentKindDefinition 仍驱动 scoreValue / accuracyWeight / comboBehavior / isMissKind 与特效颜色。
确定性契约(强制):判定链路(JudgmentAdjuster.adjust / NoteTypeDefinition.onJudge / onJudgeTick)位于可重演路径上——暂停恢复倒带会对过去的时间窗逐帧正向重演,多帧率下同一输入也必须产出同一结果。实现必须:
- 禁随机:不使用
java.util.Random及任何非种子化随机源; - 禁墙钟:不读
System.currentTimeMillis()/System.nanoTime()(时间一律取回调入参的 τ / delta); - 禁网络 / 禁跨帧可变共享态写入:外部世界在重演窗口内不会重放;
- 输出只由入参 + 本模组按 τ 重建的纯函数状态决定。
违反契约的表现是「倒带后判定结果与首演不一致」;宿主无法拦截,属于模组自身缺陷。
示例(把所有 PERFECT 降为 GREAT;内置等级实例在 onPreInit 内取出):
private static final ResourceLocation PERFECT = ResourceLocation.of("seecmd", "perfect");
private static final ResourceLocation GREAT = ResourceLocation.of("seecmd", "great");
@Override
public void onPreInit(ModContext ctx) {
JudgmentKindDefinition perfect = ctx.registries().judgmentKinds().get(PERFECT).orElse(null);
JudgmentKindDefinition great = ctx.registries().judgmentKinds().get(GREAT).orElse(null);
ctx.registries().judgmentAdjusters().register(
RegistryKey.of("my_mod", "perfect_to_great", JudgmentAdjuster.class),
(input, gameplayCtx) -> input.judgment() == perfect
? JudgeResults.withJudgment(input, great)
: input);
}线程:源码未声明额外线程约束。宿主在判定落地的同一线程(游玩/渲染线程)内同步调用;不要从自有线程调用 adjust。
生命周期阶段:onPreInit 注册;运行期每个音符判定完成后、计分前由宿主按注册顺序调用。
稳定性:2.0 起冻结:2.x 内只增不破(禁止删签名/改签名/改语义)。