> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.trysulfur.net/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.trysulfur.net/_mcp/server.

# Skills reference

Skills give a declared mob MythicMobs-style abilities: *when X happens, with chance C and under
conditions, apply mechanics M to targets T*. They are Sulfur's first intentional extension beyond
vanilla gameplay.

**Native implementation, not interop.** Sulfur's skill system is a from-scratch native API with
MythicMobs feature parity as the checklist. No Bukkit/Java plugin is ever loaded or executed, and
no YAML compatibility is pursued. The underlying subsystems a mechanic calls into (damage,
effects) are the already-ported, jar-faithful vanilla paths — so skill damage respects vanilla
armor, i-frames, and knock-on effects exactly.

Skills are **even stronger** than goals on the declare-once principle: a skill is captured as
*pure data* at load — no Starlark callable is retained at all. The Go hot path interprets the
declaration structs directly, so a skill-bearing mob burns **zero** Starlark calls per tick, idle
or active.

## Declaring skills

Skills attach to a mob declaration via the `skills` list of
[`declare_mob`](/plugins/declare-mob):

```python
declare_mob(
    name = "static_sheep",
    base_type = "sheep",
    attributes = {"max_health": 100.0, "movement_speed": 0.3},
    skills = [
        skill(
            trigger    = "timer",                                       # when
            interval   = 100,                                           # required iff trigger == "timer"
            chance     = 1.0,                                           # optional (0, 1]
            conditions = [condition("health_below", value = 0.5)],      # optional, AND-ed
            targeter   = targeter("players_in_radius", r = 10.0),       # who
            mechanics  = [                                              # what, in order
                mechanic("damage", amount = 5.0),
                mechanic("effect", effect = "poison", duration = 100, amplifier = 1),
            ],
        ),
    ],
)
```

All four builtins (`skill`, `mechanic`, `targeter`, `condition`) validate **everything at load**:
unknown kinds, missing or forbidden arguments, out-of-range values, and — crucially —
capabilities. `mechanic("damage")` in a plugin whose manifest lacks `skills.damage` is a loud
**load error** (fail-closed), not a runtime denial.

## `skill(...)`

```python
skill(trigger, interval=?, chance=1.0, conditions=?, targeter=, mechanics=[...])
```

| Argument     | Rules                                                                                                                                                                         |
| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `trigger`    | One of `"timer"`, `"spawn"`, `"damaged"`, `"death"`. Unknown = load error.                                                                                                    |
| `interval`   | **Required** for `trigger="timer"` (fire period in ticks, `>= 1`); **forbidden** for every other trigger.                                                                     |
| `chance`     | Fire probability in `(0, 1]`. Default `1.0` — and a chance of exactly 1.0 draws **no RNG** at all. When `< 1.0`, the roll draws on the caster's own per-entity seeded stream. |
| `conditions` | Optional list of `condition(...)` values; all must hold (AND).                                                                                                                |
| `targeter`   | Required, a `targeter(...)` value.                                                                                                                                            |
| `mechanics`  | Required, a non-empty list of `mechanic(...)` values, applied in declared order to each target.                                                                               |

### Triggers

| Trigger   | Fires                                                                                                                          |
| --------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `timer`   | Every `interval` ticks, per mob (a countdown armed at spawn; the first fire lands `interval` ticks after spawn, then re-arms). |
| `spawn`   | Once, when the mob spawns (after it is live and resolvable by targeters).                                                      |
| `damaged` | After a hit **lands** and the mob **survives** (the `~onDamaged` analogue).                                                    |
| `death`   | On death, after the loot roll and death broadcast (the `~onDeath` analogue).                                                   |

## `targeter(kind, r=?)`

