Skip to content

KubeJS Custom Types zh

hp-1e edited this page Oct 25, 2026 · 1 revision

English | 中文

自定义类型与费用

类型 ID 必须带命名空间,例如 pack:quest_gate。每种类型最多注册 128 个。

每次重建图谱时类型都会重新注册,不会累积。注册结束后的 type 对象不能再修改。

校验、检测、说明和费用回调必须是纯计算:不能发物品、执行指令或写玩家状态。在这些回调里通过玩家助手写入会被拒绝,也禁止递归报价。可以读取 getLevel / hasSkill 等,但不要在条件里调用 checkLearn / getLearningQuote / learn。

自定义条件类型

SkillAtlasEvents.conditionTypes(event => {
  event.register('pack:quest_gate', type => {
    type.validate(data => typeof data.flag === 'string' && typeof data.label === 'string')
      .test((ctx, data) => ctx.player.persistentData.getBoolean(data.flag))
      .describe((ctx, data) => Text.gold(data.label))
  })
})

SkillAtlasEvents.definitions(event => {
  event.modifyNode('haste', node => {
    node.condition('pack:quest_gate', { flag: 'intro_done', label: '完成入门任务' })
  })
})
  • validate(data => boolean) 可省略,默认接受任何数据。data 是只读的普通 JSON。
  • test(ctx, data) 必填,返回布尔值。
  • describe(ctx, data) 必填,返回 Component,例如用 Text 构造的样式文本。
  • 未知类型、重复注册、非法数据和不完整的类型都会拒绝新图谱。

ctx 提供 player、nodeId、level(待学习等级)、phase(preview / check / learn / commit)、language 和 skills(玩家助手)。

节点条件和下一等级条件必须全部满足才能学习,requireAll 不会把它们变成任选条件。自定义条件是在原生前置、隐藏规则和互斥组之外额外增加门禁,不会替代它们。

自定义奖励类型

SkillAtlasEvents.rewardTypes(event => {
  event.register('pack:apple_gift', type => {
    type.validate(data => data.count >= 1 && data.count <= 64)
      .describe((ctx, data) => Text.green('苹果 ×' + data.count))
      .onLearn((ctx, data) => ctx.player.give(Item.of('minecraft:apple', data.count)))
  })
})

SkillAtlasEvents.definitions(event => {
  event.modifyNode('haste', node => {
    node.level(1, rank => rank.customReward('pack:apple_gift', { count: 1 }))
  })
})

describe 必填,validate 可选。奖励至少要提供 onLearn,或者成对提供 onActivate 和 onDeactivate,也可以三个都提供。

回调 执行时机
onLearn(ctx, data) 正常付费学习成功后,对新等级执行一次;管理授予时对每个新增等级各执行一次
onActivate(ctx, data) 该等级成为当前等级时启用;登录、复活时恢复,定义重载时重建
onDeactivate(ctx, data) 被其他等级替换、洗点、定义删除或变更、退出、停服时撤销
  • 持续奖励只采用当前等级的声明,不累加历史等级。
  • 重复刷新不会重复启用。更换玩家实体或类型注册代次时,会先撤销旧实例再启用新实例。
  • 查询、取消或失败的学习、重新登录都不会触发 onLearn。洗点后重新付费学习、重新向上授予都算新的学习,与原生一次性命令奖励一致。如果要终身只领一次,需要脚本自己用持久标记去重。

奖励 ctx 提供 player、nodeId、level、index(自定义奖励槽位,从 0 开始)、reason、key、modifierId、modifierKey、language、skills。

  • key 是稳定的资源键。
  • modifierId 是稳定的 UUID,供 1.20.1 的属性修饰符使用。
  • modifierKey 是相同含义的 ResourceLocation(1.21.1)或 Identifier(26.1.2),供这两个版本的属性修饰符使用。

它们都按节点、等级和槽位区分,不依赖玩家实体实例。

reason 可能是 learn、grant、login、reset、definition_change、respawn、refresh、logout、stop。onActivate / onDeactivate 必须按稳定键做到幂等,不要在 onActivate 里反复发放物品。

提交后奖励出错会记录服务端错误,已完成的学习、费用和等级保留,一次性奖励不会重试。启用失败的实例会保留下来,以便之后清理。撤销出错同样会记录,所以脚本必须实现可靠的撤销逻辑。

