English
Animation System
The EmakiCoreLib animation system provides cross-module animation timing infrastructure: animation definition registration, tick-accurate keyframe (K-frame) scheduling, priority pre-emption and conflict arbitration, and playback lifecycle listeners. It owns "what happens at which tick" only; the model animation itself is played by the consumer (e.g. EmakiMobs) through its own model backend bridge
Core concepts
| Concept | Type | Description |
|---|---|---|
| Animation definition | AnimationDefinition | Immutable record: id, engine animation name (engineAnimation, never interpreted by CoreLib), duration in ticks, loop flag, priority, conflict policy, keyframe list |
| Keyframe | AnimationKeyframe | Fires at tick tick after playback starts; payload is one CoreLib action pipeline line (blank frames are legal listener-only markers) |
| Priority | AnimationPriority | Five tiers AMBIENT < UTILITY < ACTION < REACTIVE < TERMINAL; a higher tier pre-empts a lower one |
| Conflict policy | AnimationConflictPolicy | On equal priority: REPLACE (new playback wins) or REJECT (new request refused) |
| Playback handle | AnimationPlaybackHandle | States PLAYING / FINISHED / CANCELLED; cancel() is idempotent |
| Playback listener | AnimationListener | onPlay / onKeyframe / onStop, dispatched on the entity's owner thread |
Third-party API
Depend on emaki-corelib-api as provided (never shade). All entry points live on EmakiCoreLibApi:
java
// Register a definition (owner-scoped, revocable; the registry survives a CoreLib reload, so no rebuild callback is needed)
AnimationRegistration reg = EmakiCoreLibApi.registerAnimation(plugin,
AnimationDefinition.of("my_boss_slam", "slam_anim", 30, false,
AnimationPriority.ACTION, AnimationConflictPolicy.REPLACE,
List.of(
AnimationKeyframe.of(0, "self | play_sound sound=minecraft:entity.wither.shoot", "windup"),
AnimationKeyframe.of(12, "self | spawn_particle particle=explosion", "impact")
)));
// Play (callable from any thread; the future's completion thread is not fixed)
CompletableFuture<EmakiResult<AnimationPlaybackHandle>> future =
EmakiCoreLibApi.playAnimationAsync(plugin, entity, "my_boss_slam",
CoreActionExecutionContext.builder().caster(entity).phase("animation").build());
// Stop / observe
EmakiCoreLibApi.stopAnimation(plugin, entity, "my_boss_slam");
AnimationRegistration listener = EmakiCoreLibApi.addAnimationListener(plugin, new AnimationListener() {
@Override public void onPlay(Entity e, AnimationDefinition d, UUID id) {
// forward d.engineAnimation () to your model backend here
}
});Threading & lifecycle contract
playAnimationAsyncmay be called from any thread; keyframe action lines execute on the entity's owner thread (Folia-safe), scheduled through CoreLib'sExecutionDispatcher- Keyframe payloads compile through the shared action engine and are cached per line; a line that fails to compile is logged and skipped without affecting other frames
- Handles must be closed in
onDisable; when an owner plugin is disabled its registrations and in-flight playbacks are revoked automatically - The animation definition registry survives a CoreLib reload, so no rebuild callback is needed. What does get rebuilt on reload is the action stage table (
registerActionStageand friends), so those stages must be replayed throughonStageRegistryRebuilt/addStageRegistryRebuildListener - With CoreLib absent every entry point returns a stable unavailable/inactive object instead of throwing
Division of labour with EmakiMobs
| Layer | Owner | Contents |
|---|---|---|
| Timing infrastructure | EmakiCoreLib | definition registry, K-frame scheduler, priority arbitration, listener dispatch |
| Model rendering bridges | EmakiMobs | ModelEngine / BetterModel bridges, engineAnimation interpretation, LOD |
| Gameplay mapping | EmakiMobs | standard actions (idle/walk/run/attack/hurt/death) and trigger bindings |
Mob semantics (which mob uses which blueprint, which animation hurt plays) never enter CoreLib, and CoreLib never references any model plugin class
Configuration
The animation system itself has no config section; durations, priorities, and conflict policies come from the registering side. Consumer-side model/LOD settings are documented in EmakiMobs entity models