> ## 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.

# Models & Animation

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

LodeEntities 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 **model engine**, whose own API ships in the same jar under `org.nexomaker.engine.api`, with `LodeEngine` as its entry point. That is where models, animations, bones and hitboxes live.

Your addon calls it directly, with no extra dependency. `LodeEntitiesAPI` deliberately does not wrap it: the engine API is large, and re-exporting it would only add a layer to keep in step.

> [!WARNING]
>
> The engine API moves at its own pace: an engine change can reshape things here in a way it never will in `org.nexomaker.api`. Pin the plugin version you compile against.

## 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 org.nexomaker.engine.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 org.nexomaker.engine.api.LodeEngine;

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

Player-model limbs live in a parallel set: `LodeEngine.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](https://entities.lodemc.net/addon-mobs.md): the engine sees every model in `Mobs/`, the `/le` commands only see the ones a mob uses.

## Putting a model on any entity

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

```java theme={null}
ModelRenderer renderer = LodeEngine.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 = LodeEngine.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 org.nexomaker.engine.api.animation.AnimationIterator;
import org.nexomaker.engine.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 org.nexomaker.engine.api.tracker.TrackerUpdateAction;
import org.nexomaker.engine.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](https://entities.lodemc.net/bone-tags.md), 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 org.nexomaker.engine.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.

## 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 `LodeEngineBukkitEvent`:

```java theme={null}
@EventHandler
public void onModelEvent(LodeEngineBukkitEvent 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 = LodeEngine.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.

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


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