---
title: Internal Hooks
hide_meta: true
---

# Internal Hooks

`PmmoJS.internal` covers PMMO handler paths that do not go through the trigger registry. Use these hooks when you need to intercept PMMO's built-in handler logic directly.

## When to use internal vs trigger

Use `PmmoJS.internal` when the handler you want to hook into is not available through `PmmoJS.trigger`. The general rule:

- **`PmmoJS.trigger`** — PMMO events that route through the `EventTriggerRegistry`. Most gameplay actions live here: breaking blocks, crafting, combat, movement, etc.
- **`PmmoJS.internal`** — PMMO handler classes that never use the trigger registry. These are specific engine-level paths: login, dimension travel, explosions, pistons, player death, sleep, potion brewing.

## Supported internal hooks

On PMMO 1.7.40, the following internal hooks are bridged:

| Hook ID | What it intercepts |
|---|---|
| `DIMENSION_TRAVEL` | Player changing dimensions |
| `EXPLOSION` | Explosion events (seeded with affected block/entity counts) |
| `LOGIN` | Player login and data sync |
| `MOUNT` | Player mounting an entity |
| `PISTON` | Piston chunk tracking updates |
| `PLAYER_DEATH` | Player death XP loss handling |
| `POTION_BREW` | Potion brewing (also reachable via `EventType.BREW`) |
| `SLEEP_FINISHED` | Player finishing sleep |

## Two control paths

`PMMOInternalEventJS` gives you two independent ways to control PMMO's behavior:

- **Skip PMMO only:** `event.skipPmmo()` or `event.setSkipPmmo(true)` — PMMO's handler logic is skipped, but the underlying Forge action is not cancelled. Use this when you want PMMO to ignore the event while the rest of the game processes it normally.
- **Deny the action:** `event.deny()` or `event.setActionCancelled(true)` — Cancels the wrapped Forge action AND skips PMMO's handler. Use this when you want to prevent the action entirely.

```js
PmmoJS.internal(PMMOInternalType.DIMENSION_TRAVEL, event => {
    const player = event.getPlayer()
    if (!player) return

    if (player.stages.has('pmmo_bypass_travel')) {
        event.skipPmmo()  // let the player travel, just don't run PMMO's checks
    }

    if (player.stages.has('pmmo_lock_dimensions')) {
        event.deny()  // block the travel entirely
    }
})
```

## Potion brewing example

`POTION_BREW` is special: the bridge seeds the context with a tracking flag and lets you control the brewed-item output.

```js
PmmoJS.internal(PMMOInternalType.POTION_BREW, event => {
    if (event.getContextBoolean('alreadyTracked')) {
        return
    }

    event.addXpAward('alchemy', 10)
    event.putContextBoolean('markBrewed', true)
})
```

## Explosion example

`EXPLOSION` receives `affectedBlockCount` and `affectedEntityCount` from the bridge:

```js
PmmoJS.internal(PMMOInternalType.EXPLOSION, event => {
    if (event.getContextInt('affectedBlockCount') > 64) {
        event.setSkipPmmo(true)
    }
})
```

## Key methods on the internal event

- `getType()` and `getTypeId()` — which internal hook fired
- `getForgeEvent()` and `getForgeEventClassName()` — the underlying Forge event
- `hasPlayer()`, `getPlayer()`, `getServerPlayer()` — the player, if applicable
- `shouldSkipPmmo()`, `setSkipPmmo(true)`, `skipPmmo()` — skip PMMO's handler
- `canCancelAction()`, `isActionCancelled()`, `setActionCancelled(true)`, `deny()` — cancel the Forge action
- `getContextBoolean(key)`, `getContextString(key)`, etc. — read context values
- `putContextBoolean(key, value)`, `putContextString(key, value)`, etc. — write context values
- `getXpAwards()`, `setXpAwards(map)`, `setXpAward(skill, amount)`, `addXpAward(skill, amount)` — XP management

## ProbeJS 类型补全

PmmoJS 支持 ProbeJS 和 ProbeJS Legacy。ProbeJS 生成类型定义时，会同时生成枚举值、事件对象和工具类的类型补全。

PmmoJS 类型不需要额外编写 ProbeJS event listener；插件会在 mod 初始化时完成注册。
