Minecraft Codec extras and utilities, including Attachments.
Attachments allow you to transfer context into your Codecs like what Minecraft does with RegistryOps
.
Codextra can be added to your project's dependencies by adding the following to your project's build.gradle
:
repositories {
maven {
name = "Kneelawk"
url = "https://maven.kneelawk.com/releases/"
}
}
dependencies {
// On Loom-based xplat projects like Architectury:
modCompileOnly "com.kneelawk.codextra:codextra-xplat-intermediary:<version>"
// On VanillaGradle-based xplat projects:
modCompileOnly "com.kneelawk.codextra:codextra-xplat-mojmap:<version>"
// On Loom Fabric projects:
modImplementation "com.kneelawk.codextra:codextra-fabric:<version>"
include "com.kneelawk.codextra:codextra-fabric:<version>"
// On Userdev Neoforge projects:
implementation "com.kneelawk.codextra:codextra-neoforge:<version>"
jarJar "com.kneelawk.codextra:codextra-neoforge:<version>"
}
You can declare a new attachment type simply by creating an AttachmentKey
of the desired type:
public static final AttachmentKey<Map<Integer, ResourceLocation>> RL_LOOKUP_ATTACHMENT_KEY =
AttachmentKey.ofStaticFieldName();
public static final AttachmentKey<String> NAME_ATTACHMENT_KEY = AttachmentKey.ofStaticFieldName();
Once you have created an attachment key, you can attach attachments of that type to your codecs.
When starting an encode or decode, you can attach an attachment directly to a DynamicOps
like so:
DynamicOps<T> attachedOps = ATTACHMENT_KEY.push(oldOps);
When starting an encode or decode, you don't generally need to pop an attachment from your ops. However, if you are
writing your own Codec
or MapCodec
implementation, calling pop is generally good practice:
DynamicOps<T> unattachedOps = ATTACHMENT_KEY.pop(attachedOps);
When creating a CODEC
for your object type, it may be useful to be able to attach an attachment so that CODEC
s used
within your codec can make use of that attachment.
public static Codec<MyObject> codec(String name) {
return NAME_ATTACHMENT_KEY.attachingCodec(name, CODEC);
}
You can also decode a value and immediately attach it as an attachment:
public static final MapCodec<WithLookup> MAP_CODEC =
RL_LOOKUP_ATTACHMENT_KEY.keyAttachingCodec(LOOKUP_MAP_CODEC, WITH_LOOKUP_RECORD_MAP_CODEC, WithLookup::lookup);
The simplest way to use an attachment in your own codec is by incorporating an attachment as an argument in
a RegistryCodecBuilder
.
public static final Codec<MyObject> CODEC = RecordCodecBuilder.create(instance -> instance.group(
// ...
NAME_ATTACHMENT_KEY.retrieve()
// ...
).apply(instance, MyObject::new));
You can also dispatch codecs based on attachments:
public static final Codec<MyObject> CODEC1 = NAME_ATTACHMENT_KEY.dispatchCodec(name -> getCodecFromName(name));
Or you can use an attachment in conjunction with another codec:
public static final Codec<MyObject> CODEC2 =
NAME_ATTACHMENT_KEY.retrieveWithCodec(ResourceLocation.CODEC, (name, rl) -> new MyObject(name, rl));
See the javadocs for more ways to use attachments.
Attachments also work for StreamCodec
s.
You can attach an attachment to a FriendlyByteBuf
:
FriendlyByteBuf buf = getBuf();
ATTACHMENT_KEY.push(buf);
You can attach a value mid-StreamCodec
:
public static StreamCodec<FriendlyByteBuf, MyObject> streamCodec(String name) {
return NAME_ATTACHMENT_KEY.attachingStreamCodec(name, STREAM_CODEC);
}
You can retrieve an attachment inside a composite StreamCodec
:
public static final StreamCodec<FriendlyByteBuf, MyObject> STREAM_CODEC = StreamCodec.composite(
// ...
NAME_ATTACHMENT_KEY.retrieveStream(), FunctionUtils.nullFunc(),
// ...
MyObject::new
);
Many of the other DFU codecs have stream-codec variants as well.