The big Registry system update is here

Introduction

A big update to the registry system was just released in NeoForge version 20.2.59-beta! Our primary goal is to simplify the code as much as possible, and align it with the registry system in vanilla Minecraft.

This blog post will go through the most important changes that were made, to act as a migration guide for modders.

This rework is the first of the three big reworks to land after our initial 20.2 release. The other two systems that will receive an overhaul in the coming weeks are capabilities and networking. Once these also land we will be aiming for a 20.2 stable release.

Using the registries

Previously, the NeoForge registry system was completely separate from vanilla’s. Now, we use the existing registry system in vanilla, with a few additions that are relevant to mod support.

This means that IForgeRegistry is replaced by the vanilla Registry. NeoForge adds a few methods to the vanilla Registry type via IRegistryExtension. Here is an overview of the changed methods:

IForgeRegistry Registry
getValue(ResourceLocation) get(ResourceLocation)
getKeys() keySet()
getValues().stream() stream()
getHolder(T)Optional<Holder> wrapAsHolder(T)Holder?
tags() Use getTag(TagKey) and the HolderSet API

The registries that are defined by Minecraft itself should now be accessed via BuiltInRegistries:

- ForgeRegistries.ITEMS.getValue(new ResourceLocation("minecraft:diamond"));
+ BuiltInRegistries.ITEM.get(new ResourceLocation("minecraft:diamond"));

The registries that are defined by NeoForge are accessible via NeoForgeRegistries. They are not wrapped in a Supplier anymore, and can be used directly:

- ForgeRegistries.FLUID_TYPES.get().getValue(new ResourceLocation("mymod:fancyfluid"));
+ NeoForgeRegistries.FLUID_TYPES.get(new ResourceLocation("mymod:fancyfluid"));

Registration

Registration still happens in the RegisterEvent, and we still recommend that modders use DeferredRegister to abstract over the events.

RegistryObject was replaced by DeferredHolder, which implements vanilla’s Holder interface. When registering objects to a deferred register, we recommended two options:

  • If you don’t need any of the Holder functions, you can use a Supplier for the type of your field.
  • Otherwise, use DeferredHolder with the two generic parameters (one for the registry and one for your object type).

Here are is an example:

  private static final DeferredRegister<Enchantment> ENCHANTMENTS = DeferredRegister.create(Registries.ENCHANTMENT, "mymod");

- public static final RegistryObject<Enchantment> MAGIC =
-         ENCHANTMENTS.register("magic", () -> new MagicEnchantment(/* create enchantment */));
 // Supplier only:
+ public static final Supplier<MagicEnchantment> MAGIC =
+         ENCHANTMENTS.register("magic", () -> new MagicEnchantment(/* create enchantment */));
  // Access to both Holder and the exact object type:
+ public static final DeferredHolder<Enchantment, MagicEnchantment> MAGIC =
+         ENCHANTMENTS.register("magic", () -> new MagicEnchantment(/* create enchantment */));

NeoForge also provides DeferredHolder and DeferredRegister specializations for items and blocks that implement ItemLike. For example:

// Make sure you use the special DeferredRegister.Blocks and DeferredRegister.Items types,
// NOT DeferredRegister<Block> or DeferredRegister<Item>!
private static final DeferredRegister.Blocks BLOCKS = DeferredRegister.createBlocks("mymod");
private static final DeferredRegister.Items ITEMS = DeferredRegister.createItems("mymod");

// If you are registering blocks or items directly, use a normal `register` call:
public static final DeferredBlock<MyBlock> MY_BLOCK = BLOCKS.register("my_block", () -> new MyBlock(/* create block */));
public static final DeferredItem<MyItem> MY_ITEM = ITEMS.register("my_item", () -> new MyItem(/* create item */));

// There are also a few extra helper functions, for example to register "simple" blocks.
// `registerSimpleBlock` to directly register a `new Block` from some block properties:
public static final DeferredBlock<Block> MY_SIMPLE_BLOCK =
        BLOCKS.registerSimpleBlock("simple_block", BlockBehaviour.Properties.of().mapColor(MapColor.STONE));
// `registerSimpleItem` to directly register a `new Item` from some item properties:
public static final DeferredItem<Item> MY_SIMPLE_ITEM =
        ITEMS.registerSimpleItem("simple_item", new Item.Properties().stacksTo(1));
// `registerSimpleBlockItem` to directly register a `new BlockItem` for a block:
public static final DeferredItem<BlockItem> MY_BLOCK_ITEM =
        ITEMS.registerSimpleBlockItem(MY_BLOCK);

As usual, don’t forget to pass your mod bus to your DeferredRegisters:

@Mod("mymod")
public class MyMod {
    // In case you missed it, mod constructors can now receive a number of optional arguments,
    // including the mod's event bus. Unrelated to registries, but still pretty cool. ;)
    public MyMod(IEventBus modEventBus) {
        ENCHANTMENTS.register(modEventBus);
        BLOCKS.register(modEventBus);
        ITEMS.register(modEventBus);
    }
}

Custom registries

Custom registries are created with a RegistryBuilder, and must be registered to the NewRegistryEvent. They can now be held in static fields, just like the registries in BuiltInRegistries or NeoForgeRegistries.

Here is a registration example, using the helper methods provided by DeferredRegister:

// Create a registry key - we don't have a registry yet so give the key to DeferredRegister.
public static final ResourceKey<Registry<Custom>> CUSTOM_REGISTRY_KEY =
        ResourceKey.createRegistryKey(new ResourceLocation("mymod:custom"));
// Create the DeferredRegister with our registry key.
private static final DeferredRegister<Custom> CUSTOMS =
        DeferredRegister.create(CUSTOM_REGISTRY_KEY, "mymod");

// We can register objects as usual...
public static final Holder<Custom> CUSTOM_OBJECT =
        CUSTOMS.register("custom_object", () -> new Custom());

// And here is how to create the registry!
public static final Registry<Custom> CUSTOM_REGISTRY =
        CUSTOMS.makeRegistry(builder -> /* use builder to configure registry if needed */);

// Remember to register CUSTOMS in the mod constructor!

Another way is to directly create the registry with a RegistryBuilder, and manually register it:

// We still need a registry key.
public static final ResourceKey<Registry<Custom>> CUSTOM_REGISTRY_KEY =
        ResourceKey.createRegistryKey(new ResourceLocation("mymod:custom"));
// Create the registry directly...
public static final Registry<Custom> CUSTOM_REGISTRY = new RegistryBuilder<>(CUSTOM_REGISTRY_KEY)
    // configure the builder if you want, for example with .sync(true)
    // then build the registry
    .build();

// Remember to tell NeoForge about your registry! For example:
modEventBus.addListener(NewRegistryEvent.class, event -> event.register(CUSTOM_REGISTRY));

That’s it!

As usual, ask on our Discord server in the #modder-support-1.20 channel if you have any question.