> 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/edlib-api/mine-entities.md).

# Spawning Entities into Mines

This is the practical payoff: do exactly what PinnaPrison's animated enchants (Meteor Shower, Zombie Horde, Dragon Breath…) do — spawn **packet-based** [EdLib entities](/p/pinnaprison/developers/edlib-api/entities.md) over a player's private mine, drive them with [goals](/p/pinnaprison/developers/edlib-api/goals.md), and **break + reward** blocks on impact.

It combines two APIs:

* **EdLib** for the visuals — `EdLibAPI.getInstance()` to create/animate entities.
* **PinnaPrison** for the gameplay — `PinnaPrisonAPI.getInstance().getMines()` to break blocks and pay the player exactly like mining, and the [Enchant API](/p/pinnaprison/developers/enchant-api.md) to hook the action onto mining.

## Why it's safe

Mines live in a shared **void world** and are streamed to players as packets — there are no real blocks or entities there. So everything here is packet-only and runs on the async break pipeline:

* EdLib entities + goals are packet sends → fine off-thread.
* `MineService` breaks are atomic chunk-data writes that send block packets → fine off-thread.

That means your enchant can declare `asyncSafe() { return true; }` and run entirely without touching the main thread.

## The integration points

| You need                    | Get it from                                                                                              |
| --------------------------- | -------------------------------------------------------------------------------------------------------- |
| The proc + the mined block  | `APIEnchant.onProc(player, data)` → `((BlockBreakEnchantData) data).getPosition()`                       |
| The world to spawn in       | `PinnaPrisonAPI.getInstance().getMines().getMinesWorld()`                                                |
| Show the entity to the mine | `MineService.spawnInMine(player, entity)` — see below                                                    |
| Release the entity          | `MineService.despawnInMine(player, entity)` — untrack from the mine + despawn                            |
| Actually break + pay        | `MineService.breakSphere / breakBlocks / breakBlock` (block currencies, autosell, Token Greed are flags) |
| Scheduling                  | `EdLibAPI.getExecutor()` (`asyncLater`, `repeatedAsync`)                                                 |

{% hint style="info" %}
`MineService.breakSphere(player, center, radius, affectBlockCurrencies, affectAutosell, affectTokenGreed)` only breaks blocks that still exist in **that player's mine** and pays for each one like a normal dig. You never have to worry about hitting the real world or another player's mine.
{% endhint %}

### Show it to the whole mine, not just the digger

A mine can have several viewers at once — co-op members and visitors. So **don't** call `entity.addWatcher(player)` / `entity.spawn()` yourself (that shows it to one player). Configure the entity, then hand it to the mine:

```java
MineService mines = PinnaPrisonAPI.getInstance().getMines();
mines.spawnInMine(player, entity);   // tracked by the mine, then spawned for every viewer
```

`spawnInMine` mirrors exactly what the built-in enchants do internally — the entity is **tracked by the mine**, so:

* every current viewer sees it, and anyone who **joins mid-animation** sees it too;
* it's **despawned automatically the moment a player leaves or switches mines** — it won't linger in their new mine;
* it's torn down with the mine.

Set the entity up (block material, gravity, scale…) **before** the call; add equipment and [goals](/p/pinnaprison/developers/edlib-api/goals.md) after. When the animation ends, release it with **`mines.despawnInMine(player, entity)`** (not `entity.remove()`) so the mine stops tracking it. Need the viewer list for your own packet effects? `mines.getMineViewers(player)`.

***

## Example 1 — "Comet" (falling block + impact)

A burning comet streaks down from the sky and slams into the mine, blasting a sphere of ore. This is the Meteor Shower pattern, distilled to the public API.

