Skip to content

API Extensible Accessors

Sleys edited this page Sep 22, 2026 · 1 revision

The Animation Accessor system is fully extensible.

Third-party mods may register custom animation accessor types, allowing them to define new animation structures, properties, and registration logic that can be loaded directly from animation JSON.

An accessor definition combines two independently extensible components:

  • An animation accessor, which defines how the animation itself is constructed.
  • An animation properties object, which defines additional properties applied to the resulting animation.

🧩 Architecture

ExtendedDatapacksRegistry
          │
          ▼
 IAnimationAccessorType
          │
          ├───────────────┐
          ▼               ▼
 definitionCodec()   accessorCodec()
          │               │
          │               ▼
          │       IAnimationAccessor<T>
          │               │
          │               ▼
          │          register(...)
          │               │
          │               ▼
          │          DynamicAnimation
          │
          └──────► propertyCodec()
                          │
                          ▼
                  IAnimationProperties<T>
                          │
                          ▼
                      applyTo(...)

An accessor type therefore determines how its complete animation definition is decoded and registered.


📦 Step 1 — Create the Animation Accessor

An animation accessor implements:

IAnimationAccessor<T>

where T is the DynamicAnimation type produced by the accessor.

The accessor is responsible for:

  • Defining its serialized fields.
  • Identifying its accessor type.
  • Creating and registering the corresponding Epic Fight animation.
  • Applying the decoded animation properties.

Example

public record AttackAnimationAccessor(
        float transitionTime,
        String animationPath,
        IArmatureType armatureType,
        AnimationPhase... phases
) implements IAnimationAccessor<AttackAnimation> {

    public static final MapCodec<AttackAnimationAccessor> CODEC =
        RecordCodecBuilder.mapCodec(instance ->
            instance.group(
                Codec.FLOAT.fieldOf("transition_time")
                    .forGetter(AttackAnimationAccessor::transitionTime),

                Codec.STRING.fieldOf("animation")
                    .forGetter(AttackAnimationAccessor::animationPath),

                ArmatureTypeRegistry.CODEC.fieldOf("armature")
                    .forGetter(AttackAnimationAccessor::armatureType),

                AnimationPhase.CODEC.codec()
                    .listOf()
                    .fieldOf("phases")
                    .forGetter(r -> Arrays.asList(r.phases()))

            ).apply(instance, (transition, path, armature, phases) ->
                new AttackAnimationAccessor(
                    transition,
                    path,
                    armature,
                    phases.toArray(AnimationPhase[]::new)
                )
            )
        );

    @Override
    public IAnimationAccessorType accessorType() {
        return CombatAnimationAccessors.ATTACK;
    }

    @Override
    public AnimationManager.AnimationAccessor<AttackAnimation> register(
            AnimationManager.AnimationBuilder builder,
            IAnimationProperties<AttackAnimation> property) {

        return builder.nextAccessor(animationPath, accessor -> {
            var animation = new AttackAnimation(
                    transitionTime,
                    accessor,
                    armatureType.armature(),
                    Arrays.stream(phases)
                            .map(phase ->
                                phase.parseToEpicFightPhases(armatureType))
                            .toArray(AttackAnimation.Phase[]::new)
            );

            property.applyTo(animation);
            this.isSuccessful(accessor);

            return animation;
        });
    }
}

The register() method receives the decoded animation properties and is responsible for constructing the actual Epic Fight animation.


📦 Step 2 — Create the Animation Properties

The second part of an accessor definition is its property object.

Properties implement:

IAnimationProperties<T>

where T corresponds to the animation produced by the accessor.

For example, an attack animation can have properties such as:

{
  "basis_attack_speed": 0.70,
  "cancellable_movement": false
}

The properties object is decoded independently from the accessor itself and is subsequently passed to:

property.applyTo(animation);

This separation allows the same accessor architecture to support different property sets for different animation types.


📦 Step 3 — Combine the Accessor and Property Codecs

Every accessor type uses:

AnimationAccessorDefinitionCodec<T>

to associate its accessor codec with its property codec.