动态学习费用

SkillAtlasEvents.learningCost('haste', event => {
  if (event.player.persistentData.getBoolean('discount')) {
    event.amount = Math.floor(event.amount * 0.8)
  }
  event.items = [{ item: 'minecraft:emerald', count: 2 }]
})

事件继承了玩家、节点、等级、source / reason 和 cancel / deny。可以修改:

字段 规则
amount 0–1000000000 的整数
type skill_points 或 experience(实际经验值,不是经验等级)
items 交付物对象数组,最多 16 项;每项 count 为正整数;nbt 按目标版本解释;[] 表示不交付物品

请先在本地构造好完整的 JavaScript 数组,再整体赋值给 event.items。读取到的 Java 列表可能是只读的,不要依赖 push 追加。

baseCost 是原始等级 JSON 的只读快照(cost、可选的 experience、items、原生 rewards)。点数池必须保持为所属分类的池,没有点数池的分类不能改用 skill_points。小数、非有限数、负数、错误币种、非法物品和超限费用都会拒绝本次报价。回调报错或取消时不扣费。

客户端报价

  • 详情和悬浮提示显示服务端报价的费用、条件勾叉和自定义奖励说明。还没拿到报价时显示「查询中」,详情里的学习按钮暂时不可用。
  • 报价绑定玩家、节点、当前等级、revision 和有效期,并带一次性令牌。每个玩家最多缓存 128 个节点。
  • 服务端每 5 tick 最多接受一次报价请求,报价有效期 100 tick。客户端约 60 tick 刷新一次,80 tick 后停用旧缓存。只刷新当前查看的节点。
  • 点击学习必须使用显示过的报价。过期、串玩家、已过时、重复提交或没有报价的请求都会被拒绝。
  • 实际扣费会重新检查资源和条件,beforeLearn 之后再检查一次条件。费用在事务中冻结,不会静默改成另一个价格后扣费。
  • SkillAtlas.player(p).learn(id) 会自己生成内部报价,脚本不需要处理令牌。

限制:最多 16 项交付物,每项最多 8192 字符的 NBT/组件数据。费用 JSON 最多 1 Mi 字符,报价数据包最多 1048512 字节。说明过大无法编码时会拒绝报价,而不是截断后继续。

只要存在 learningCost 监听器,全图节点都会进入报价流程。如果只有条件或奖励声明,则只有对应节点进入报价流程。普通的原生学习请求无法绕过受管节点的费用规则。

行为获取类型

行为获取类型让其他模组的行为也能获得技能点。

SkillAtlasEvents.acquisitionTypes(event => {
  event.register('pack:machine', type => {
    type.name('机器加工').description('完成指定机器加工获取技能点')
      .unit('count')
      .validate(data => typeof data.machine === 'string')
      .test((ctx, data) => ctx.payload.machine === data.machine)
  })
})

SkillAtlasEvents.definitions(event => {
  event.createPool('machine').name('加工点数')
    .customAcquisition('pack:machine', { machine: 'press' }, 8, 1, 20)
})

然后在所接入模组真实的服务端事件回调里上报:

SkillAtlas.player(event.player).emitAcquisition('pack:machine', { machine: 'press' }, 1)
  • name 必填。description 默认与 name 相同,unit 默认 count,validate / test 默认全部接受。
  • name / description 可以是语言键:存在翻译时按客户端语言显示,否则显示原文。每项最多 512 字符。
  • unit 可为 count、seconds、centimeters。自定义单位都直接用上报的 units 和 count 阈值比较,不会乘 20 或 100。units 是 0–1000000000 的整数。
  • 配置 data 与每次上报的 payload 分开传递,都是最多 16 KiB 的只读普通 JSON,最多嵌套 32 层、校验最多 8192 步。
  • ctx 提供 player、skills、language、typeId、poolId、payload、units。
  • 一次上报会先筛选同类型的全部池,再逐池结算。未知类型或匹配出错时不发点,并且匹配错误发生在任何池结算之前。

集成只提供类型注册和分发入口,不会自动监听其他模组的事件,需要整合包自己桥接。返回结果和资格规则见服务端 API。

Clone this wiki locally