```java
import es.edwardbelt.edlib.iapi.EdLibAPI;
import es.edwardbelt.edlib.iapi.entity.EdFallingBlock;
import es.edwardbelt.edlib.iapi.entity.goal.impl.EdGoalMove;
import es.edwardbelt.pinnaprison.iapi.PinnaPrisonAPI;
import es.edwardbelt.pinnaprison.iapi.enchant.APIEnchant;
import es.edwardbelt.pinnaprison.iapi.enchant.data.BlockBreakEnchantData;
import es.edwardbelt.pinnaprison.iapi.enchant.data.EnchantData;
import es.edwardbelt.pinnaprison.iapi.service.MineService;
import org.bukkit.*;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.util.Vector;

public class CometEnchant implements APIEnchant {

    // Packet-only: spawning, goals and mine breaks are all safe off the main thread.
    @Override public boolean asyncSafe() { return true; }

    @Override
    public void onProc(Player player, EnchantData data) {
        if (!(data instanceof BlockBreakEnchantData hit)) return;

        EdLibAPI edlib = EdLibAPI.getInstance();
        MineService mines = PinnaPrisonAPI.getInstance().getMines();
        World world = mines.getMinesWorld();

        // Impact = the block the player just mined; spawn the comet 16 blocks above it.
        Vector impact = hit.getPosition().clone().add(new Vector(0.5, 0.5, 0.5));
        Vector spawn = impact.clone().add(new Vector(0, 16, 0));

        EdFallingBlock comet = (EdFallingBlock)
                edlib.createEntity(EntityType.FALLING_BLOCK, new Location(world, spawn.getX(), spawn.getY(), spawn.getZ()));
        comet.setFallingBlock(Material.MAGMA_BLOCK);
        comet.setGravity(false);          // we drive it with a goal
        mines.spawnInMine(player, comet); // visible to everyone in the mine, then spawned

        // Fly to the impact point at 1.4 blocks/tick.
        EdGoalMove fall = new EdGoalMove(impact, 1.4);
        fall.setEachTickRunnable(() -> {
            Vector p = comet.getPosition();
            world.spawnParticle(Particle.FLAME, p.getX(), p.getY(), p.getZ(), 4, 0.2, 0.2, 0.2, 0);
        });
        fall.setEndRunnable(() -> {
            mines.despawnInMine(player, comet); // untrack from the mine + despawn
            // Break + pay a 3-block sphere: block currencies OFF, autosell ON, Token Greed ON.
            int broken = mines.breakSphere(player, impact, 3, false, true, true);
            world.playSound(new Location(world, impact.getX(), impact.getY(), impact.getZ()),
                    Sound.ENTITY_GENERIC_EXPLODE, 1f, 0.8f);
            if (broken > 0) player.sendMessage("§6☄ Comet smashed §e" + broken + "§6 blocks!");
        });
        comet.addGoal(fall);

        // Safety cleanup in case the player leaves mid-flight.
        EdLibAPI.getExecutor().asyncLater(() -> mines.despawnInMine(player, comet), 120, "comet-cleanup");
    }
}
```

Register it (see the [Enchant API](/p/pinnaprison/developers/enchant-api.md) for the config file):

```java
PinnaPrisonAPI.getInstance().getEnchants().registerEnchant("comet", new CometEnchant());
```

***

## Example 2 — "Dig Crew" (mobs with equipment + chained goals)

Spawns a few pickaxe-wielding zombies that march from waypoint to waypoint, biting a pocket of blocks out at each stop, then rot away. This is the Zombie Horde pattern — equipment, swing animations, and a chain of `EdGoalMove` goals whose **end callbacks** break the blocks.