public record AnimationAccessorDefinitionCodec<T extends DynamicAnimation>(
        MapCodec<? extends IAnimationAccessor<T>> accessorCodec,
        MapCodec<? extends IAnimationProperties<T>> propertyCodec) {

    @SuppressWarnings({"unchecked", "RedundantCast"})
    public MapCodec<AnimationAccessorDefinition<T>> combined() {
        MapCodec<IAnimationAccessor<T>> castedAccessor =
                (MapCodec<IAnimationAccessor<T>>) (MapCodec<?>) accessorCodec;

        MapCodec<IAnimationProperties<T>> castedProperty =
                (MapCodec<IAnimationProperties<T>>) (MapCodec<?>) propertyCodec;

        return RecordCodecBuilder.mapCodec(instance ->
                instance.group(
                        castedAccessor.forGetter(
                                AnimationAccessorDefinition::accessor),

                        castedProperty.forGetter(
                                AnimationAccessorDefinition::properties)

                ).apply(instance, AnimationAccessorDefinition::new)
        );
    }
}

Conceptually, this produces a definition containing:

AnimationAccessorDefinition<T>
        │
        ├── accessor
        │     └── IAnimationAccessor<T>
        │
        └── properties
              └── IAnimationProperties<T>

This allows the animation accessor and its properties to be decoded as one complete animation definition.


📦 Step 4 — Create the Accessor Type

Accessor types implement:

IAnimationAccessorType

An accessor type provides:

  • Its unique identifier.
  • The codec describing its complete animation definition.

Example

public enum CombatAnimationAccessors
        implements IAnimationAccessorType {

    ATTACK(
        "epicfight_edp:attack",
        new AnimationAccessorDefinitionCodec<>(
            AttackAnimationAccessor.CODEC,
            AttackAnimationProperties.CODEC
        )
    ),

    COMBO_ATTACK(
        "epicfight_edp:combo_attack",
        new AnimationAccessorDefinitionCodec<>(
            ComboAttackAccessor.CODEC,
            ComboAttackAnimationProperties.CODEC
        )
    ),

    DASH_ATTACK(
        "epicfight_edp:dash_attack",
        new AnimationAccessorDefinitionCodec<>(
            DashAttackAnimationAccessor.CODEC,
            DashAttackAnimationProperties.CODEC
        )
    ),

    AIR_ATTACK(
        "epicfight_edp:air_attack",
        new AnimationAccessorDefinitionCodec<>(
            AirAttackAnimationAccessor.CODEC,
            AirAttackAnimationProperties.CODEC
        )
    ),

    AIM(
        "epicfight_edp:aim",
        new AnimationAccessorDefinitionCodec<>(
            AimAnimationAccessor.CODEC,
            AimAnimationProperties.CODEC
        )
    );

    private final String definitionId;
    private final AnimationAccessorDefinitionCodec<?> definitionCodec;

    CombatAnimationAccessors(
            String definitionId,
            AnimationAccessorDefinitionCodec<?> codec) {

        this.definitionId = definitionId;
        this.definitionCodec = codec;
    }

    @Override
    public String id() {
        return definitionId;
    }

    @Override
    public AnimationAccessorDefinitionCodec<?> definitionCodec() {
        return definitionCodec;
    }
}

Unlike animation events and playback modifiers, accessor identifiers are explicitly defined by the accessor type through:

String id()

This means the identifier does not have to be derived from the enum constant name.

For example:

epicfight_edp:attack
epicfight_edp:combo_attack
epicfight_edp:dash_attack

📦 Step 5 — Register the Accessor Type

Accessor types are registered through the centralized:

ExtendedDatapacksRegistry

using:

ANIMATION_ACCESSOR

Single accessor registry

ExtendedDatapacksRegistry.ANIMATION_ACCESSOR.register(
        MOD_ID,
        CombatAnimationAccessors.class
);

Multiple accessor registries

Multiple enum classes can be registered in a single call:

ExtendedDatapacksRegistry.ANIMATION_ACCESSOR.register(
        MOD_ID,
        CombatAnimationAccessors.class,
        HitAnimationAccessors.class,
        InteractionAnimationAccessors.class,
        LivingAnimationAccessors.class
);

The registry automatically verifies that every supplied enum implements:

