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

# Models & Animation

> Attach models to any entity, play animations, drive bones and hitboxes from code.

NMEntities is one jar with two layers. The mob, NPC, spawner and effect layer is `org.nexomaker.api`, documented in the rest of this section. Underneath it sits the embedded **BetterModel engine**, whose own API ships in the same jar under `kr.toxicity.model.api` — and that is where models, animations, bones and hitboxes live.

That package is **not relocated** when the plugin jar is shaded, so your addon can call it directly with no extra dependency. `NMEntitiesAPI` deliberately does not wrap it: the engine API is large, well-documented in its own javadoc, and re-exporting it would only add a layer to keep in step.

<Warning>
  This is the vendored engine's surface, not ours. It is stable in practice and every addon that does model work uses it, but it is versioned by the engine rather than by NMEntities — an engine upgrade can move things here in a way it never will in `org.nexomaker.api`. Pin the plugin version you compile against.
</Warning>

## Bukkit types to platform types

The engine is platform-agnostic, so it speaks `PlatformEntity`, `PlatformPlayer`, `PlatformLocation` rather than Bukkit types. One static class converts:

```java theme={null}
import kr.toxicity.model.api.bukkit.platform.BukkitAdapter;

BukkitAdapter.adapt(entity);     // Entity        -> PlatformEntity
BukkitAdapter.adapt(livingMob);  // LivingEntity  -> PlatformLivingEntity
BukkitAdapter.adapt(player);     // Player        -> PlatformPlayer
BukkitAdapter.adapt(location);   // Location      -> PlatformLocation
BukkitAdapter.adapt(world);      // World         -> PlatformWorld
BukkitAdapter.adapt(itemStack);  // ItemStack     -> PlatformItemStack
```

## Finding a model

```java theme={null}
import kr.toxicity.model.api.BetterModel;

ModelRenderer renderer = BetterModel.modelOrNull("flame_knight");   // null if not loaded
Optional<ModelRenderer> maybe = BetterModel.model("flame_knight");
Set<String> names = BetterModel.modelKeys();                        // every loaded model
Collection<ModelRenderer> all = BetterModel.models();
```

Player-model limbs live in a parallel set: `BetterModel.limb(name)`, `limbOrNull(name)`, `limbs()`, `limbKeys()`.

A model name is the `.bbmodel` filename without its extension — the same string a mob's `model:` field takes. Note that a model being *loaded* is not the same as it being a registered [mob](addon-mobs); the engine sees every model in `models/`, the `/nme` commands only see the ones declared as mobs.

## Putting a model on any entity

This is the thing config can't do: attach a model to an entity NMEntities did not spawn — another plugin's boss, a vanilla mob, an armour stand you placed yourself.

```java theme={null}
ModelRenderer renderer = BetterModel.modelOrNull("flame_knight");
if (renderer == null) return;

EntityTracker tracker = renderer.getOrCreate(BukkitAdapter.adapt(entity));
```

`create(...)` always builds a fresh tracker; `getOrCreate(...)` reuses one already on that entity. Both take an optional `TrackerModifier` and an optional `Consumer` that runs before the first update, so you can configure the tracker before any packet goes out.

To find what is already attached, go through the registry:

```java theme={null}
EntityTrackerRegistry registry = BetterModel.registryOrNull(entity.getUniqueId());
if (registry != null) {
    EntityTracker first = registry.first();          // the entity's first model, or null
    EntityTracker named = registry.tracker("key");   // a specific one
    registry.trackers();                             // all of them
    registry.close();                                // detach every model
}
```

An entity can carry more than one model at once, which is why the registry sits between the entity and its trackers.

## Animations

```java theme={null}
tracker.animate("walk");
tracker.stopAnimation("walk");
```

`animate` returns `false` when the model has no animation by that name. For anything beyond "play it", pass an `AnimationModifier`:

```java theme={null}
import kr.toxicity.model.api.animation.AnimationIterator;
import kr.toxicity.model.api.animation.AnimationModifier;

AnimationModifier once = AnimationModifier.builder()
    .type(AnimationIterator.Type.PLAY_ONCE)
    .speed(1.5f)
    .priority(2)
    .build();

tracker.animate("slam", once);
tracker.animate("slam", once, () -> plugin.getLogger().info("slam finished"));
```

