Custom data types

Add reusable JSON value shapes with codecs — usable across your powers.

A data type is any reusable value shape a field can accept — an attribute modifier, a status effect, a rope endpoint. In Apoli a data type is just a codec: define one once, and reference it from as many power/action/condition codecs as you like.

A codec is a data type

Say your powers need a “swing” value — an axis and an amount:

public record Swing(Direction.Axis axis, float amount) {
    public static final Codec<Swing> CODEC = RecordCodecBuilder.create(i -> i.group(
        Direction.Axis.CODEC.fieldOf("axis").forGetter(Swing::axis),
        Codec.FLOAT.optionalFieldOf("amount", 1.0f).forGetter(Swing::amount)
    ).apply(i, Swing::new));
}

Reference it from any config codec:

Swing.CODEC.fieldOf("swing").forGetter(Cfg::swing)

A data pack then writes:

{ "swing": { "axis": "y", "amount": 2.0 } }

There’s no registry to touch — the codec travels with the field that uses it.

Single-or-list

Many Apoli fields accept one value or an array. Expose both with a small helper:

public static final Codec<List<Swing>> LIST_OR_SINGLE =
    Codec.either(Codec.list(Swing.CODEC), Swing.CODEC)
        .xmap(e -> e.map(l -> l, List::of),
              l -> l.size() == 1 ? Either.right(l.get(0)) : Either.left(l));

Then LIST_OR_SINGLE.fieldOf("swings") accepts both forms. This is how modifier/modifiers and effect/effects work.

Reusing Apoli’s data types

Prefer reusing Apoli’s existing codecs over rolling your own — they’re public:

CodecJSON
AttributeModifier.CODEC{ operation, value, attribute }
Comparison.CODEC"<="
Space.CODEC / Shape.CODEC"local" / "sphere"
HudRender.CODECresource bar config
Expression.FLOAT_OR_EXPRa number or an expression

Reaching for these keeps your JSON consistent with the rest of Apoli, and means fixes and aliases apply to your fields for free.

Aliasing field names

To accept a legacy field name for a data type, use the field-alias helpers at registration rather than branching inside the codec.