> ## Documentation Index
> Fetch the complete documentation index at: https://entities.lodemc.net/llms.txt
> Use this file to discover all available pages before exploring further.

# Extending the Effect Language

> Five extension points for the effect DSL: mechanics, targeters, triggers, conditions and placeholders.

Addons can add **new words to the effect language**, in all five categories. Once registered, they work everywhere the built-ins do: mob effects, [skills](https://entities.lodemc.net/named-effects.md), dialog nodes, inline blocks, spawn rules and `/le effect start`. Server owners write them in YAML without knowing or caring that an addon provides them.

| Extension point | Example | Usable in |
| - | - | - |
| [**Mechanics**](#custom-mechanics) | `shout{m=&4FOR GLORY}` | any effect line |
| [**Targeters**](#custom-targeters) | `@lowestHealth[16]` | any effect line |
| [**Triggers**](#custom-triggers) | `+onCast` | mob `effects:` lists, fired from your own events |
| [**Conditions**](#custom-conditions) | `if{c=wielding_rpg{tier=3}}` | `if` and `while` lines, skill `conditions:`, inline `?`, spawn rule `cond:`, dialog `conditions:` |
| [**Placeholders**](#custom-placeholders) | `<rpg.tier>` | any message, title, name or command text |

Together they cover the whole skill language. An RPG addon can fire its own `+onCast` trigger, gate a branch on its own condition, pick victims with `@lowestHealth[16]`, hit them with its `shout` mechanic and print `<rpg.tier>` in the message — all inside a server owner's ordinary YAML.

## Registration rules

These apply to all five extension points:

* **Built-in names always win.** An addon can never change what a stock config does. Pick distinctive names.
* **Registrations survive `/le reload`.** Register once in `onEnable`.
* **Returning `null` from a provider means "invalid arguments".** The line is skipped and a warning is logged, instead of failing the whole config.

### Load order is handled for you

Configs load before your addon enables, so a config line using `shout` — or a custom trigger or condition — logs one parse warning during boot. LodeEntities then re-parses all configs right after you register, and the line resolves. Nothing to do on your side.

> [!NOTE]
>
> This applies to anything that affects **parsing**: mechanics, targeters, triggers and conditions. Placeholders resolve at runtime, so they never need a re-parse.

A boot-time warning about an unknown mechanic that disappears once all plugins are enabled is this behaviour, not a bug to chase.

## Custom mechanics

A **mechanic** is what an effect line *does*. Registering one makes it usable in any mob or skill, exactly like a built-in.

```java theme={null}
// usable as:  shout{m=Hello} +onHurt @playersRadius[10]
LodeEntitiesAPI.effects().registerMechanic("shout", args -> {
    String message = args.text("message", "m"); // named attr or positional text
    if (message == null) return null;           // invalid args -> line is skipped with a warning
    return (ctx, targets) -> {
        for (Entity target : targets) {
            target.sendMessage(Component.text(message));
        }
    };
});
```

* `args` is the parsed `{key=value;...}` block plus positional tokens (`org.nexomaker.engine.bukkit.effect.MechanicArgs`). Helpers: `string`, `text`, `int`, `double`, `bool`, `ticks` (accepting `40`, `2s` or `1.5m`) and `entries()`.
* `ctx` (`EffectContext`) carries the **caster** (`ctx.getMob()`), the **trigger entity** (`ctx.getTrigger()`, for example the attacker) and the per-run `skill` variable store.
* `targets` is the resolved targeter result.

## Custom targeters

A **targeter** is who a line acts on — `@self`, `@playersRadius[20]`, and now yours.

```java theme={null}
// usable as:  damage{amount=5} @lowestHealth[16]
LodeEntitiesAPI.effects().registerTargeter("lowestHealth", arg -> {
    double radius = arg != null ? Double.parseDouble(arg) : 16.0; // arg = raw text inside [...]
    return ctx -> ctx.getMob().getNearbyEntities(radius, radius, radius).stream()
        .filter(e -> e instanceof LivingEntity)
        .sorted(Comparator.comparingDouble(e -> ((LivingEntity) e).getHealth()))
        .limit(1).toList();
});
```

## Custom triggers

A **trigger** is *when* a mob's effect line fires. Register your own, then fire it from your own game events — this is how you add item-use, consume or channel-style triggers.

```java theme={null}
// config can now write:  shout{m=Used!} +onCast @self
LodeEntitiesAPI.effects().registerTrigger("cast", List.of("oncast"));

// fire it when your event happens (only mobs with a matching line react):
@EventHandler
public void onAbility(MyAbilityEvent e) {
    LodeEntitiesAPI.mobs().fireTrigger(e.getMob(), "cast", e.getCaster());
}
```

## Custom conditions

A **condition** is a yes/no test usable everywhere conditions are: `if` and `while`, skill `conditions:`, inline `?cond`, targeter filters, spawn rules and dialogs. Function-style, MythicMobs-shaped: `name{args}`, `name[args]` or bare `name`. The built-in names (every MythicMobs condition) always win, so pick a distinctive one.

The provider is `arg -> Condition`, and a `Condition` is `(ctx, subject) -> boolean` — two lambda layers, not three.

```java theme={null}
// usable as:  if{c=mainhand[DIAMOND_SWORD];then={shout{m=Nice blade}}} +onInteract @trigger
LodeEntitiesAPI.effects().registerCondition("mainhand", arg -> {
    // arg = raw text inside the brackets (null if bare)
    if (arg == null) return null;                        // invalid -> line skipped with a warning
    Material want = Material.matchMaterial(arg.trim());
    if (want == null) return null;
    return (ctx, subject) -> subject instanceof Player p
        && p.getInventory().getItemInMainHand().getType() == want;
});
```

Core condition syntax (`var.x >= 2`, `health < maxhealth` and friends) always wins.

> [!WARNING]
>
> **Conditions may be asked about a place with no caster.** [Spawn rules](https://entities.lodemc.net/addon-natural-spawns.md) evaluate `cond:` before anything is spawned, so `ctx.getMob()` throws there by design. Use `ctx.getLocation()`, which is always valid, and `ctx.getMobOrNull()` when your condition can work either way. A condition that reaches for a caster unconditionally simply never passes in a spawn rule.

## Custom placeholders

A **placeholder** is a token in angle brackets, resolved in any effect text: messages, titles, `setname`, `command` and the rest. You register a **prefix** and receive the text after it.

```java theme={null}
// usable as:  message{m=Your tier is <rpg.tier>} @trigger
LodeEntitiesAPI.effects().registerPlaceholder("rpg", (arg, ctx, subject) -> {
    // arg = "tier" for <rpg.tier>, null for bare <rpg>
    return "tier".equals(arg) ? lookupTier(subject) : null; // null -> empty string
});
```

Built-in placeholders such as `<caster.name>`, `<target.name>` and `<scope.var.name>` always win.

## Running effects from code

```java theme={null}
LodeEntitiesAPI.effects().run("greeting", npcEntity, clickedPlayer); // a skill, by id
ParsedLine line = LodeEntitiesAPI.effects().parse("lightning +onSpawn @self");
LodeEntitiesAPI.effects().run(List.of(line), caster, target);        // run lines you parsed yourself
Set<String> named = LodeEntitiesAPI.effects().ids();                 // every skill id
```

`run(effectId, ...)` returns `false` if no such skill exists. A skill's `conditions:` gate applies here too. `parse` returns `null` if the line is malformed — it is the same parser the config loader uses, so it accepts triggers and targeters too.

## For server owners

If you're not writing an addon yourself, the takeaway is simple: installing an addon can give your effect configs new words. The addon's own docs tell you which.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.