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
ItemStack
s 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 DataComponentType
s 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
DataComponentType
andDataComponentType.Builder
. DataComponentHolder
which 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.
Item
s 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 StreamCodec
s, 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:
ByteBuf
for the most generic stream codecs.FriendlyByteBuf
for codecs that use the convenience methods inFriendlyByteBuf
, or that rely on other codecs usingFriendlyByteBuf
.RegistryFriendlyByteBuf
for 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:
ByteBufCodecs
contains many useful stream codecs, such asByteBufCodecs.INT
and 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
NeoForgeStreamCodecs
class.
Custom Payload Changes
CustomPacketPayload
s 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:
RegistryFriendlyByteBuf
is only usable in play packets.FriendlyByteBuf
can be used for any packet.ByteBuf
can also be used for any packet in case the convenienceFriendlyByteBuf
methods 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)
. IPayloadContext
has been flattened into a single interface, and received a few new methods as well.- The registration methods in
PayloadRegistrar
have 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 additionalCodec
s) 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
type
field) now require aMapCodec
for the dispatched type. For example, recipe serializers now return aMapCodec
instead of aCodec
.- When using
RecordCodecBuilder
, useRecordCodecBuilder.mapCodec
instead of.create
to produce aMapCodec
. - Many combinators such as
.xmap
will work withMapCodec
s too. - Turning a
MapCodec
to aCodec
is 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 registries
parameter.- 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.