Skip to content

API ‐ Extensible Playback Speed

Sleys edited this page Sep 22, 2026 · 4 revisions

The Playback Speed system is fully extensible.

Third-party mods may register custom playback speed modifiers, allowing animations to compute their playback speed using arbitrary logic.

Every playback modifier consists of three parts:

  • A parameter record.
  • A playback modifier type.
  • A registry entry.

🧩 Architecture

ExtendedDatapacksRegistry
            │
            ▼
IPlaySpeedModifierType
            │
            ▼
     paramsCodec()
            │
            ▼
IPlaySpeedModifierParams
            │
            ▼
         modify(...)
            │
            ▼
 Animation Playback Speed

The registry serializes modifier types using DFU (Codec), while modifier parameters are decoded through the MapCodec supplied by each registered type.


📦 Step 1 — Create the Parameter Record

Parameter objects contain both:

  • The serialized configuration.
  • The playback speed logic.

Every parameter record must implement:

IPlaySpeedExternalModifierParams

which extends the non-sealed extension point of:

IPlaySpeedModifierParams

Example

public record ConstantAnimationSpeed(Float constant)
        implements IPlaySpeedExternalModifierParams {

    public static final MapCodec<ConstantAnimationSpeed> CODEC =
        RecordCodecBuilder.mapCodec(instance ->
            instance.group(
                Codec.FLOAT.fieldOf("constant")
                    .forGetter(ConstantAnimationSpeed::constant)
            ).apply(instance, ConstantAnimationSpeed::new)
        );

    @Override
    public <T extends DynamicAnimation> float modify(
            T self,
            LivingEntityPatch<?> patch,
            float speed,
            float prevElapsedTime,
            float elapsedTime) {

        return constant;
    }
}

The modify() method is invoked every tick while the animation is playing.


📦 Step 2 — Create the Modifier Type

Playback modifier types expose the codec responsible for decoding their parameter object.

They implement:

IPlaySpeedModifierType

Example

public enum ExamplePlaybackTypes implements IPlaySpeedModifierType {

    CONSTANT(ConstantAnimationSpeed.CODEC);

    private final MapCodec<? extends IPlaySpeedModifierParams> codec;

    ExamplePlaybackTypes(
            MapCodec<? extends IPlaySpeedModifierParams> codec) {
        this.codec = codec;
    }

    @Override
    public MapCodec<? extends IPlaySpeedModifierParams> paramsCodec() {
        return codec;
    }
}

Each enum constant represents an individual playback modifier type.


📦 Step 3 — Register the Modifier Type

Playback modifier types are registered through the centralized:

ExtendedDatapacksRegistry

Use the ANIMATION_PLAYBACK registry entry:

ExtendedDatapacksRegistry.ANIMATION_PLAYBACK.register(
        MOD_ID,
        ExamplePlaybackTypes.class
);

The registry accepts either a single enum class or multiple enum classes.

Registering multiple types

ExtendedDatapacksRegistry.ANIMATION_PLAYBACK.register(
        MOD_ID,
        FirstPlaybackTypes.class,
        SecondPlaybackTypes.class,
        ThirdPlaybackTypes.class
);

The registry automatically verifies that every supplied enum implements:

IPlaySpeedModifierType

Invalid classes are rejected and a warning is emitted to the log.

Every enum constant becomes a playback modifier.

The generated identifier follows the format:

<modid>:<enum_name>

For example:

example_mod:constant

Registry API

The centralized registry exposes the following general form:

ExtendedDatapacksRegistry.ANIMATION_PLAYBACK.register(
        String modId,
        Class<? extends Enum<?>>... enumClasses
);

This same registration system is also used by Extended Datapacks for other animation extension points, such as animation events, accessors, groups, and armatures.


📄 Datapack Usage

Once registered, the modifier can immediately be used inside animation JSON.

