---
title: Perk 注册
hide_meta: true
---

# 启动 Perk 注册

`PmmoJS.registerPerk(...)` 在 `startup_scripts/` 中注册新的 PMMO Perk 类型。这是用来定义全新 Perk 的 —— 如果你只想编辑已有的 Perk 条目，请使用 `server_scripts/` 中的 `PmmoJS.perksConfig(...)`。

## 基本语法

注册一个 Perk 至少需要一个 ID、一个 Side、一个技能和至少一个回调。构建器为其他所有内容提供默认值。

```js
PmmoJS.registerPerk(event => {
  event
    .create('kubejs:steady_mind', PMMOPerkSide.SERVER)
    .withSkill('combat')
    .description('一个从 KubeJS 注册的自定义 Perk。')
    .conditions(ctx => true)
    .start(ctx => {})
    .tick(ctx => {})
    .stop(ctx => {})
    .status(ctx => {})
    .register()
})
```

调用 `register()` 之后，你的 Perk 就进入了 PMMO 的注册表。你必须把 `register()` 作为最后一步调用 —— 没有它，构建器会丢弃所有内容。

## 设置默认值

这些构建器方法定义 Perk 的启动默认值。它们会成为 PMMO 传入回调的 tag 的一部分。

**标准 PMMO 字段：**

| 方法 | 写入的键 | 作用 |
|---|---|---|
| `withSkill(skill)` | `skill` | 此 Perk 关联的技能 |
| `withCooldown(ticks)` | `cooldown` | 两次激活之间的最小 tick 数 |
| `withDuration(ticks)` | `duration` | Perk 保持活跃的时长（0 = 不 tick） |
| `withChance(value)` | `chance` | 激活概率 —— `0.0` 到 `1.0` |
| `withMinLevel(level)` | `min_level` | 技能最低要求等级 |
| `withMaxLevel(level)` | `max_level` | 技能最高允许等级 |
| `withPerXLevel(step)` | `per_x_level` | 仅在技能等级能被此值整除时激活 |
| `withMilestones(values)` | `milestones` | 仅在达到这些特定等级时激活 |

**自定义字段：**

- `withString(key, value)`、`withBool(key, value)`、`withInt(key, value)`
- `withLong(key, value)`、`withFloat(key, value)`、`withDouble(key, value)`
- `withStringList(key, values)`、`withNumberList(key, values)`
- `withCompound(key, value)` —— 嵌套 compound tag
- `defaults(tag => {})` —— 通过 `PerkTagJS` 自由编辑默认 tag

## 生命周期回调

每个回调在 Perk 生命周期的特定时刻运行，并接收一个方法集不同的上下文对象。

### `conditions(ctx)` —— 此 Perk 是否应该激活？

在 PMMO 内置的有效性检查之后运行。返回 `true` 允许激活，`false` 阻止激活。

### `start(ctx)` —— Perk 刚刚激活

这是初始化状态的地方。`start()` 是唯一同时获得 `ctx.getSettings()`（用于持久状态）和 `ctx.getResult()`（用于一次性输出回当前 PMMO 执行趟次）的回调。

### `tick(ctx)` —— Perk 处于活跃状态

在 Perk 保持活跃期间持续触发。使用 `ctx.getElapsedTicks()` 进行计时。PMMO 会忽略返回值。

### `stop(ctx)` —— Perk 正在停用

清理你在 `ctx.getSettings()` 中存储的所有持久状态。

### `status(ctx)` —— UI 正在请求显示文本

使用 `ctx.addLine(...)` 和 `ctx.addLines(...)` 构建显示在 PMMO 的 UI 中的状态行。PMMO 会忽略返回值。

## 上下文对象速览

| 回调 | 上下文类型 | 额外方法 |
|---|---|---|
| `conditions(ctx)` | `PerkConditionContextJS` | — |
| `start(ctx)` | `PerkStartContextJS` | `getResult()` |
| `tick(ctx)` | `PerkTickContextJS` | `getElapsedTicks()` |
| `stop(ctx)` | `PerkStopContextJS` | — |
| `status(ctx)` | `PerkStatusContextJS` | `addLine(v)`、`addLines(vs)`、`clearLines()`、`getLines()` |

所有上下文共享：`getPerkId()`、`getPlayer()`、`getServerPlayer()`、`getSettings()`。

## 设置如何合并

在调用你的回调之前，PMMO 会按顺序叠加以下数据源，构建一个合并后的 tag：

