Skip to content

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 ​

ConceptTypeDescription
Animation definitionAnimationDefinitionImmutable record: id, engine animation name (engineAnimation, never interpreted by CoreLib), duration in ticks, loop flag, priority, conflict policy, keyframe list
KeyframeAnimationKeyframeFires at tick tick after playback starts; payload is one CoreLib action pipeline line (blank frames are legal listener-only markers)
PriorityAnimationPriorityFive tiers AMBIENT < UTILITY < ACTION < REACTIVE < TERMINAL; a higher tier pre-empts a lower one
Conflict policyAnimationConflictPolicyOn equal priority: REPLACE (new playback wins) or REJECT (new request refused)
Playback handleAnimationPlaybackHandleStates PLAYING / FINISHED / CANCELLED; cancel() is idempotent
Playback listenerAnimationListeneronPlay / 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 ​

  • playAnimationAsync may be called from any thread; keyframe action lines execute on the entity's owner thread (Folia-safe), scheduled through CoreLib's ExecutionDispatcher
  • 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 (registerActionStage and friends), so those stages must be replayed through onStageRegistryRebuilt / addStageRegistryRebuildListener
  • With CoreLib absent every entry point returns a stable unavailable/inactive object instead of throwing

Division of labour with EmakiMobs ​

LayerOwnerContents
Timing infrastructureEmakiCoreLibdefinition registry, K-frame scheduler, priority arbitration, listener dispatch
Model rendering bridgesEmakiMobsModelEngine / BetterModel bridges, engineAnimation interpretation, LOD
Gameplay mappingEmakiMobsstandard 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