NeoForge 20.5 for Minecraft 1.20.5
The first beta release of NeoForge for Minecraft 1.20.5, NeoForge 20.5.0-beta is now released! Please try it out, play with it, develop with it, and give us feedback! For players, you can grab the latest installer directly from https://neoforged.net/.
We are not stable yet, so expect breaking changes in the coming weeks. If you update your mod now, you will have to make a few adjustments soon. Nonetheless, we recommend that modders start to port already given the extent of the changes. If you are a contributor, now is a great time to start working on your Pull Requests.
There are a few important breaking changes in NeoForge, but most of the technical changes come from Minecraft 1.20.5 itself.
Java 21
Minecraft 1.20.5 now uses Java 21.
Modders need to update their Gradle scripts to compile and run with Java 21, for example:
- java.toolchain.languageVersion = JavaLanguageVersion.of(17)
+ java.toolchain.languageVersion = JavaLanguageVersion.of(21)
NeoGradle needs an update as well. The current latest version is 7.0.105.
Gradle itself will also need to be on version 8.6 at least.
The neoforge.mods.toml File
Starting from 20.5, the only recognized metadata file for mods is neoforge.mods.toml,
still in the META-INF folder.
All mods.toml files must be renamed to neoforge.mods.toml to be recognized by NeoForge.
This change was made to make identifying NeoForge mods easier,
considering that mods.toml is also in use by MinecraftForge.
Remember to replace mods.toml by neoforge.mods.toml in your Gradle scripts too,
especially in the processResources block.
As of this blog post, jar files with no neoforge.mods.toml file will be skipped silently by NeoForge.
We are actively working on a better error message, and will have it ready before the stable release.
Tag Convention Updates
Tags that are common across mods should now use the c namespace, standing for common or convention.
Groups of related tags are encouraged to use a hierarchical structure,
for example c:ores for all ores and c:ores/tin for all tin ores.
Modders can access definitions for these tags through the Tags class.
Datapack authors and multi-platform modders, please note that this new tag format has also been adopted by the Fabric mod loader, and should be safe to target by everyone in the modded community.
Item Data Components
ItemStacks do not have a CompoundTag anymore.
Instead, a stack can now have any number of data components.
Each data component is a plain Java object, identified by a DataComponentType.
Modders should generally create and register their own DataComponentTypes to store data on item stacks.
The vanilla component types are available in the DataComponentTypes class.
Warning
Stack copies are shallow, which means that the components themselves are shared between stacks.
Components are compared using equals for stack comparisons,
and hashCode can also be called when hashing stacks.
Therefore, data components must be immutable, and provide a correct implementation of equals and hashCode.
NeoForge adds a sanity check to make sure that components override these methods.
For example, assuming that we want to store a single integer on an ItemStack, the code would have to change as follows:
+ DataComponentType<Integer> ENERGY = ...;
- int energy = stack.getOrCreateTag().getInt("energy");
+ int energy = stack.getOrDefault(ENERGY, 0);
- stack.getOrCreateTag().setInt("energy", 10);
+ stack.set(ENERGY, 10);
For reference, have a look at the following code:
- All of
DataComponentTypeandDataComponentType.Builder. DataComponentHolderwhich is implemented by item stacks.ItemStack.set(..., ...)andItemStack.remove(...)to change the components on a stack.ItemStack.update(...)for convenience when updating the value of a component.
Items can provide a default set of components.
- See
Item.Properties.component(...)to add default components to items.
Comparison with Data Attachments
Stack data attachments were removed since they are superseded by this new system. While attachments were mutable and unique to each stack, data components are shared between stacks and must therefore be immutable.
For now, data attachments are still fully functional on other types of objects (block entities, chunks, entities, and levels).
Networking Updates
This update introduces StreamCodecs, and comes with more changes to custom payloads.
Stream Codecs
Stream codecs are used to abstract over reading and writing data to network buffers, similarly to how codecs are used to abstract over reading and writing data to JSON or NBT but with a much simpler implementation. Stream codecs have two generic types: the buffer type and the data type. Common buffer types are:
ByteBuffor the most generic stream codecs.FriendlyByteBuffor codecs that use the convenience methods inFriendlyByteBuf, or that rely on other codecs usingFriendlyByteBuf.RegistryFriendlyByteBuffor codecs that access registry information such as raw integer IDs.
For example, a stream codec that reads an integer will have the type StreamCodec<ByteBuf, Integer>
whereas a stream codec that reads an item stack will have the type StreamCodec<RegistryFriendlyByteBuf, ItemStack>.
Stream codecs are designed to be composed together using combinators. Here are a few pointers to get you started:
ByteBufCodecscontains many useful stream codecs, such asByteBufCodecs.INTand other primitive types.StreamCodec.composite(...)is used to combine multiple stream codecs, for example for the fields of a class.- List stream codecs are created using
streamCodec.apply(ByteBufCodecs.list()). Similar methods exist for other collections. - NeoForge provides additional helpers in its
NeoForgeStreamCodecsclass.
Custom Payload Changes
CustomPacketPayloads are now identified by a CustomPacketPayload.Type wrapper around a ResourceLocation.
Additionally, the serialization and deserialization is handled by a StreamCodec.
For the first generic parameter of the stream codec, please note that:
RegistryFriendlyByteBufis only usable in play packets.FriendlyByteBufcan be used for any packet.ByteBufcan also be used for any packet in case the convenienceFriendlyByteBufmethods are not needed.
The packet type and stream codec must be registered using the RegisterPayloadHandlersEvent event.
Network API Rework
Based on feedback from the previous iteration, we have also reorganized the network API to be more consistent and easier to use. Here is a brief summary of the changes:
- Payload handlers run on the main thread by default. This can be changed using
registrar.executesOn(HandlerThread.NETWORK). IPayloadContexthas been flattened into a single interface, and received a few new methods as well.- The registration methods in
PayloadRegistrarhave been renamed. RegisterPayloadHandlerEvent->RegisterPayloadHandlersEvent: renamed.OnGameConfigurationEvent->RegisterConfigurationTasksEvent: renamed.IPayloadRegistrar->PayloadRegistrar: replaced by direct class reference.isConnected->hasChannel: renamed.
We have also simplified the PacketDistributor system used to send packets.
Refer to the PacketDistributor class for more details.
Updated Example
To help you get started, here is an example of a custom play payload that contains two item stacks.
public record TwoStacksPayload(ItemStack first, ItemStack second) implements CustomPacketPayload {
// We have a type that wraps the resource location.
public static final Type<TwoStacksPayload> TYPE = new Type<>(new ResourceLocation("mymod", "two_stacks"));
// And we have a stream codec, here using RFBB (RegistryFriendlyByteBuf) because item stacks require it.
public static final StreamCodec<RegistryFriendlyByteBuf, TwoStacksPayload> STREAM_CODEC = StreamCodec.composite(
ItemStack.OPTIONAL_STREAM_CODEC,
TwoStacksPayload::first,
ItemStack.OPTIONAL_STREAM_CODEC,
TwoStacksPayload::second,
TwoStacksPayload::new);
@Override
public Type<TwoStacksPayload> type() {
return TYPE;
}
}
And the registration code will look as follows:
public static void onPayloadRegister(RegisterPayloadHandlersEvent event) { // note: Handlers is plural
PayloadRegistrar registrar = event.registrar("version");
// could also use playToClient or playToServer for unidirectional packets
registrar.playBidirectional(TwoStacksPayload.TYPE, TwoStacksPayload.STREAM_CODEC, <packet handler>);
// and so on...
}
The packet can be sent using the PacketDistributor:
// On the client side, send a packet to the server:
PacketDistributor.sendToServer(new TwoStacksPayload(..., ...));
// On the server side, send a packet to a specific client:
PacketDistributor.sendToClient(player, new TwoStacksPayload(..., ...));
// And so on...
DFU Update
The DFU (DataFixerUpper) library was updated from major version 6 to 7. This update fixes many longstanding annoyances with DFU, at the cost of a few breaking changes.
Here are a few highlights:
- Many methods in
ExtraCodecs(a Minecraft class that contains additionalCodecs) were removed, as they now have an equivalent in DFU itself.ExtraCodecs.strictOptionalField(codec, ...)is replaced bycodec.optionalFieldOf(...), which is now strict by default.ExtraCodecs.validate(codec, ...)is replaced bycodec.validate(...).ExtraCodecs.lazyInitializedCodec(() -> ...)is replaced byCodec.lazilyInitialized(() -> ...).- And so on…
- Dispatch codecs (typically, codecs whose serialization depends on a
typefield) now require aMapCodecfor the dispatched type. For example, recipe serializers now return aMapCodecinstead of aCodec.- When using
RecordCodecBuilder, useRecordCodecBuilder.mapCodecinstead of.createto produce aMapCodec. - Many combinators such as
.xmapwill work withMapCodecs too. - Turning a
MapCodecto aCodecis done usingmapCodec.codec().
- When using
Util.getOrThrow(dataResult, ...)is replaced bydataResult.getOrThrow(...).
Smaller Changes
Here are a few smaller changes that will impact many mods.
Event System
@Mod.EventBusSubscriber->@EventBusSubscriber: moved to top-level.Mod.EventBusSubscriber.Bus.FORGE->EventBusSubscriber.Bus.GAME: renamed constant. The game bus itself is still atNeoForge.EVENT_BUS.
Game Object Serialization
- Many serialization methods now take an additional
HolderLookup.Provider registriesparameter.- It can be used to serialize a codec to NBT:
registries.buildSerializationContext(NbtOps.INSTANCE). - The registry context can be retrieved from a
RegistryFriendlyByteBuf, or used to create one.
- It can be used to serialize a codec to NBT:
BlockEntity.save(CompoundTag tag)->BlockEntity.saveAdditional(CompoundTag tag, HolderLookup.Provider registries).- Use
ItemStack.save(registries)andItemStack.parseOptional(registries, compoundTag)to save and load item stacks to/from NBT.- WARNING:
ItemStack.save(registries, Tag)will not modify the passed tag. Make sure that you always use the returned tag, or preferable call the overload that only takes the registries.
- WARNING:
GUI Layers
RegisterGuiOverlaysEvent->RegisterGuiLayersEvent.VanillaGuiOverlay->VanillaGuiLayers.
Porting Primer
Many more changes are collected on the following Mod Migration Primer. Thanks to @ChampionAsh5357 for this write-up.
1.20.1 And 1.20.4 Plans
We will now focus on 1.20.5 going forward, but we do not abandon support for 1.20.1 and 1.20.4. As usual, any Pull Request targeting older versions will first have to be accepted and merged into the 1.20.5 branch.