The builder covers `predicate` (a `BooleanSupplier` gating whether it plays), `start` and `end` keyframes, `priority`, loop `type`, `speed` (fixed or a supplier, so it can follow the mob's movement speed), `override`, and `player` for an animation only one viewer sees. `AnimationModifier.DEFAULT` and `DEFAULT_WITH_PLAY_ONCE` are there for the common cases.

`replace(target, animation, modifier)` swaps one animation for another while it is running — the clean way to change gait without a visible reset.

The effect DSL's animation mechanics drive this same tracker, so an addon playing an animation from code and a config line doing it produce identical results.

## Who can see it

```java theme={null}
tracker.hide(BukkitAdapter.adapt(player));     // this player stops seeing the model
tracker.show(BukkitAdapter.adapt(player));
tracker.isHide(BukkitAdapter.adapt(player));
tracker.isSpawned(player.getUniqueId());
tracker.playerCount();
```

Per-player visibility is a packet-level thing — the entity is untouched, so vanish plugins, spectator logic and instanced content can all use it without side effects.

## Bones

Every bone in the Blockbench model is a `RenderedBone` you can reach and restyle at runtime:

```java theme={null}
import kr.toxicity.model.api.tracker.TrackerUpdateAction;
import kr.toxicity.model.api.util.function.BonePredicate;

tracker.bone("head");                    // a single bone, or null
tracker.bones();                         // all of them

// tint just the head red
tracker.update(TrackerUpdateAction.tint(0xFF0000), BonePredicate.name("head"));

// hide a part entirely
tracker.update(TrackerUpdateAction.togglePart(false), BonePredicate.name("cape"));
```

The available actions are `brightness`, `glow`, `glowColor`, `viewRange`, `tint`, `previousTint`, `enchant`, `togglePart`, `itemStack`, `billboard`, `itemMapping`, `moveDuration`, plus `composite(...)` to apply several at once and `perBone(...)` to compute a different action per bone.

`BonePredicate.name("head")` matches by name, `BonePredicate.tag(...)` by [bone tag](bone-tags), and `BonePredicate.TRUE` hits every bone. Predicates compose with `and`, `or` and `negate`.

## Hitboxes and headshots

Models carry per-bone hitboxes, and a tracker can listen to what happens to them. This is how you build damage zones today:

```java theme={null}
import kr.toxicity.model.api.event.hitbox.HitBoxDamagedEvent;

tracker.listenHitBox(HitBoxDamagedEvent.class, event -> {
    if (event.getHitBox().groupName().name().equalsIgnoreCase("head")) {
        event.setDamage(event.getDamage() * 2.5f);
    }
});
```

`HitBoxDamagedEvent` is cancellable and its damage is mutable, so you can multiply, floor or veto per bone. `HitBoxInteractAtEvent` gives you the same for right-clicks, carrying the player, the hand and the exact hit position on the bone.

<Note>
  Configurable damage zones — headshot multipliers declared in a mob's YAML — are on the roadmap. Until then this listener is the supported way to do it, and it is what that feature will be built on.
</Note>

## Engine events

The engine has its own event type, `ModelEvent`, rather than one Bukkit event class per event. There are two ways to listen.

**Through Bukkit.** Every engine event is delivered wrapped in a single `BetterModelBukkitEvent`:

```java theme={null}
@EventHandler
public void onModelEvent(BetterModelBukkitEvent event) {
    event.as(ModelSpawnAtPlayerEvent.class, spawn -> {
        if (isHidden(spawn.player())) spawn.setCancelled(true);
    });
}
```

**Through the event bus**, which is narrower and unregisterable:

```java theme={null}
ModelEventListener listener = BetterModel.eventBus().subscribe(
    BukkitEventApplication.of(this),
    ModelImportedEvent.class,
    event -> getLogger().info("loaded model " + event.renderer().name()));

// later
listener.unregister();
```

`BukkitEventApplication` holds a weak reference to your plugin and checks that it is still enabled, so a disabled addon stops receiving events without leaking.

### The events

| Event                                                         | Carries                                                              | Cancellable |
| ------------------------------------------------------------- | -------------------------------------------------------------------- | ----------- |
| `ModelSpawnAtPlayerEvent`                                     | `player`, `tracker` — a model becoming visible to someone            | Yes         |
| `ModelDespawnAtPlayerEvent`                                   | `player`, `tracker`                                                  | No          |
| `PlayerShowTrackerEvent` / `PlayerHideTrackerEvent`           | `tracker`, `player`                                                  | Yes         |
| `CreateEntityTrackerEvent`                                    | `tracker` — a model attached to an entity                            | No          |
| `CreateDummyTrackerEvent`                                     | `tracker` — a location-only model, no entity behind it               | No          |
| `CloseTrackerEvent`                                           | `tracker`, `reason`                                                  | No          |
| `ModelImportedEvent`                                          | `blueprint`, `renderer` — a `.bbmodel` finished loading              | No          |
| `ModelAssetsEvent`                                            | `type`, `assets` — generated resource-pack assets                    | No          |
| `AnimationSignalEvent`                                        | `player`, `signal` — a named signal fired from a Blockbench keyframe | No          |
| `PlayerPerAnimationStartEvent` / `PlayerPerAnimationEndEvent` | `tracker`, `player`                                                  | No          |
| `MountModelEvent` / `DismountModelEvent`                      | `tracker`, `bone`, `hitBox`, `entity`                                | Yes         |
| `CreatePlayerSkinEvent` / `RemovePlayerSkinEvent`             | `modelProfile`                                                       | Remove only |
| `PluginStartReloadEvent`                                      | `zipper` — the pack being built                                      | No          |
| `PluginEndReloadEvent`                                        | `result`                                                             | No          |
| `HitBoxCreateEvent` / `HitBoxRemoveEvent`                     | `hitBox`                                                             | No          |
| `HitBoxDamagedEvent`                                          | `hitBox`, `source`, mutable `damage`                                 | Yes         |
| `HitBoxInteractAtEvent`                                       | `who`, `hitBox`, `hand`, `position`                                  | Yes         |
| `HitBoxMountEvent` / `HitBoxDismountEvent`                    | `hitBox`, `entity`                                                   | No          |

`AnimationSignalEvent` is worth calling out: you place a signal on a keyframe in Blockbench and it reaches your addon at exactly that frame — sound on footfall, damage on the frame the sword lands.

NMEntities' own events — mob spawns, NPC interactions, reloads — are ordinary Bukkit events and live in [`org.nexomaker.api.event`](addon-events).