IAnimationAccessorType

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


📄 Datapack Usage

Once an accessor type has been registered, it can be used directly inside animation JSON.

For example:

{
  "mod_id": "efdg",
  "type": "combo_attack",

  "transition_time": 0.25,
  "animation": "biped/combat/greatsword_dual_auto_1",

  "armature": "epicfight:entity/biped",

  "basis_attack_speed": 0.70,
  "cancellable_movement": false,

  "phases": [
    {
      "antic": 0.0,
      "pre_delay": 0.2,
      "contact": 0.4,
      "recovery": 0.45,
      "hand": "off_hand",

      "properties": {
        "damage": 0.85
      },

      "colliders": [
        {
          "joint": "Tool_L"
        }
      ]
    },

    {
      "antic": 0.45,
      "pre_delay": 0.5,
      "contact": 0.7,
      "recovery": 1.4,
      "hand": "main_hand",

      "properties": {
        "damage": 0.85
      },

      "colliders": [
        {
          "joint": "Tool_R"
        }
      ]
    }
  ]
}

The value:

"type": "combo_attack"

identifies the registered accessor type.

The corresponding accessor and property codecs then decode the remaining fields.


🔄 Definition Decoding

An accessor definition is conceptually decoded as:

JSON
 │
 ▼
"type"
 │
 ▼
IAnimationAccessorType
 │
 ▼
AnimationAccessorDefinitionCodec
 │
 ├── accessorCodec
 │      │
 │      ▼
 │   IAnimationAccessor<T>
 │
 └── propertyCodec
        │
        ▼
   IAnimationProperties<T>

The resulting definition contains both components:

AnimationAccessorDefinition<T>
        │
        ├── accessor
        │
        └── properties

The accessor can then construct the animation and apply the decoded properties to it.


⚙️ Registration Lifecycle

Animation accessors must be registered before Extended Datapacks initializes and consumes its animation registries.

Attempting to register an accessor after the registry has been consumed will throw:

RegistryObjectException

This restriction exists because the animation definition codecs resolve the available accessor types during initialization.

Registration should therefore happen during mod initialization:

private static void registerAnimationAccessors() {
    ExtendedDatapacksRegistry.ANIMATION_ACCESSOR.register(
            MOD_ID,
            CombatAnimationAccessors.class
    );
}

🧱 Creating a Custom Accessor

A third-party mod can therefore introduce an entirely new animation type without modifying the existing Extended Datapacks accessor implementations.

The basic structure is:

YourAccessor
      │
      ├── CODEC
      │
      └── register(...)
             │
             ▼
        YourAnimation

YourProperties
      │
      ├── CODEC
      │
      └── applyTo(...)
             │
             ▼
        YourAnimation

          │
          ▼
YourAccessorType
          │
          ▼
ExtendedDatapacksRegistry

This makes the accessor system an extension point rather than a fixed collection of predefined animation types.


⚠️ Notes

  • Custom animation accessors should implement IAnimationAccessor<T>.
  • Custom animation properties should implement IAnimationProperties<T>.
  • Custom accessor types must implement IAnimationAccessorType.
  • AnimationAccessorDefinitionCodec combines the accessor and property MapCodecs into a single definition codec.
  • Accessor identifiers are explicitly supplied through IAnimationAccessorType.id().
  • Register accessor enum classes through ExtendedDatapacksRegistry.ANIMATION_ACCESSOR.
  • Multiple accessor enum classes can be registered in a single call.
  • The centralized ExtendedDatapacksRegistry is the recommended registration API.
  • Registration must occur before Extended Datapacks initializes and consumes its animation registries.
  • The accessor's register() method is responsible for constructing the corresponding Epic Fight animation.
  • Animation properties are applied after the animation is constructed through IAnimationProperties.applyTo(...).
  • Each accessor type defines its own JSON structure through its AnimationAccessorDefinitionCodec.

EF: Extended Datapacks

📦 Data

🧰 Core

👁️ Visuals

⚔️ Combat

🗝️ Skill Builder

🛠️ Innate Builder

🗡️ Animation Operations

⚙️ Third-Party

Clone this wiki locally