Custom data types
A data type is any reusable value shape a field can accept, such as an attribute modifier, a status effect or a rope endpoint. In Apoli a data type is a codec: define it once, and reference it from as many power, action and condition codecs as you like.
A codec is a data type
Say your powers need a "swing" value, made of 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 is no registry to touch, because the codec travels with the field that uses it.
Single or list
Many Apoli fields accept one value or an array. A small helper exposes both:
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. That is how modifier and modifiers, or effect and effects, work.
Reusing Apoli's data types
Prefer Apoli's existing codecs over writing your own. They are public:
| Codec | JSON |
|---|---|
AttributeModifier.CODEC | { operation, value, attribute } |
Comparison.CODEC | "<=" |
Space.CODEC, Shape.CODEC | "local", "sphere" |
HudRender.CODEC | The resource bar settings |
Expression.FLOAT_OR_EXPR | A number or an expression |
Using these keeps your JSON consistent with the rest of Apoli, and fixes and aliases then apply to your fields as well.
Aliasing field names
To accept a legacy field name for a data type, use the field alias helpers at registration instead of branching inside the codec.