# 运行时 Helper

`PassiveSkillTreeJS` 是暴露给脚本的运行时入口。

公开入口方法：

- `PassiveSkillTreeJS.player(player)`
- `PassiveSkillTreeJS.skill(id)`
- `PassiveSkillTreeJS.tree(id)`
- `PassiveSkillTreeJS.item(stack)`

## Player View

`PassiveSkillTreeJS.player(player)` 会返回 `PSTPlayerView`；如果玩家没有 PST capability，就返回 `null`。

`PSTPlayerView` 常用方法：

- `skillPoints()`
- `treeReset()`
- `grantSkillPoints(amount)`
- `consumeSkillPoints(amount)`
- `hasSkill(id)`
- `learn(id)`
- `learnWithoutSkillPointCost(id)`
- `learnWithSkillPointCost(id, cost)`
- `remove(id)`
- `reset()`
- `learnedSkillIds()`
- `learnedSkills()`
- `learnedSkill(id)`
- `hasLearnedInTree(id)`
- `learnedSkillsInTree(id)`
- `skill(id)`

`skill(id)` 会返回一个绑定玩家状态的 `PSTPlayerSkillView`。

## 玩家绑定的 Skill View

`PSTPlayerSkillView` 适合处理“这个玩家当前是否学会、现在能不能学”这类问题。

常用方法：

- `learned()`
- `canLearn()`
- `learn()`
- `remove()`
- `requirements()`
- `bonuses()`
- `listeners()`

每个 requirement、bonus 和 listener view 都会暴露：

- `typeId()`
- `text()`
- `node()`

只有当对象背后是 PassiveSTJS 运行时 serializer 类型时，`node()` 才会存在。

## 只读的 Skill 与 Tree View

`PassiveSkillTreeJS.skill(id)` 会返回只读的 `PSTSkillView`。

常用 `PSTSkillView` 方法：

- `id()`
- `title()`
- `titleColor()`
- `positionX()`
- `positionY()`
- `tags()`
- `directConnections()`
- `bonusCount()`
- `requirementCount()`

`PassiveSkillTreeJS.tree(id)` 会返回只读的 `PSTTreeView`。

常用 `PSTTreeView` 方法：

- `id()`
- `skillIds()`
- `limits()`
- `hasSkill(id)`
- `skill(id)`
- `skills()`
- `isDefaultTree()`

## Item Bonus Runtime View

`PassiveSkillTreeJS.item(stack)` 会返回 `PSTItemView`。

常用方法：

- `bonusCount()`
- `bonuses()`
- `clearBonuses()`
- `addItemBonus(typeId, consumer)`
- `addItemBonus(builder)`
- `addSkillBonusItemBonus(typeId, consumer)`
- `addAttributeItemBonus(consumer)`
- `addItemBonusList(consumer)`

每个 `PSTItemBonusView` 都会暴露：

- `typeId()`
- `text()`
- `node()`

## 最小模式

读取玩家状态：

```js
var playerView = PassiveSkillTreeJS.player(event.player)
if (playerView && playerView.hasSkill('kubejs:sample_runtime_tree/water_pulse')) {
  playerView.grantSkillPoints(1)
}
```

检查玩家绑定 skill 的 requirement 与 listener：

```js
var skillView = playerView ? playerView.skill('kubejs:passivestjs/runtime_threshold') : null
if (skillView && !skillView.learned()) {
  for (var requirement of skillView.requirements()) {
    console.log(requirement.typeId(), requirement.passed())
  }
}
```

直接把 item bonus 加到活体 `ItemStack`：

```js
var stackView = PassiveSkillTreeJS.item(event.item)
if (stackView && stackView.bonusCount() === 0) {
  stackView.addAttributeItemBonus(bonus => {
    bonus.attribute('minecraft:generic.armor')
    bonus.amount(2)
    bonus.operation(0)
    bonus.modifierId('8516d3f4-373e-42c3-9138-3215993b34c4')
    bonus.bonusName('passivestjs.sample')
  })
}
```

这些模式都在仓库里的样例文件中有对应写法：

- `examples/kubejs/passivestjs/03_server_runtime_api.js`
- `examples/kubejs/passivestjs/07_server_runtime_helper_samples.js`
- `examples/kubejs/passivestjs/09_server_item_bonus_runtime_api.js`

最容易混淆的是两种 skill view：

- `PassiveSkillTreeJS.skill(id)`
  只看技能定义，不绑定玩家
- `PassiveSkillTreeJS.player(player).skill(id)`
  绑定玩家，可以判断 `learned()`、`canLearn()`，并读取 `requirements()`、`bonuses()`、`listeners()`

如果你的逻辑是“这个玩家现在能不能点这个技能”，就应该走第二种。
