Data Attachments
Overview
Data attachments let you store typed custom data directly on game objects, avoiding messy caches or mixins. They are useful for persisted pieces of state that belong to a player, entity, block entity, level or chunk.
Balm exposes a common API over Fabric's and NeoForge's implementations. Data Attachments are not supported on Forge, as Forge does not provide an equivalent API.
For attaching data to items, you should use Data Components instead.
Defining the data
Create a class for your attachment. It will need a Codec if you want to have it saved.
public class MyPlayerData {
public static final Codec<MyPlayerData> CODEC = RecordCodecBuilder.create(instance -> instance.group(
Codec.INT.fieldOf("cooldownTicks").forGetter(MyPlayerData::getCooldownTicks)
).apply(instance, MyPlayerData::new));
private int cooldownTicks;
public MyPlayerData() {
this(0);
}
public MyPlayerData(int cooldownTicks) {
this.cooldownTicks = cooldownTicks;
}
public int getCooldownTicks() {
return cooldownTicks;
}
public void setCooldownTicks(int cooldownTicks) {
this.cooldownTicks = cooldownTicks;
}
}
Registering attachments
Register attachment types through BalmRegistrars.dataAttachmentTypes(). The registration returns a DataAttachmentLookup<T> that you keep around and use for reads and writes.
public class ModDataAttachments {
public final DataAttachmentLookup<MyPlayerData> playerData;
public ModDataAttachments(BalmDataAttachmentTypeRegistrar registrar) {
playerData = registrar
.register("player_data", MyPlayerData.CODEC, MyPlayerData::new, true)
.asLookup();
}
}
Then initialize it from your mod's common initialization code.
public class MyMod {
public static final String MOD_ID = "mymod";
private static @Nullable ModDataAttachments dataAttachments;
public static void initialize(BalmRegistrars registrars) {
registrars.dataAttachmentTypes(registrar -> dataAttachments = new ModDataAttachments(registrar));
}
public static ModDataAttachments dataAttachments() {
return Objects.requireNonNull(dataAttachments);
}
}
Accessing attachments
DataAttachmentLookup<T> supports players, entities, block entities, levels and chunks.
DataAttachmentLookup<MyPlayerData> lookup = MyMod.dataAttachments().playerData;
MyPlayerData data = lookup.getOrCreate(player);
data.setCooldownTicks(20 * 60);
if (lookup.has(player)) {
MyPlayerData existing = lookup.get(player);
}
lookup.update(player, new MyPlayerData(0));
lookup.remove(player);
The same methods are available for other supported targets.
lookup.getOrCreate(entity);
lookup.getOrCreate(blockEntity);
lookup.getOrCreate(level);
lookup.getOrCreate(chunk);
Builder options
If you need more control when registering your attachment, use the builder overload.
playerData = registrar.register("player_data", builder -> builder
.persistent(MyPlayerData.CODEC)
.initializer(MyPlayerData::new)
.copyOnDeath()
).asLookup();
The builder supports:
persistent(codec)to save and load the attachment value.initializer(supplier)to create a default value forgetOrCreate().copyOnDeath()to copy player data when the player respawns after death.networkSynchronized(streamCodec)to synchronize attachment data to clients.networkSynchronized(streamCodec, predicate)to synchronize only to players that match the predicate.