```java
import es.edwardbelt.edlib.iapi.EdLibAPI;
import es.edwardbelt.edlib.iapi.entity.EdEntity;
import es.edwardbelt.edlib.iapi.entity.EntityAnimation;
import es.edwardbelt.edlib.iapi.entity.EntityEquipmentSlot;
import es.edwardbelt.edlib.iapi.entity.goal.impl.EdGoalMove;
import es.edwardbelt.pinnaprison.iapi.enchant.EnchantRegions; // PinnaPrison region helpers
import es.edwardbelt.pinnaprison.iapi.PinnaPrisonAPI;
import es.edwardbelt.pinnaprison.iapi.enchant.APIEnchant;
import es.edwardbelt.pinnaprison.iapi.enchant.data.BlockBreakEnchantData;
import es.edwardbelt.pinnaprison.iapi.enchant.data.EnchantData;
import es.edwardbelt.pinnaprison.iapi.service.MineService;
import org.bukkit.*;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.util.Vector;
import java.util.concurrent.ThreadLocalRandom;

public class DigCrewEnchant implements APIEnchant {

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

    @Override
    public void onProc(Player player, EnchantData data) {
        if (!(data instanceof BlockBreakEnchantData hit)) return;

        EdLibAPI edlib = EdLibAPI.getInstance();
        MineService mines = PinnaPrisonAPI.getInstance().getMines();
        World world = mines.getMinesWorld();
        ThreadLocalRandom rng = ThreadLocalRandom.current();

        Vector origin = hit.getPosition().clone().add(new Vector(0.5, 0.5, 0.5));

        for (int i = 0; i < 5; i++) {
            Vector start = origin.clone().add(new Vector(rng.nextDouble(-2, 2), 0, rng.nextDouble(-2, 2)));
            EdEntity zombie = edlib.createEntity(EntityType.ZOMBIE,
                    new Location(world, start.getX(), start.getY(), start.getZ()));
            zombie.setGravity(false);
            mines.spawnInMine(player, zombie); // visible to the whole mine, then spawned
            zombie.setEquipment(EntityEquipmentSlot.MAIN_HAND, new ItemStack(Material.IRON_PICKAXE));

            // Chain 3 march-and-bite legs.
            Vector from = start;
            for (int leg = 0; leg < 3; leg++) {
                Vector dest = origin.clone().add(new Vector(rng.nextDouble(-8, 8), 0, rng.nextDouble(-8, 8)));
                boolean last = leg == 2;
                EdGoalMove march = new EdGoalMove(dest, 0.5);
                march.setSendRotationEachTick(true);
                march.setEndRunnable(() -> {
                    zombie.playAnimation(EntityAnimation.SWING_MAIN_HAND);
                    // Break a 3x3x3 cuboid where it stopped (block currencies + autosell + Token Greed).
                    mines.breakBlocks(player, EnchantRegions.cuboid(
                            dest.clone().add(new Vector(-1, -1, -1)),
                            dest.clone().add(new Vector(1, 1, 1))), true, true, true);
                    world.playSound(new Location(world, dest.getX(), dest.getY(), dest.getZ()),
                            Sound.ENTITY_ZOMBIE_ATTACK_WOODEN_DOOR, 0.8f, 1f);
                    if (last) mines.despawnInMine(player, zombie);
                });
                zombie.addGoal(march);
                from = dest;
            }

            // Fail-safe despawn.
            EdLibAPI.getExecutor().asyncLater(() -> mines.despawnInMine(player, zombie), 300, "digcrew-cleanup");
        }
    }
}
```

## Respecting the player's settings

Wrap your sounds/particles/messages in the [enchant settings checks](/p/pinnaprison/developers/enchant-api.md#respecting-the-players-settings) so muted players stay muted:

```java
var enchants = PinnaPrisonAPI.getInstance().getEnchants();
if (!enchants.isSoundsDisabled(player.getUniqueId()))
    world.playSound(loc, Sound.ENTITY_GENERIC_EXPLODE, 1f, 1f);
```

## Checklist

* **`asyncSafe() { return true; }`** — entities, goals and `MineService` breaks are all packet-based.
* **Spawn in `mines.getMinesWorld()`** at the mined block's position (`+0.5` to center).
* **Show it with `mines.spawnInMine(player, entity)`**, not `addWatcher`/`spawn` — so it's tracked by the mine: every viewer (and anyone who joins) sees it, and it's despawned when a player leaves.
* **Break with `MineService`**, never with Bukkit — that's what pays the player and resets the mine.
* **Release with `mines.despawnInMine(player, entity)`** (plus an `asyncLater` fail-safe), not `entity.remove()` — so the mine drops its reference.
* **Boss mobs only** (Ender Dragon, Wither): build them with `EdLibAPI.getExecutor().sync(...)`, then animate off-thread.


---

# 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/edlib-api/mine-entities.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.
