> For the complete documentation index, see [llms.txt](https://edseries-plugins.gitbook.io/p/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://edseries-plugins.gitbook.io/p/pinnaprison/developers/enchant-api.md).

# Enchant API

The Enchant API lets you **read/write** players' enchants and **register your own custom enchants** that proc as players mine — with full access to the mine, currencies and the same block-break tools the built-in enchants use.

Everything is reached through `PinnaPrisonAPI.getInstance().getEnchants()` (an `EnchantService`).

## Reading & writing enchants

`EnchantService` exposes the whole enchant model:

```java
EnchantService enchants = PinnaPrisonAPI.getInstance().getEnchants();

enchants.getEnchantIds();                       // every registered enchant id
enchants.exists("jackhammer");
enchants.getDisplayName("jackhammer");
enchants.getMaxLevel("jackhammer");

enchants.getLevel(uuid, "jackhammer");          // a player's level
enchants.setLevel(uuid, "jackhammer", BigDecimal.valueOf(100));
enchants.addLevel(uuid, "jackhammer", BigDecimal.TEN);
enchants.removeLevel(uuid, "jackhammer", BigDecimal.ONE);

enchants.getChance(uuid, "jackhammer");         // effective proc % (booster/crystal aware)
enchants.getCost(uuid, "jackhammer", BigDecimal.TEN);
enchants.getMaxLevelsAffordable(uuid, "jackhammer");

enchants.getPrestige(uuid, "jackhammer");
enchants.setPrestige(uuid, "jackhammer", 3);
enchants.canPrestige(uuid, "jackhammer");
enchants.prestige(player, "jackhammer");        // validates + charges

enchants.isDisabled(uuid, "jackhammer");        // player toggled it off

enchants.tryProcEnchants(player, data);         // roll ALL the player's enchants
enchants.procEnchant(player, "jackhammer", data); // force-proc one (no chance roll)
```

## Creating a custom enchant

A custom enchant is two things: a **config file** (so it behaves like a real enchant — chance, level, cost, prestige) and an **`APIEnchant`** (your proc behaviour).

### 1. Ship the config file

Create `plugins/PinnaPrison/enchants/<id>.yml`. The id is the filename. Use `type: api`. Ship it with your plugin (`saveResource`) or have the admin add it. It's the exact same format as a built-in enchant ([Enchants](/p/pinnaprison/core/enchants.md)):

```yaml
# enchants/volcano.yml
color:
  primary: '&c'
  secondary: '&6'
starting-level: 0
max-chance: 5            # max proc chance (%) reached at max-level
max-level: 1000
material: FIRE_CHARGE
type: 'api'              # IMPORTANT: makes it a mining (proc) enchant
display-name: 'Volcano'
cooldown-ticks: 20

requirement:
  economy: 'pickaxelevel'
  amount: 10

cost:
  currency: tokens
  starting-cost: 1000
  increase-cost-by: 500

prestige:
  enabled: true
  max-prestige: 5
  max-chance-per-prestige: 1
  reset-level: true
  requirements:
    tokens: { type: currency, amount: 500000, remove: true }
```

Add the enchant to one of your enchant GUIs (or a built-in one) so players can buy it — it's just an `enchant` GUI item with its id.

### 2. Implement `APIEnchant`

```java
import es.edwardbelt.pinnaprison.iapi.enchant.APIEnchant;
import es.edwardbelt.pinnaprison.iapi.enchant.data.BlockBreakEnchantData;
import es.edwardbelt.pinnaprison.iapi.enchant.data.EnchantData;
import org.bukkit.entity.Player;

public class VolcanoEnchant implements APIEnchant {

    @Override
    public void onProc(Player player, EnchantData data) {
        // The mining trigger always passes a BlockBreakEnchantData (position + material).
        if (!(data instanceof BlockBreakEnchantData hit)) return;
        // … behaviour (break blocks, pay currency, FX) — see below.
    }
}
```

| Method                 | Description                                                                                                                                                                                                |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `onProc(player, data)` | Runs when the enchant procs. `data` is a `BlockBreakEnchantData` for the mining trigger.                                                                                                                   |
| `asyncSafe()`          | Default `false` → `onProc` is dispatched to the **main thread**. Return `true` only if `onProc` does packet/data-only work (the API mine-break methods, particles) so it can run on the fast break thread. |

{% hint style="danger" %}
Procs are rolled on an async break loop. If `onProc` touches the Bukkit world, entities or inventories, keep `asyncSafe()` as `false` (the default) — otherwise Bukkit will throw. The API mine-break methods (`breakBlocks`/`breakLayer`) are packet-based and safe either way.
{% endhint %}

### 3. Register it

In your `onEnable` (PinnaPrison is loaded first thanks to `depend`):

```java
@Override
public void onEnable() {
    saveResource("enchants/volcano.yml", false); // ship the config
    // copy/merge it into PinnaPrison/enchants/ — or just tell admins to drop it there

    PinnaPrisonAPI.getInstance().getEnchants()
        .registerEnchant("volcano", new VolcanoEnchant());
}
```

The registration **survives `/pinna reload`** — PinnaPrison re-creates the enchant (re-reading its config) automatically.

## Tools for `onProc`

### The trigger data

```java
BlockBreakEnchantData hit = (BlockBreakEnchantData) data;
Vector pos = hit.getPosition();      // the mined block
Material mat = hit.getMaterial();
```

### Breaking blocks (and paying like mining)

`MineService` breaks blocks in the player's mine and pays for them exactly like a normal dig — block currencies, backpack/autosell and Token Greed are gated by three flags:

```java
MineService mines = PinnaPrisonAPI.getInstance().getMines();

// Break an arbitrary set of positions.
int broken = mines.breakBlocks(player, positions,
        /*blockCurrencies*/ false, /*autosell*/ true, /*tokenGreed*/ true);

// Break the whole mine layer at a Y (jackhammer-style).
int layer = mines.breakLayer(player, pos.getBlockY(), false, true, true);

// A solid sphere around a point (explosion-style).
int blast = mines.breakSphere(player, center, 4, false, true, true);

// A single block — optionally re-rolling the player's enchants on it (chaining).
mines.breakBlock(player, pos, false, true, true, /*affectEnchants*/ true);
```

{% hint style="warning" %}
`breakBlock(..., affectEnchants=true)` lets the broken block roll the player's enchants again. That can cascade — keep it bounded (the enchants' own cooldown/chance limit it) and don't build infinite chains.
{% endhint %}

