> For the complete documentation index, see [llms.txt](https://enchantedmobs.superiormc.cn/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://enchantedmobs.superiormc.cn/develop/za-xiang.md).

# Developer API

EnchantedMobs 2.0.0 exposes registries for custom abilities, conditions, modifiers, and triggers. Register extensions after EnchantedMobs has enabled.

## Maven dependency

```xml
<repositories>
    <repository>
        <id>repo-lanink-cn</id>
        <url>https://repo.lanink.cn/repository/maven-public/</url>
    </repository>
</repositories>

<dependencies>
    <dependency>
        <groupId>cn.superiormc.enchantedmobs</groupId>
        <artifactId>plugin</artifactId>
        <version>2.0.0</version>
        <scope>provided</scope>
    </dependency>
</dependencies>
```

Add a hard dependency when your plugin requires the API:

```yaml
depend:
  - EnchantedMobs
```

## Registry keys

Ability, condition, and modifier keys are normalized by:

* converting to lowercase;
* replacing `-` with `_`.

`My-Ability` and `my_ability` therefore refer to the same key. Registering a duplicate normalized key throws `IllegalArgumentException`; built-ins are not silently replaced.

## Custom ability

Register a factory during `onEnable`:

```java
@Override
public void onEnable() {
    AbilityManager.abilityManager.register("launch_ring", LaunchRingAbility::new);
}
```

```java
public final class LaunchRingAbility extends AbstractAbility {

    public LaunchRingAbility(ConfigurationSection section) {
        super("launch_ring", section);
    }

    @Override
    public boolean execute(PowerContext context) {
        Entity target = getTargetEntity(context);
        if (target == null) {
            return false;
        }

        double strength = getDouble("strength", 1.0, context);
        Vector velocity = target.getVelocity();
        velocity.setY(strength);
        target.setVelocity(velocity);
        return false;
    }

    @Override
    public TargetEntityType getDefaultTargetEntityType() {
        return TargetEntityType.TARGET;
    }
}
```

Returning `true` requests cancellation of a cancellable trigger event. Most effect abilities return `false`.

The base class applies common typed `conditions`, `random`, `cooldown`, `times`, dynamic values, entity selectors, and location offsets.

```yaml
abilities:
  launch:
    type: launch_ring
    target: TARGET
    strength: 1.4
```

## Custom condition

```java
PowerConditionsManager.powerConditions.register(new PowerConditionIsNamed());
```

```java
public final class PowerConditionIsNamed extends AbstractPowerCondition {

    public PowerConditionIsNamed() {
        super("is_named");
    }

    @Override
    protected boolean onMatch(ObjectSingleCondition condition) {
        PowerContext context = condition.getContext();
        if (context == null) {
            return false;
        }

        LivingEntity target = context.livingEntity(
                condition.getString("target", "TARGET"),
                EntitySelector.TARGET
        );
        return target != null && target.getCustomName() != null;
    }
}
```

The base implementation applies the common `not: true` inversion after `onMatch` returns.

```yaml
conditions:
  named-target:
    type: is_named
    target: TARGET
```

## Custom modifier

```java
PowerModifiersManager.powerModifiers.register(
        "minimum_damage",
        MinimumDamageModifier::new
);
```

```java
public final class MinimumDamageModifier extends AbstractPowerModifier {

    public MinimumDamageModifier(ConfigurationSection section) {
        super("minimum_damage", section);
    }

    @Override
    protected void onApply(PowerContext context) {
        double current = context.result().damage(context.triggerData());
        double minimum = getDouble(
                "value",
                1.0,
                context,
                "original",
                String.valueOf(current)
        );
        context.result().damage(Math.max(current, minimum));
    }
}
```

The modifier base class evaluates typed `conditions`, `random`, and per-path `cooldown` before `onApply`.

## Manual trigger

A manual trigger lets another plugin expose a custom power section without adapting a Bukkit event.

```java
public final class MyPlugin extends JavaPlugin {

    private ManualTrigger ragePulse;

    @Override
    public void onEnable() {
        TriggerManager manager = EnchantedMobs.instance.getTriggerManager();
        ragePulse = manager.register(this, "rage_pulse");
    }

    public TriggerResult fireRagePulse(
            LivingEntity owner,
            LivingEntity target
    ) {
        TriggerData data = TriggerData.builder(owner)
                .source(owner)
                .skill(owner)
                .target(target)
                .location(target.getLocation())
                .build();

        return EnchantedMobs.instance
                .getTriggerManager()
                .fire(ragePulse, data);
    }

    @Override
    public void onDisable() {
        EnchantedMobs.instance
                .getTriggerManager()
                .unregisterAll(this);
    }
}
```

The power section key is the trigger's namespaced key. For a plugin named `MyPlugin`:

```yaml
'myplugin:rage_pulse':
  conditions:
    close-enough:
      type: distance
      max: 12
  abilities:
    effect:
      type: particle
      particle: ANGRY_VILLAGER
```

`TriggerData.builder(owner)` requires an owner. Source, skill, target, block, location, Bukkit event, tick number, and typed extra context values are optional.

`TriggerManager.fire` evaluates the owner's assigned powers, applies their final result, and returns a `TriggerResult` containing execution and cancellation state.

## Custom event-backed trigger

Advanced integrations may extend `AbstractTrigger<E>`, provide a unique `NamespacedKey` and configuration key, declare supported `TriggerEventType` values, and register it with:

```java
EnchantedMobs.instance
        .getTriggerManager()
        .register(this, customTrigger);
```

The trigger manager dispatches registered triggers for their event types. Call `unregisterAll(plugin)` during disable to remove every trigger owned by that plugin.

## Lifecycle cleanup

Custom ability, modifier, and condition implementations may override:

```java
public void onUnload()
public void onEntityUnload(UUID entityId)
```

These hooks are intended for type-wide runtime cleanup during EnchantedMobs reload/unload and tracked entity removal. Do not make cleanup depend on one particular YAML section instance.

## Compatibility guidance

* Use scheduler-safe entity operations when supporting Folia.
* Store UUIDs instead of retaining unloaded entity objects.
* Validate that required context entities and result values exist for the trigger.
* Keep published type IDs stable because user YAML depends on them.
* Re-test integrations when the EnchantedMobs API version changes across a major release.
