Using Rosetta Library
Networking
Rosetta packets use the same declaration on every supported target. Rosetta translates that declaration to legacy Fabric networking, modern Fabric payloads, Forge SimpleChannel, or NeoForge payload registration.
Define a packet
A packet implements RosettaPacket. The packet may be a record and does not need to expose loader-specific payload types or codecs.
package com.example.network;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.rasanovum.rosetta.network.RosettaPacket;
public record RenameRequestC2S(String name) implements RosettaPacket {
public RenameRequestC2S(FriendlyByteBuf buffer) {
this(buffer.readUtf());
}
public void write(FriendlyByteBuf buffer) {
buffer.writeUtf(name);
}
public static void handle(RenameRequestC2S packet, Level level, Player player) {
// This packet is serverbound, so validate the request and modify server state here.
}
}
Packet readers and writers use FriendlyByteBuf on every target. Rosetta creates the required modern CustomPacketPayload.Type and stream codec internally on Minecraft 1.21 and newer.
Register packets
Create one channel for the consuming mod and register each packet direction explicitly:
package com.example.network;
import net.rasanovum.rosetta.network.RosettaNetwork;
public final class ExamplePackets {
private static final RosettaNetwork.Channel CHANNEL =
RosettaNetwork.channel("example_mod");
private ExamplePackets() {}
public static void register() {
CHANNEL.serverbound(
"rename_request",
RenameRequestC2S.class,
RenameRequestC2S::write,
RenameRequestC2S::new,
RenameRequestC2S::handle
);
CHANNEL.clientbound(
"rename_result",
RenameResultS2C.class,
RenameResultS2C::write,
RenameResultS2C::new,
RenameResultS2C::handle
);
}
}
Call ExamplePackets.register() during the consuming mod’s initialization.
Send packets
Send a client-to-server packet from client code:
RosettaNetwork.sendToServer(new RenameRequestC2S("New name"));
Send a server-to-client packet to a specific player:
RosettaNetwork.sendToPlayer(new RenameResultS2C(true), serverPlayer);
Persistent data attachments
Rosetta attachments provide persistent values on levels, entities, players, chunks, and block entities. Fabric and NeoForge use their native attachment facilities. On Forge 1.20.1, Rosetta stores the same codec-backed values as ordinary NBT through Forge’s standard save/load hooks.
Define an attachment value
package com.example.data;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
public record VisitData(int visits, String lastDestination) {
public static final Codec<VisitData> CODEC = RecordCodecBuilder.create(instance -> instance.group(
Codec.INT.fieldOf("visits").forGetter(VisitData::visits),
Codec.STRING.fieldOf("last_destination").forGetter(VisitData::lastDestination)
).apply(instance, VisitData::new));
public static VisitData empty() {
return new VisitData(0, "");
}
}
Register an attachment
import net.rasanovum.rosetta.attachment.LevelAttachmentKey;
import net.rasanovum.rosetta.attachment.RosettaAttachments;
public static final LevelAttachmentKey<VisitData> VISITS =
RosettaAttachments.level("example_mod").persistent(
"visits",
VisitData::empty,
VisitData.CODEC
);
Available holder builders are:
RosettaAttachments.level("example_mod");
RosettaAttachments.entity("example_mod");
RosettaAttachments.player("example_mod");
RosettaAttachments.chunk("example_mod");
RosettaAttachments.blockEntity("example_mod");
Chunk attachments use ChunkAccess, so the same key works with proto-chunks and fully generated chunks.
Player attachments copy their value when a player is recreated after death by default. Disable that behavior when declaring the key:
PlayerAttachmentKey<VisitData> TEMPORARY =
RosettaAttachments.player("example_mod").persistent(
"temporary_visits",
false,
VisitData::empty,
VisitData.CODEC
);
Read and modify attachments
find returns an empty Optional when no value has been attached:
Optional<VisitData> existing = VISITS.find(serverLevel);
getOrCreate installs the declared default when no value exists:
VisitData current = VISITS.getOrCreate(serverLevel);
Prefer immutable attachment values and replace them through set:
VisitData current = VISITS.getOrCreate(serverLevel);
VISITS.set(serverLevel, new VisitData(current.visits() + 1, "spawn"));
Remove a value and restore the absent state with:
VISITS.remove(serverLevel);
Mutable values are supported, but the loader cannot observe an in-place change. Call markDirty after mutation so the value is persisted:
MutableGraph graph = GRAPH.getOrCreate(serverLevel);
graph.addNode(node);
GRAPH.markDirty(serverLevel);
Data migration
Rosetta contains a migration seam that runs before getOrCreate installs a default attachment value. The Data Anchor importer is a work in progress.
Other compatibility helpers
Rosetta also contains compatibility facades for NBT access, registries, worlds, biomes, attributes, entities, GUI rendering, client utilities, gamerules, and loader/platform queries. These helpers centralize ordinary API replacements, while Stonecutter remains appropriate for method signatures, overrides, mixin targets, and other source-shape changes that cannot be hidden behind a method call.
Consult the classes under net.rasanovum.rosetta.nbt, net.rasanovum.rosetta.util, net.rasanovum.rosetta.client, and net.rasanovum.rosetta.loaders for the currently available facade methods.