| Kind                  | `r`             | Resolves to                                                                                      |
| --------------------- | --------------- | ------------------------------------------------------------------------------------------------ |
| `"self"`              | forbidden       | The caster itself.                                                                               |
| `"nearest_player"`    | required, `> 0` | The nearest living player within `r` blocks (one target or none).                                |
| `"players_in_radius"` | required, `> 0` | Every living player within `r` blocks.                                                           |
| `"mobs_in_radius"`    | required, `> 0` | Every living mob within `r` blocks, excluding the caster (scoped to the caster's owning region). |

## `mechanic(kind, ...)`

| Kind       | Arguments                                                                | Capability       | Routes through                                                                                                                                                                                                                                    |
| ---------- | ------------------------------------------------------------------------ | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `"damage"` | `amount` (required, `> 0`) — raw pre-armor damage                        | `skills.damage`  | The ported vanilla hurt pipelines (`applyDamage` for players, `applyDamageEntity` for mobs), attributed to the caster as a mob attack — armor, i-frames, and knockback are vanilla-exact.                                                         |
| `"effect"` | `effect` (required id), `duration` (ticks, `>= 1`), `amplifier` (`>= 0`) | `skills.effects` | The ported effect system (`addPlayerEffect` / `addEntityEffect`). Player targets get full modifier/tick semantics; mob targets currently get add + instant semantics (duration ticking for arbitrary mobs is on the [roadmap](/plugins/roadmap)). |

Passing an argument that doesn't belong to the kind (e.g. `amount` on an `effect` mechanic) is a
load error — arguments are never silently dead.

### Implemented effect ids

The `effect` mechanic accepts these ids, with or without the `minecraft:` prefix:

`poison`, `regeneration`, `speed`, `slowness`, `weakness`, `haste`, `resistance`, `jump_boost`,
`strength`, `wither`, `fire_resistance`, `water_breathing`, `instant_health`, `instant_damage`.

An effect outside this set is a load error — never a silently-inert buff.

## `condition(kind, value)`

| Kind             | `value`                           | Holds when                                            |
| ---------------- | --------------------------------- | ----------------------------------------------------- |
| `"health_below"` | fraction of MaxHealth in `(0, 1]` | The **caster's** health is below `value × MaxHealth`. |

## Runtime semantics

Execution order per skill fire: **conditions → chance gate → targeter resolve → mechanics in
declared order, per target**.

* **Per-mob state** is just the timer countdowns and a re-entrancy guard; the declaration itself
  is shared immutable data across all spawned mobs of the type.
* **Re-entrancy is dropped:** a mechanic whose side effect re-triggers the same mob (e.g. a
  self-damage mechanic firing the mob's own `damaged` trigger) is suppressed while the skill
  executes — no cascades.
* **RNG discipline:** the only RNG draw is the optional `chance` gate, on the caster's own seeded
  per-entity stream. Vanilla mobs declare no skills, so no vanilla RNG stream is ever perturbed.

## Complete example: zapmob

The `zapmob` test plugin declares a pig-based mob with a periodic AoE zap and a retaliation hit:

```python title="zapmob/main.star"
# Two skills:
#   1. ~onTimer:10  @PlayersInRadius{r=10} -> damage 5 + poison 100 ticks
#   2. ~onDamaged   @NearestPlayer{r=16}   -> damage 2 (the retaliation zap)

declare_mob(
    name = "zapper",
    base_type = "pig",
    attributes = {
        "max_health": 30.0,
        "movement_speed": 0.25,
    },
    skills = [
        skill(
            trigger = "timer",
            interval = 10,
            targeter = targeter("players_in_radius", r = 10.0),
            mechanics = [
                mechanic("damage", amount = 5.0),
                mechanic("effect", effect = "poison", duration = 100, amplifier = 0),
            ],
        ),
        skill(
            trigger = "damaged",
            targeter = targeter("nearest_player", r = 16.0),
            mechanics = [
                mechanic("damage", amount = 2.0),
            ],
        ),
    ],
)
```

```toml title="zapmob/plugin.toml"
name = "zapmob"
version = "0.1.0"
entrypoint = "main.star"
runtime = "starlark"
capabilities = ["entities.read", "entities.write", "nav", "skills.damage", "skills.effects"]
```

For what's coming next (summon/heal/throw/teleport mechanics, meta-skills, cooldowns, more
triggers and conditions, and ModelEngine-class models), see the [Roadmap](/plugins/roadmap).