Generate region shapes with `EnchantRegions` (pure math, any thread):

```java
import es.edwardbelt.pinnaprison.iapi.enchant.EnchantRegions;

EnchantRegions.sphere(center, 4);          // solid sphere
EnchantRegions.disc(center, 6, 1);         // flat disc, ±1 on Y
EnchantRegions.cuboid(corner1, corner2);   // box
```

### Rewarding currency

```java
CurrencyService eco = PinnaPrisonAPI.getInstance().getCurrencies();
eco.addBalanceBoosted(uuid, "tokens", amount);  // applies the player's boosters/crystals
// or the raw amount:
eco.addBalance(uuid, "tokens", amount);
```

### Scaling by the enchant's level

```java
BigDecimal level = PinnaPrisonAPI.getInstance().getEnchants().getLevel(uuid, "volcano");
double radius = 2 + level.doubleValue() / 500.0; // bigger blast at higher levels
```

### Respecting the player's settings

Players can mute enchant messages, sounds and particles in `/settings` (and a single enchant's message in the upgrade menu). Honour those before you send/play anything — exactly like the built-in enchants:

```java
EnchantService enchants = PinnaPrisonAPI.getInstance().getEnchants();

if (!enchants.isMessagesDisabled(uuid) && !enchants.isProcMessageDisabled(uuid, "volcano"))
    player.sendMessage("§6Volcano erupted!");

if (!enchants.isSoundsDisabled(uuid))
    player.playSound(player.getLocation(), Sound.ENTITY_GENERIC_EXPLODE, 1f, 1f);

if (!enchants.isParticlesDisabled(uuid))
    /* spawn your particles */;
```

## Full examples

### Currency greed

Pays a lump of Tokens (scaled by level) on each proc — async-safe (no Bukkit world access):

```java
public class GoldRushEnchant implements APIEnchant {

    @Override public boolean asyncSafe() { return true; }  // packet/data only

    @Override
    public void onProc(Player player, EnchantData data) {
        UUID uuid = player.getUniqueId();
        var api = PinnaPrisonAPI.getInstance();
        BigDecimal level = api.getEnchants().getLevel(uuid, "goldrush");
        BigDecimal reward = BigDecimal.valueOf(1000).add(level); // 1000 + level
        api.getCurrencies().addBalanceBoosted(uuid, "tokens", reward);
    }
}
```

{% hint style="success" %}
Want the **animated** enchants — falling meteors, marching mobs, orbiting crystals — that spawn packet entities over the mine? That's the [EdLib API](/p/pinnaprison/developers/edlib-api.md). See [Spawning entities into mines](/p/pinnaprison/developers/edlib-api/mine-entities.md) for full "Comet" and "Dig Crew" examples that drive EdLib entities with goals and break + reward through `MineService`.
{% endhint %}

### AOE explosion

Blows up a sphere of blocks around the mined block:

```java
public class ExplosionEnchant implements APIEnchant {

    @Override public boolean asyncSafe() { return true; } // breakBlocks is packet-based

    @Override
    public void onProc(Player player, EnchantData data) {
        if (!(data instanceof BlockBreakEnchantData hit)) return;
        var mines = PinnaPrisonAPI.getInstance().getMines();
        mines.breakBlocks(player,
                EnchantRegions.sphere(hit.getPosition(), 3),
                false, true, true);
    }
}
```

### Jackhammer

Clears the entire layer the player mined:

```java
public class JackhammerEnchant implements APIEnchant {

    @Override public boolean asyncSafe() { return true; }

    @Override
    public void onProc(Player player, EnchantData data) {
        if (!(data instanceof BlockBreakEnchantData hit)) return;
        PinnaPrisonAPI.getInstance().getMines()
                .breakLayer(player, hit.getPosition().getBlockY(), false, true, true);
    }
}
```

## Reacting to procs (events)

If you only want to *observe* (not implement) enchants, listen for `EnchantProcEvent` — it's cancellable and fires for every enchant proc:

```java
@EventHandler
public void onProc(EnchantProcEvent event) {
    if (event.getEnchantId().equals("jackhammer")) {
        // event.getPlayer(), event.getData()
        // event.setCancelled(true); // veto the proc
    }
}
```

{% hint style="info" %}
`EnchantProcEvent` may fire **asynchronously** (the break path). Don't touch the Bukkit world from the handler unless you hop to the main thread.
{% endhint %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://edseries-plugins.gitbook.io/p/pinnaprison/developers/enchant-api.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