1. 你在构建器方法中设置的启动默认值
2. `perks.toml` 中匹配的条目（如果有的话）
3. 当前 PMMO 事件传入的数据
4. 同一执行趟次中更早 Perk 的输出

如果合并后的 tag 有 `skill` 字段，PMMO 会注入该技能下玩家当前的 `level`。

`ctx.getSettings()` 给你最终的合并结果。

## PerkTagJS 快速参考

你的回调使用 `ctx.getSettings()` 和（在 `start()` 中）`ctx.getResult()`，两者都是 `PerkTagJS` 实例。

**读取：** `keys()`、`has(key)`、`getString(key)`、`getInt(key)`、`getLong(key)`、`getFloat(key)`、`getDouble(key)`、`getBoolean(key)`、`getOrCreateCompound(key)`。每个都有 `*Or(key, fallback)` 变体。

**写入：** `remove(key)`、`clear()`、`putString(key, value)`、`putBoolean(key, value)`、`putInt(key, value)`、`putLong(key, value)`、`putFloat(key, value)`、`putDouble(key, value)`、`putCompound(key, value)`、`putStringList(key, values)`、`putNumberList(key, values)`、`merge(value)`。

**PMMO 辅助方法：** `getSkill()`、`getResolvedLevel()`、`getCooldown()`、`getDuration()`、`getChance()`。

## 完整示例

下面是一个完整的 Perk，在活跃时治疗玩家，且只在玩家生命值未满时生效：

```js
PmmoJS.registerPerk(event => {
  event
    .create('kubejs:steady_mind', PMMOPerkSide.SERVER)
    .withSkill('combat')
    .withCooldown(200)
    .withDuration(100)
    .withMinLevel(10)
    .withInt('heal_interval', 20)
    .withDouble('heal_amount', 1.0)
    .defaults(tag => {
      tag.putBoolean('active', false)
      tag.putLong('started_at', 0)
    })
    .description('在 Perk 活跃期间持续治疗玩家。')
    .conditions(ctx => {
      return ctx.getPlayer().getHealth() < ctx.getPlayer().getMaxHealth()
    })
    .start(ctx => {
      ctx.getSettings().putBoolean('active', true)
      ctx.getSettings().putLong('started_at', ctx.getPlayer().level().getGameTime())
      ctx.getResult().putString('steady_mind_started', ctx.getPerkId())
    })
    .tick(ctx => {
      const settings = ctx.getSettings()
      const interval = settings.getIntOr('heal_interval', 20)
      const amount = settings.getDoubleOr('heal_amount', 1.0)

      if (settings.getBooleanOr('active', false) && ctx.getElapsedTicks() % interval === 0) {
        ctx.getPlayer().heal(amount)
      }
    })
    .stop(ctx => {
      ctx.getSettings().putBoolean('active', false)
    })
    .status(ctx => {
      const settings = ctx.getSettings()
      ctx.clearLines()
      ctx.addLine(`perk: ${ctx.getPerkId()}`)
      ctx.addLine(`skill: ${settings.getSkill()}`)
      ctx.addLine(`level: ${settings.getResolvedLevel()}`)
      ctx.addLine(`active: ${settings.getBooleanOr('active', false)}`)
    })
    .register()
})
```

要点说明：

- `withInt('heal_interval', 20)` 和 `withDouble('heal_amount', 1.0)` 设置了可配置的调节项。玩家可以在 `perks.toml` 中覆盖它们。
- `defaults(tag => {})` 设置只有你的 Perk 使用的私有字段。PMMO 忽略未知键。
- `conditions(ctx)` 只决定是否激活 —— 不存储状态。
- `start(ctx)` 把 `active` 和 `started_at` 存入 `ctx.getSettings()`，以便 `tick()` 和 `stop()` 后续读取。
- `start(ctx)` 把 `steady_mind_started` 写入 `ctx.getResult()` —— 这是当前执行趟次的一次性输出。
- `tick(ctx)` 使用 `ctx.getElapsedTicks()` 配合取模检查，按可配置间隔执行治疗。
- `stop(ctx)` 清理 `active` 标记。
- `status(ctx)` 只构建 UI 行 —— 不创建额外状态。

## 相关页面

- [启动注册](./registry)
- [阶段规则](./phases)
- [自定义 Perk Tag](./custom-perk-tags)
- [Tag 模型](../tag-model)
