API 参考 / 常见错误
常见错误
- 把注册写在
onInit里:注册窗口只在onPreInit打开,宿主在装配阶段完成全部模组的注册回调后立即冻结所有注册表,此后的写入(含渲染回调、热启用模组的onPreInit)都会抛RegistryFrozenException。正确做法是把注册放回onPreInit,不要捕获该异常来规避。 - 用裸字符串当注册键:注册键一律是
RegistryKey(ResourceLocation+ 值类型),旧 API 的裸字符串 id 已被取代;Registry也没有按字符串查询的重载。 - 命名空间或路径写了大写、下划线以外的符号或超长:
namespace只允许[a-z0-9_.-]、长度 1~64,path只允许[a-z0-9/._-]、长度 1~255,且强制小写;ResourceLocation.of/parse在构造期就会抛IllegalArgumentException。 parse里的字符串格式错误:传入空串、或写成含多个:的字符串(如a:b:c)都会抛异常;不含:时(如slide)会落到默认命名空间seecmd,若本意是自己的模组命名空间就会注册到错误位置。- 重复注册同一个
ResourceLocation:不会静默覆盖或忽略,而是抛IllegalArgumentException;同一个 key 的第二次注册即失败,且接口不提供unregister用于回退。 - 键与值的类型不匹配:
RegistryKey<T>的泛型类型必须与目标注册表的内容类型一致(如把游戏模式注册进noteTypes()),否则在编译期即被拦下;向register传 null 键、null 值或 null 类型同样抛IllegalArgumentException。 - 把「收紧判定」写成改判定窗口:多模组策略合并时逐字段取最大,较窄的窗口会被更宽者覆盖;但内置默认
seecmd:default不参与 max,单个模组注册的窗口会直接生效(可宽可窄)。要与其它模组竞争地收紧,或想无条件收紧判定结果,应通过JudgmentAdjuster改写判定等级。 - 以为
JudgmentAdjuster返回null能「取消判定」:null只会让该调整器被跳过(限频告警),链继续使用上一步结果;最终判定仍会计分并投递事件。要改变结果必须返回新的JudgeResult。 - 用
NoteJudgedEvent/ComboBreakEvent干预判定:两者都是判定落地后的纯通知、不可取消;要阻止一次 MISS 或改写等级,只能在JudgmentAdjuster链里做。 - 在位姿 adjuster 或实体位姿里使用随机数 / 墙钟 / 网络:位姿链与实体位姿位于倒带可重演路径上,宿主会逐帧正向重演;这些不可重演输入会造成「倒带后画面与首演不一致」。时间一律取
ctx.musicTimeMs()或GameplayTickEvent#musicTimeMs(),状态按 τ 重建。 - 跨帧保存
TrackPose/CameraPose/EntityPose引用:三者都是宿主复用实例,下一帧读到的是新值或被清空;需要记忆的状态放在模组自持对象里,每帧重写。 - 以为位姿 adjuster 写相机等于「永久接管相机」:链尾写回不改变接管标志,接管只由
GameStateAccess#setCameraPose建立、由setCameraPoseAuto()/ 倒带 / 新谱面复位清除;链本身每帧从当前输入重跑。接管期间轨道 tilt 作为内容旋转与相机位姿正交合成,不再归一进 pitch。 - 在 tick 分发期间调用
mountTo*/destroyEntity,或把entityPose调用放到 tick 之外:前者被拒绝(限频告警),后者返回null。装配类操作放GameStartEvent等非 tick 上下文,位姿写入放GameplayTickEvent.Pre/Post。 - 假设
musicTimeMs()只增:暂停恢复的倒带重演窗口内宿主时间先倒流再正向重演(非单调),且窗口内不产生新的计分判定。模组按state += dt累积的动画 / 计时器会与宿主时间脱节(宿主时间在回退、你的+= dt仍在前进)。请检查GameplayContext#isRewinding()或GameplayTickEvent#rewinding():窗口内应跳过 / 重建累计状态,或改为按 τ 求值(订阅RewindEvent的BEGIN即窗口开始)。