{
  "play_speed_modifier": {
    "type": "example_mod:constant",
    "params": {
      "constant": 1.5
    }
  }
}

The params object is automatically decoded using the MapCodec returned by the registered playback type.


⚙️ Registration Lifecycle

Playback modifiers must be registered before Extended Datapacks loads and consumes its animation registries.

For example, registration should occur during your mod's initialization:

private static void registerPlaybackModifiers() {
    ExtendedDatapacksRegistry.ANIMATION_PLAYBACK.register(
            ExtendedDatapacks.MODID,
            ExamplePlaybackTypes.class
    );
}

Attempting to register new playback modifiers after the registry has been consumed will throw:

RegistryObjectException

This restriction exists because the animation codecs resolve the available playback modifier types during registry initialization.


🔄 Serialization

The playback modifier registry internally uses DFU.

Conceptually, the registry exposes:

Codec<IPlaySpeedModifierType>

Each playback type supplies its own:

MapCodec<? extends IPlaySpeedModifierParams>

This allows every modifier to define its own parameter structure independently.

For example, one modifier may use:

{
  "constant": 1.5
}

while another could define an entirely different structure:

{
  "minimum": 0.5,
  "maximum": 2.0
}

The registry only needs to identify the modifier type; the registered type is responsible for decoding its parameters.


🧱 Complete Example

A minimal external playback modifier therefore consists of:

1. Parameter record

public record ConstantAnimationSpeed(Float constant)
        implements IPlaySpeedExternalModifierParams {

    public static final MapCodec<ConstantAnimationSpeed> CODEC =
        RecordCodecBuilder.mapCodec(instance ->
            instance.group(
                Codec.FLOAT.fieldOf("constant")
                    .forGetter(ConstantAnimationSpeed::constant)
            ).apply(instance, ConstantAnimationSpeed::new)
        );

    @Override
    public <T extends DynamicAnimation> float modify(
            T self,
            LivingEntityPatch<?> patch,
            float speed,
            float prevElapsedTime,
            float elapsedTime) {

        return constant;
    }
}

2. Modifier type

public enum ExamplePlaybackTypes
        implements IPlaySpeedModifierType {

    CONSTANT(ConstantAnimationSpeed.CODEC);

    private final MapCodec<? extends IPlaySpeedModifierParams> codec;

    ExamplePlaybackTypes(
            MapCodec<? extends IPlaySpeedModifierParams> codec) {
        this.codec = codec;
    }

    @Override
    public MapCodec<? extends IPlaySpeedModifierParams> paramsCodec() {
        return codec;
    }
}

3. Registration

ExtendedDatapacksRegistry.ANIMATION_PLAYBACK.register(
        MOD_ID,
        ExamplePlaybackTypes.class
);

4. JSON

{
  "play_speed_modifier": {
    "type": "example_mod:constant",
    "params": {
      "constant": 1.5
    }
  }
}

⚠️ Notes

  • Custom playback modifiers should implement IPlaySpeedExternalModifierParams rather than directly implementing IPlaySpeedModifierParams.
  • Playback modifier types must implement IPlaySpeedModifierType.
  • Register modifier enums through ExtendedDatapacksRegistry.ANIMATION_PLAYBACK.
  • Multiple enum classes can be registered in a single call.
  • The registry automatically validates that supplied classes implement the appropriate registry interface.
  • Registration must occur before Extended Datapacks initializes and consumes its animation registries.
  • The generated modifier identifier is based on the mod ID and enum constant name.
  • The params object is decoded using the MapCodec supplied by the registered modifier type.
  • The centralized ExtendedDatapacksRegistry is now the recommended registration API; the underlying PlaySpeedModifierTypeRegistry remains an implementation detail.

EF: Extended Datapacks

📦 Data

🧰 Core

👁️ Visuals

⚔️ Combat

🗝️ Skill Builder

🛠️ Innate Builder

🗡️ Animation Operations

⚙️ Third-Party

Clone this wiki locally