Views
Actions
Difference between revisions of "Registration"
(Registry creation language port) |
(Update to 1.17) |
||
Line 5: | Line 5: | ||
Most objects that are known within the game are handled by a <code>Registry</code>. Each registry uniquely defines each object through a "registry name" via a [[Using Resources#ResourceLocation|ResourceLocation]]. This "registry name" can be accessed with its respective getter and setter: <code>#getRegistryName</code> and <code>#setRegistryName</code>. You can only set the "registry name" of a given object once; otherwise, an exception will be thrown. | Most objects that are known within the game are handled by a <code>Registry</code>. Each registry uniquely defines each object through a "registry name" via a [[Using Resources#ResourceLocation|ResourceLocation]]. This "registry name" can be accessed with its respective getter and setter: <code>#getRegistryName</code> and <code>#setRegistryName</code>. You can only set the "registry name" of a given object once; otherwise, an exception will be thrown. | ||
− | {{Tip|In a global context, each object is universally unique through its <code> | + | {{Tip|In a global context, each object is universally unique through its <code>ResourceKey</code>: a concatenation of its registry's id and the object's registry name.}} |
Due to the inconsistent ordering and registration process vanilla uses, Forge wraps most vanilla registries using <code>IForgeRegistry</code>. This guarantees that the loading order for these wrapped registries will be <code>Block</code>, <code>Item</code>, and then the rest of the wrapped registries in alphabetical order. All registries supported by Forge can be found within the <code>ForgeRegistries</code> class. Since all registry names are unique to a specific registry, different registry objects within different registries can have the same name (e.g. a <code>Block</code> and an <code>Item</code> each hold a registry object named <code>examplemod:object</code>. | Due to the inconsistent ordering and registration process vanilla uses, Forge wraps most vanilla registries using <code>IForgeRegistry</code>. This guarantees that the loading order for these wrapped registries will be <code>Block</code>, <code>Item</code>, and then the rest of the wrapped registries in alphabetical order. All registries supported by Forge can be found within the <code>ForgeRegistries</code> class. Since all registry names are unique to a specific registry, different registry objects within different registries can have the same name (e.g. a <code>Block</code> and an <code>Item</code> each hold a registry object named <code>examplemod:object</code>. | ||
Line 26: | Line 26: | ||
|java=private static final DeferredRegister<Block> BLOCKS = DeferredRegister.create(ForgeRegistries.BLOCKS, MODID); | |java=private static final DeferredRegister<Block> BLOCKS = DeferredRegister.create(ForgeRegistries.BLOCKS, MODID); | ||
− | public static final RegistryObject<Block> EXAMPLE_BLOCK = BLOCKS.register("example_block", () -> new Block( | + | public static final RegistryObject<Block> EXAMPLE_BLOCK = BLOCKS.register("example_block", () -> new Block(BlockBehaviour.Properties.of(Material.STONE))); |
public ExampleMod() { | public ExampleMod() { | ||
Line 33: | Line 33: | ||
|kotlin=private val BLOCKS = DeferredRegister.create(ForgeRegistries.BLOCKS, MODID) | |kotlin=private val BLOCKS = DeferredRegister.create(ForgeRegistries.BLOCKS, MODID) | ||
− | val EXAMPLE_BLOCK: RegistryObject<Block> = BLOCKS.register("example_block") { Block( | + | val EXAMPLE_BLOCK: RegistryObject<Block> = BLOCKS.register("example_block") { Block(BlockBehaviour.Properties.of(Material.ROCK)) } |
internal class ExampleMod { | internal class ExampleMod { | ||
Line 43: | Line 43: | ||
private final val BLOCKS = DeferredRegister.create(ForgeRegistries.BLOCKS, MODID) | private final val BLOCKS = DeferredRegister.create(ForgeRegistries.BLOCKS, MODID) | ||
− | final val EXAMPLE_BLOCK = registerBlock("example_block", () => new Block( | + | final val EXAMPLE_BLOCK = registerBlock("example_block", () => new Block(BlockBehaviour.Properties.of(Material.ROCK))) |
} | } | ||
class ExampleMod { | class ExampleMod { | ||
Line 76: | Line 76: | ||
|}} | |}} | ||
− | {{Tip/Important|Since all objects registered must be singleton, some classes cannot by themselves be registered. Instead, <code>*Type</code> classes are registered and used in the formers' constructors to wrap the flyweight objects. For example, a [[ | + | {{Tip/Important|Since all objects registered must be singleton, some classes cannot by themselves be registered. Instead, <code>*Type</code> classes are registered and used in the formers' constructors to wrap the flyweight objects. For example, a [[Basics_of_Block_Entities|<code>BlockEntity</code>]] is wrapped via <code>BlockEntityType</code>, and <code>Entity</code> is wrapped via <code>EntityType</code>. These <code>*Type</code> classes hold factories that simply create the containing type on demand. |
− | These factory holders are created through the use of their <code>*Type$Builder</code> classes. An example: (<code>REGISTER</code> here refers to a <code>DeferredRegister< | + | These factory holders are created through the use of their <code>*Type$Builder</code> classes. An example: (<code>REGISTER</code> here refers to a <code>DeferredRegister<BlockEntityType<?>></code>) |
{{Template:Tabs/Code_Snippets | {{Template:Tabs/Code_Snippets | ||
− | |java=public static final RegistryObject< | + | |java=public static final RegistryObject<BlockEntityType<ExampleBlockEntity>> EXAMPLE_BLOCK_ENTITY = REGISTER.register( |
− | " | + | "example_block_entity", () -> BlockEntityType.Builder.of(ExampleBlockEntity::new, EXAMPLE_BLOCK.get()).build(null) |
); | ); | ||
− | |kotlin=val | + | |kotlin=val EXAMPLE_BLOCK_ENTITY: RegistryObject<BlockEntityType<ExampleBlockEntity>> = REGISTER.register("example_block_entity") { BlockEntityType.Builder.of(::ExampleBlockEntity, EXAMPLE_BLOCK.get()).build(null)) } |
− | |scala=final val | + | |scala=final val EXAMPLE_BLOCK_ENTITY = REGISTER.register("example_block_entity", () => BlockEntityType.Builder.of(() => new ExampleBlockEntity(), GeneralRegistrar.EXAMPLE_BLOCK.get).build(null)) |
|}} | |}} | ||
}} | }} | ||
Line 95: | Line 95: | ||
These registries include: | These registries include: | ||
* Custom Stats (a <code>ResourceLocation</code> registry) | * Custom Stats (a <code>ResourceLocation</code> registry) | ||
− | * <code> | + | * <code>RuleTestType</code> |
− | * <code> | + | * <code>PosRuleTestType</code> |
− | * <code> | + | * <code>RecipeType</code> |
+ | * <code>GameEvent</code> | ||
+ | * <code>PositionSourceType</code> | ||
* <code>VillagerType</code> | * <code>VillagerType</code> | ||
* <code>LootPoolEntryType</code> | * <code>LootPoolEntryType</code> | ||
− | * <code> | + | * <code>LootItemFunctionType</code> |
− | * <code> | + | * <code>LootItemConditionType</code> |
− | * <code> | + | * <code>LootNumberProviderType</code> |
+ | * <code>LootNbtProviderType</code> | ||
+ | * <code>LootScoreProviderType</code> | ||
+ | * <code>FloatProviderType</code> | ||
+ | * <code>IntProviderType</code> | ||
+ | * <code>HeightProviderType</code> | ||
+ | * <code>StructurePieceType</code> | ||
* <code>TrunkPlacerType</code> | * <code>TrunkPlacerType</code> | ||
* <code>FeatureSizeType</code> | * <code>FeatureSizeType</code> | ||
− | * <code> | + | * A <code>Codec</code> of <code>BiomeSource</code> |
− | * <code> | + | * A <code>Codec</code> of <code>ChunkGenerator</code> |
− | * <code> | + | * <code>StructureProcessorType</code> |
− | * <code> | + | * <code>StructurePoolElementType</code> |
− | * All registries within <code> | + | * All registries within <code>BuiltinRegistries</code> excluding <code>Biome</code> |
To register objects to any one of these registries, you will need to call <code>Registry::register(Registry, ResourceLocation, T)</code> where the type parameter <code>T</code> is the object instance being registered. The method can then be called and registered during the highest priority of <code>FMLCommonSetupEvent</code>. We can also utilize a <code>Lazy</code> above to store the result on first access and use within our code. | To register objects to any one of these registries, you will need to call <code>Registry::register(Registry, ResourceLocation, T)</code> where the type parameter <code>T</code> is the object instance being registered. The method can then be called and registered during the highest priority of <code>FMLCommonSetupEvent</code>. We can also utilize a <code>Lazy</code> above to store the result on first access and use within our code. | ||
Line 118: | Line 126: | ||
|java=public static final Lazy<ConfiguredFeature<?, ?>> EXAMPLE_CONFIGURED_FEATURE = Lazy.of(() -> | |java=public static final Lazy<ConfiguredFeature<?, ?>> EXAMPLE_CONFIGURED_FEATURE = Lazy.of(() -> | ||
register("example_configured_feature", | register("example_configured_feature", | ||
− | Feature.NO_OP. | + | Feature.NO_OP.configured(NoneFeatureConfiguration.INSTANCE) |
− | . | + | .decorated(FeatureDecorator.NOPE.configured(NoneDecoratorConfiguration.INSTANCE)) |
) | ) | ||
); | ); | ||
Line 129: | Line 137: | ||
private static <T extends ConfiguredFeature<?, ?>> T register(String name, T value) { | private static <T extends ConfiguredFeature<?, ?>> T register(String name, T value) { | ||
− | return Registry.register( | + | return Registry.register(BuiltinRegistries.CONFIGURED_FEATURE, new ResourceLocation(MODID, name), value); |
} | } | ||
|kotlin=val EXAMPLE_CONFIGURED_FEATURE: ConfiguredFeature<*, *> by lazy { | |kotlin=val EXAMPLE_CONFIGURED_FEATURE: ConfiguredFeature<*, *> by lazy { | ||
register("example_configured_feature", | register("example_configured_feature", | ||
− | Feature.NO_OP. | + | Feature.NO_OP.configured(NoneFeatureConfiguration.INSTANCE) |
− | . | + | .decorated(FeatureDecorator.NOPE.configured(NoneDecoratorConfiguration.INSTANCE))) |
} | } | ||
Line 146: | Line 154: | ||
private fun <T: ConfiguredFeature<*, *>> register(name: String, value: T): T = | private fun <T: ConfiguredFeature<*, *>> register(name: String, value: T): T = | ||
− | Registry.register( | + | Registry.register(BuiltinRegistries.CONFIGURED_FEATURE, ResourceLocation(MODID, name), value) |
|scala=class Events { | |scala=class Events { | ||
@SubscribeEvent(priority = EventPriority.HIGHEST) | @SubscribeEvent(priority = EventPriority.HIGHEST) | ||
Line 155: | Line 163: | ||
object Events { | object Events { | ||
final lazy val EXAMPLE_CONFIGURED_FEATURE = register("example_configured_feature", | final lazy val EXAMPLE_CONFIGURED_FEATURE = register("example_configured_feature", | ||
− | Feature.NO_OP. | + | Feature.NO_OP.configured(NoneFeatureConfiguration.INSTANCE) |
− | . | + | .decorated(FeatureDecorator.NOPE.configured(NoneDecoratorConfiguration.INSTANCE))) |
private def register[T <: ConfiguredFeature[_, _]](name: String, value: T): T = | private def register[T <: ConfiguredFeature[_, _]](name: String, value: T): T = | ||
− | Registry.register( | + | Registry.register(BuiltinRegistries.CONFIGURED_FEATURE, new ResourceLocation(MODID, name), value) |
} | } | ||
|}} | |}} | ||
Line 165: | Line 173: | ||
{{Tip/Warning|Vanilla registry methods are not thread-safe, so they must be wrapped within the synchronous queue provided within the common setup event via <code>#enqueueWork</code>.}} | {{Tip/Warning|Vanilla registry methods are not thread-safe, so they must be wrapped within the synchronous queue provided within the common setup event via <code>#enqueueWork</code>.}} | ||
− | Besides the registries within <code> | + | Besides the registries within <code>BuiltinRegistries</code>, all other '''non-forge''' wrapped registries can be statically initialized like so: |
{{Template:Tabs/Code_Snippets | {{Template:Tabs/Code_Snippets | ||
− | |java=public static final | + | |java=public static final RecipeType<ExampleRecipe> EXAMPLE_RECIPE = RecipeType.register(MODID + ":example_recipe"); |
− | |kotlin=val EXAMPLE_RECIPE: | + | |kotlin=val EXAMPLE_RECIPE: RecipeType<ExampleRecipe> = RecipeType.register("${MODID}:example_recipe") |
− | |scala=final val EXAMPLE_RECIPE = | + | |scala=final val EXAMPLE_RECIPE = RecipeType.register(s"${MODID}:example_recipe") |
|}} | |}} | ||
Line 177: | Line 185: | ||
=== Data Driven Entries === | === Data Driven Entries === | ||
− | Registries are considered to be data driven if they are located within <code> | + | Registries are considered to be data driven if they are located within <code>RegistryAccess</code> with the exception of <code>LevelStem</code> and <code>Level</code>. |
The following registries are data driven: | The following registries are data driven: | ||
* <code>ConfiguredSurfaceBuilder</code> | * <code>ConfiguredSurfaceBuilder</code> | ||
− | * <code> | + | * <code>ConfiguredWorldCarver</code> |
* <code>ConfiguredFeature</code> | * <code>ConfiguredFeature</code> | ||
− | * <code> | + | * <code>ConfiguredStructureFeature</code> |
* <code>StructureProcessorList</code> | * <code>StructureProcessorList</code> | ||
− | * <code> | + | * <code>StructureTemplatePool</code> |
* <code>Biome</code> | * <code>Biome</code> | ||
− | * <code> | + | * <code>NoiseGeneratorSettings</code> |
* <code>DimensionType</code> | * <code>DimensionType</code> | ||
− | * <code> | + | * <code>LevelStem</code> |
+ | * <code>Level</code> | ||
These registry objects only need to be registered within code if they are to be used within a pre-existing registry object (e.g. a <code>ConfiguredFeature</code> for ore generation within an overworld <code>Biome</code>). Otherwise, their instance can be purely registered using a JSON file. | These registry objects only need to be registered within code if they are to be used within a pre-existing registry object (e.g. a <code>ConfiguredFeature</code> for ore generation within an overworld <code>Biome</code>). Otherwise, their instance can be purely registered using a JSON file. | ||
Line 204: | Line 213: | ||
<code>RegistryObject</code>s can be used to retrieve references to registered objects once they become available. Their references are updated along with all <code>@ObjectHolder</code> annotations after the associated <code>RegistryEvent$Register</code> has been dispatched and frozen. | <code>RegistryObject</code>s can be used to retrieve references to registered objects once they become available. Their references are updated along with all <code>@ObjectHolder</code> annotations after the associated <code>RegistryEvent$Register</code> has been dispatched and frozen. | ||
− | A <code>RegistryObject</code> can be retrieved as a result of using <code>DeferredRegister</code> or calling the static constructor <code>RegistryObject::of</code>. Each static constructor takes in the "registry name" of the object being referenced and either a <code>IForgeRegistry</code> or, if custom registries are used, a supplier of the object class implementing <code>IForgeRegistryEntry</code>. The <code>RegistryObject</code> can be stored within some field and | + | A <code>RegistryObject</code> can be retrieved as a result of using <code>DeferredRegister</code> or calling the static constructor <code>RegistryObject::of</code>. Each static constructor takes in the "registry name" of the object being referenced and either a <code>IForgeRegistry</code> or, if custom registries are used, a supplier of the object class implementing <code>IForgeRegistryEntry</code>. The <code>RegistryObject</code> can be stored within some field and retrieve the registered object using <code>#get</code>. |
An example using <code>RegistryObject</code>: | An example using <code>RegistryObject</code>: | ||
Line 259: | Line 268: | ||
@ObjectHolder("minecraft") // Inheritable resource namespace: "minecraft" | @ObjectHolder("minecraft") // Inheritable resource namespace: "minecraft" | ||
class AnnotatedHolder { | class AnnotatedHolder { | ||
− | public static final Block diamond_block = null; // No annotation. [public static final] is required. | + | public static final Block diamond_block = null; // No annotation. [public static final] is required. |
− | + | // Block has a corresponding registry: [Block] | |
− | + | // Name path is the name of the field: "diamond_block" | |
− | + | // Namespace is not explicitly defined. | |
− | + | // So, namespace is inherited from class annotation: "minecraft" | |
− | + | // To inject: "minecraft:diamond_block" from the [Block] registry | |
@ObjectHolder("ambient.cave") | @ObjectHolder("ambient.cave") | ||
− | public static SoundEvent ambient_sound = null; | + | public static SoundEvent ambient_sound = null; // Annotation present. [public static] is required. |
− | + | // SoundEvent has a corresponding registry: [SoundEvent] | |
− | + | // Name path is the value of the annotation: "ambient.cave" | |
− | + | // Namespace is not explicitly defined. | |
− | + | // So, namespace is inherited from class annotation: "minecraft" | |
− | + | // To inject: "minecraft:ambient.cave" from the [SoundEvent] registry | |
// Assume for the next entry that [ManaType] is a valid registry. | // Assume for the next entry that [ManaType] is a valid registry. | ||
@ObjectHolder("neomagicae:coffeinum") | @ObjectHolder("neomagicae:coffeinum") | ||
− | public static final ManaType coffeinum = null; | + | public static final ManaType coffeinum = null; // Annotation present. [public static] is required. [final] is optional. |
− | + | // ManaType has a corresponding registry: [ManaType] (custom registry) | |
− | + | // Resource location is explicitly defined: "neomagicae:coffeinum" | |
− | + | // To inject: "neomagicae:coffeinum" from the [ManaType] registry | |
− | public static final Item ENDER_PEARL = null; | + | public static final Item ENDER_PEARL = null; // No annotation. [public static final] is required. |
− | + | // Item has a corresponding registry: [Item]. | |
− | + | // Name path is the name of the field: "ENDER_PEARL" -> "ender_pearl" | |
− | + | // !! ^ Field name is valid, because they are | |
− | + | // converted to lowercase automatically. | |
− | + | // Namespace is not explicitly defined. | |
− | + | // So, namespace is inherited from class annotation: "minecraft" | |
− | + | // To inject: "minecraft:ender_pearl" from the [Item] registry | |
@ObjectHolder("minecraft:arrow") | @ObjectHolder("minecraft:arrow") | ||
− | public static final ArrowItem arrow = null; | + | public static final ArrowItem arrow = null; // Annotation present. [public static] is required. [final] is optional. |
− | + | // ArrowItem does not have a corresponding registry. | |
− | + | // ArrowItem's supertype of Item has a corresponding registry: [Item] | |
− | + | // Resource location is explicitly defined: "minecraft:arrow" | |
− | + | // To inject: "minecraft:arrow" from the [Item] registry | |
− | public static Block bedrock = null; | + | public static Block bedrock = null; // No annotation, so [public static final] is required. |
− | + | // Therefore, the field is ignored. | |
− | public static final | + | public static final CreativeModeTab group = null; // No annotation. [public static final] is required. |
− | + | // CreativeModeTab does not have a corresponding registry. | |
− | + | // No supertypes of CreativeModeTab has a corresponding registry. | |
− | + | // Therefore, THIS WILL PRODUCE AN EXCEPTION. | |
} | } | ||
class UnannotatedHolder { // Note the lack of an @ObjectHolder annotation on this class. | class UnannotatedHolder { // Note the lack of an @ObjectHolder annotation on this class. | ||
@ObjectHolder("minecraft:flame") | @ObjectHolder("minecraft:flame") | ||
− | public static final Enchantment flame = null; | + | public static final Enchantment flame = null; // Annotation present. [public static] is required. [final] is optional. |
− | + | // Enchantment has corresponding registry: [Enchantment]. | |
− | + | // Resource location is explicitly defined: "minecraft:flame" | |
− | + | // To inject: "minecraft:flame" from the [Enchantment] registry | |
− | public static final Biome ice_flat = null; | + | public static final Biome ice_flat = null; // No annotation on the enclosing class. |
− | + | // Therefore, the field is ignored. | |
@ObjectHolder("minecraft:creeper") | @ObjectHolder("minecraft:creeper") | ||
− | public static Entity creeper = null; | + | public static Entity creeper = null; // Annotation present. [public static] is required. |
− | + | // Entity does not have a corresponding registry. | |
− | + | // No supertypes of Entity has a corresponding registry. | |
− | + | // Therefore, THIS WILL PRODUCE AN EXCEPTION. | |
@ObjectHolder("levitation") | @ObjectHolder("levitation") | ||
− | public static final Potion levitation = null; | + | public static final Potion levitation = null; // Annotation present. [public static] is required. [final] is optional. |
− | + | // Potion has a corresponding registry: [Potion]. | |
− | + | // Name path is the value of the annotation: "levitation" | |
− | + | // Namespace is not explicitly defined. | |
− | + | // No annotation in enclosing class. | |
− | + | // Therefore, THIS WILL PRODUCE AN EXCEPTION. | |
} | } | ||
</syntaxhighlight> | </syntaxhighlight> |
Revision as of 00:58, 30 July 2021
This page is under construction.
This page is incomplete, and needs more work. Feel free to edit and improve this page!
Registration is the process of making an object (such as an item or block) known to the game during runtime. If some objects are not registered, this could cause crashes even before the game is fully loaded or arbitrary behaviors such as bottlenecking mod compatibility for world generation.
Most objects that are known within the game are handled by a Registry
. Each registry uniquely defines each object through a "registry name" via a ResourceLocation. This "registry name" can be accessed with its respective getter and setter: #getRegistryName
and #setRegistryName
. You can only set the "registry name" of a given object once; otherwise, an exception will be thrown.
ResourceKey
: a concatenation of its registry's id and the object's registry name.Due to the inconsistent ordering and registration process vanilla uses, Forge wraps most vanilla registries using IForgeRegistry
. This guarantees that the loading order for these wrapped registries will be Block
, Item
, and then the rest of the wrapped registries in alphabetical order. All registries supported by Forge can be found within the ForgeRegistries
class. Since all registry names are unique to a specific registry, different registry objects within different registries can have the same name (e.g. a Block
and an Item
each hold a registry object named examplemod:object
.
Warning
IllegalArgumentException
is the DataSerializerEntry
registry.Methods for Registering
There are two proper ways to register objects within an associated wrapped Forge registry: the DeferredRegister
class, and the RegistryEvent$Register
lifecycle event.
For objects with no associated Forge registry, you can register the associated entry during the FMLCommonSetupEvent
lifecycle event. In some cases, although not recommended, you may also statically initialize and register these entries.
DeferredRegister
DeferredRegister
is an abstraction layer over the registry event used to register objects. It maintains a map of "registry name" to their associated suppliers and resolves those suppliers during the proper RegistryEvent$Register
event. This method is the currently recommended, and documented, way to handle these objects as it provides convenience and safety for those who want to statically initialize objects while avoiding some issues associated with it.
An example of a mod registering a custom block:
private static final DeferredRegister<Block> BLOCKS = DeferredRegister.create(ForgeRegistries.BLOCKS, MODID); public static final RegistryObject<Block> EXAMPLE_BLOCK = BLOCKS.register("example_block", () -> new Block(BlockBehaviour.Properties.of(Material.STONE))); public ExampleMod() { BLOCKS.register(FMLJavaModLoadingContext.get().getModEventBus()); }
private val BLOCKS = DeferredRegister.create(ForgeRegistries.BLOCKS, MODID) val EXAMPLE_BLOCK: RegistryObject<Block> = BLOCKS.register("example_block") { Block(BlockBehaviour.Properties.of(Material.ROCK)) } internal class ExampleMod { init { BLOCKS.register(FMLJavaModLoadingContext.get().modEventBus) } }
object ExampleMod { private final val BLOCKS = DeferredRegister.create(ForgeRegistries.BLOCKS, MODID) final val EXAMPLE_BLOCK = registerBlock("example_block", () => new Block(BlockBehaviour.Properties.of(Material.ROCK))) } class ExampleMod { BLOCKS.register(FMLJavaModLoadingContext.get.getModEventBus) }
DeferredRegister
to register any object, the name inputted will be automatically prefixed with the mod id passed in, giving the above object a "registry name" of examplemod:example_block
.RegistryEvent$Register
The RegistryEvent
s are another, more slightly flexible way to register objects. These events are fired synchronously after FMLConstructModEvent
and before the configs are loaded.
The event used to register objects is RegistryEvent$Register<T>
, where the type parameter T
is the object type being registered. You can grab the associated registry using #getRegistry
and register the objects within using either #register
(pass in a single object) or #registerAll
(pass in varargs or an array of objects). The latter is useful for minimizing calls to #register
, although it provides no benefit time-complexity wise.
Important
Here is an example: (the event handler is registered on the mod event bus)
@SubscribeEvent public void registerBlocks(RegistryEvent.Register<Block> event) { event.getRegistry().registerAll(new Block(...).setRegistryName(new ResourceLocation(MODID, "example_block1")), new Block(...).setRegistryName(new ResourceLocation(MODID, "example_block2")), ...); }
@JvmStatic @SubscribeEvent private fun registerBlocks(event: RegistryEvent.Register<Block>) = event.registry.registerAll(Block(...).setRegistryName(new ResourceLocation(MODID, "example_block1")), Block(...).setRegistryName(new ResourceLocation(MODID, "example_block2")), ...)
@SubscribeEvent def registerBlocks(event: RegistryEvent.Register[Block]): Unit = event.getRegistry.registerAll(new Block(...).setRegistryName(new ResourceLocation(MODID, "example_block1")), new Block(...).setRegistryName(new ResourceLocation(MODID, "example_block2")), ...)
Important
Since all objects registered must be singleton, some classes cannot by themselves be registered. Instead, *Type
classes are registered and used in the formers' constructors to wrap the flyweight objects. For example, a BlockEntity
is wrapped via BlockEntityType
, and Entity
is wrapped via EntityType
. These *Type
classes hold factories that simply create the containing type on demand.
These factory holders are created through the use of their *Type$Builder
classes. An example: (REGISTER
here refers to a DeferredRegister<BlockEntityType<?>>
)
public static final RegistryObject<BlockEntityType<ExampleBlockEntity>> EXAMPLE_BLOCK_ENTITY = REGISTER.register( "example_block_entity", () -> BlockEntityType.Builder.of(ExampleBlockEntity::new, EXAMPLE_BLOCK.get()).build(null) );
val EXAMPLE_BLOCK_ENTITY: RegistryObject<BlockEntityType<ExampleBlockEntity>> = REGISTER.register("example_block_entity") { BlockEntityType.Builder.of(::ExampleBlockEntity, EXAMPLE_BLOCK.get()).build(null)) }
final val EXAMPLE_BLOCK_ENTITY = REGISTER.register("example_block_entity", () => BlockEntityType.Builder.of(() => new ExampleBlockEntity(), GeneralRegistrar.EXAMPLE_BLOCK.get).build(null))
Non-Forge Registries
Not all vanilla registries are wrapped as a Forge registry. This is because the registry is fully independent from any other registry, completely data driven, or just has not been wrapped yet.
These registries include:
- Custom Stats (a
ResourceLocation
registry) RuleTestType
PosRuleTestType
RecipeType
GameEvent
PositionSourceType
VillagerType
LootPoolEntryType
LootItemFunctionType
LootItemConditionType
LootNumberProviderType
LootNbtProviderType
LootScoreProviderType
FloatProviderType
IntProviderType
HeightProviderType
StructurePieceType
TrunkPlacerType
FeatureSizeType
- A
Codec
ofBiomeSource
- A
Codec
ofChunkGenerator
StructureProcessorType
StructurePoolElementType
- All registries within
BuiltinRegistries
excludingBiome
To register objects to any one of these registries, you will need to call Registry::register(Registry, ResourceLocation, T)
where the type parameter T
is the object instance being registered. The method can then be called and registered during the highest priority of FMLCommonSetupEvent
. We can also utilize a Lazy
above to store the result on first access and use within our code.
Here is an example: (the event handler is registered on the mod event bus)
public static final Lazy<ConfiguredFeature<?, ?>> EXAMPLE_CONFIGURED_FEATURE = Lazy.of(() -> register("example_configured_feature", Feature.NO_OP.configured(NoneFeatureConfiguration.INSTANCE) .decorated(FeatureDecorator.NOPE.configured(NoneDecoratorConfiguration.INSTANCE)) ) ); @SubscribeEvent(priority = EventPriority.HIGHEST) public void register(FMLCommonSetupEvent event) { event.enqueueWork(EXAMPLE_CONFIGURED_FEATURE::get); } private static <T extends ConfiguredFeature<?, ?>> T register(String name, T value) { return Registry.register(BuiltinRegistries.CONFIGURED_FEATURE, new ResourceLocation(MODID, name), value); }
val EXAMPLE_CONFIGURED_FEATURE: ConfiguredFeature<*, *> by lazy { register("example_configured_feature", Feature.NO_OP.configured(NoneFeatureConfiguration.INSTANCE) .decorated(FeatureDecorator.NOPE.configured(NoneDecoratorConfiguration.INSTANCE))) } object Events { @SubscribeEvent(priority = EventPriority.HIGHEST) fun register(event: FMLCommonSetupEvent) { event.enqueueWork(EXAMPLE_CONFIGURED_FEATURE::getConfig) } } private fun <T: ConfiguredFeature<*, *>> register(name: String, value: T): T = Registry.register(BuiltinRegistries.CONFIGURED_FEATURE, ResourceLocation(MODID, name), value)
class Events { @SubscribeEvent(priority = EventPriority.HIGHEST) def register(event: FMLCommonSetupEvent): Unit = { event.enqueueWork(Events.EXAMPLE_CONFIGURED_FEATURE.getConfig _) } } object Events { final lazy val EXAMPLE_CONFIGURED_FEATURE = register("example_configured_feature", Feature.NO_OP.configured(NoneFeatureConfiguration.INSTANCE) .decorated(FeatureDecorator.NOPE.configured(NoneDecoratorConfiguration.INSTANCE))) private def register[T <: ConfiguredFeature[_, _]](name: String, value: T): T = Registry.register(BuiltinRegistries.CONFIGURED_FEATURE, new ResourceLocation(MODID, name), value) }
Warning
#enqueueWork
.Besides the registries within BuiltinRegistries
, all other non-forge wrapped registries can be statically initialized like so:
public static final RecipeType<ExampleRecipe> EXAMPLE_RECIPE = RecipeType.register(MODID + ":example_recipe");
val EXAMPLE_RECIPE: RecipeType<ExampleRecipe> = RecipeType.register("${MODID}:example_recipe")
final val EXAMPLE_RECIPE = RecipeType.register(s"${MODID}:example_recipe")
If you attempt to make one of these instances require an instance of another registry object, you must use the lazy initialization method mentioned above to register the object in the correct order.
Data Driven Entries
Registries are considered to be data driven if they are located within RegistryAccess
with the exception of LevelStem
and Level
.
The following registries are data driven:
ConfiguredSurfaceBuilder
ConfiguredWorldCarver
ConfiguredFeature
ConfiguredStructureFeature
StructureProcessorList
StructureTemplatePool
Biome
NoiseGeneratorSettings
DimensionType
LevelStem
Level
These registry objects only need to be registered within code if they are to be used within a pre-existing registry object (e.g. a ConfiguredFeature
for ore generation within an overworld Biome
). Otherwise, their instance can be purely registered using a JSON file.
If a data driven registry object has to be registered within code, a dummy object should be supplied to hold a "registry name" and then constructed within a JSON file.
Referencing Registered Objects
Each forge registered object should not be statically initialized nor reference another instance being registered. They must always be a new, singleton instance that is resolved during their respective RegistryEvent$Register
event. This is to maintain a sane loading order for registries and their objects along with dynamic loading/unloading of mods.
Forge registered objects must always be referenced through a RegistryObject
or a field with @ObjectHolder
.
Using RegistryObjects
RegistryObject
s can be used to retrieve references to registered objects once they become available. Their references are updated along with all @ObjectHolder
annotations after the associated RegistryEvent$Register
has been dispatched and frozen.
A RegistryObject
can be retrieved as a result of using DeferredRegister
or calling the static constructor RegistryObject::of
. Each static constructor takes in the "registry name" of the object being referenced and either a IForgeRegistry
or, if custom registries are used, a supplier of the object class implementing IForgeRegistryEntry
. The RegistryObject
can be stored within some field and retrieve the registered object using #get
.
An example using RegistryObject
:
public static final RegistryObject<Item> EXAMPLE_ITEM = RegistryObject.of(new ResourceLocation("examplemod:example_item"), ForgeRegistries.ITEMS); // Assume that 'ExampleRegistry' is a valid registry, and 'examplemod:example_object' is a valid object within that registry public static final RegistryObject<ExampleRegistry> EXAMPLE_OBJECT = RegistryObject.of(new ResourceLocation("examplemod", "example_object"), () -> ExampleRegistry.class);
val EXAMPLE_ITEM: RegistryObject<Item> = RegistryObject.of(ResourceLocation("examplemod:example_item"), ForgeRegistries.ITEMS) // Assume that 'ExampleRegistry' is a valid registry, and 'examplemod:example_object' is a valid object within that registry val EXAMPLE_OBJECT: RegistryObject<ExampleRegistry> = RegistryObject.of(ResourceLocation("examplemod", "example_object")) { ExampleRegistry.class }
final val EXAMPLE_ITEM = RegistryObject.of(new ResourceLocation("examplemod:example_item"), ForgeRegistries.ITEMS) // Assume that 'ExampleRegistry' is a valid registry, and 'examplemod:example_object' is a valid object within that registry final val EXAMPLE_OBJECT = RegistryObject.of(new ResourceLocation("examplemod", "example_object"), () => classOf[ExampleRegistry]);
Important
Using @ObjectHolder
Forge registry objects can also be injected into public static
fields with either their class or that field annotated with @ObjectHolder
. There must be enough information to construct a ResourceLocation
to identify a single object within a specific registry.
The rules for @ObjectHolder
are as follows:
- If the class is annotated with
@ObjectHolder
, its value will be the default namespace for all fields within if not explicitly defined - If the class is annotated with
@Mod
, the modid will be the default namespace for all annotated fields within if not explicitly defined - A field is considered for injection if:
- it has at least the modifiers
public static
; and - one of the following conditions are true:
- the enclosing class has an
@ObjectHolder
annotation, and the field isfinal
, and:- the name value is the field's name; and
- the namespace value is the enclosing class's namespace
- An exception is thrown if the namespace value cannot be found and inherited
- the field is annotated with
@ObjectHolder
, and:- the name value is explicitly defined; and
- the namespace value is either explicitly defined or the enclosing class's namespace
- the enclosing class has an
- the field type or one of its supertypes corresponds to a valid registry (e.g.
Item
orArrowItem
for theItem
registry) - An exception is thrown if a field does not have a corresponding registry.
- it has at least the modifiers
- An exception is thrown if the resulting
ResourceLocation
is incomplete or invalid (non-valid characters in path) - If no other errors or exceptions occur, the field will be injected
- If all of the above rules do not apply, no action will be taken (and a message may be logged)
@ObjectHolder
annotated fields are injected with their associated object values after their corresponding registry's RegistryEvent$Register
event is fired, along with RegistryObject
s.
Warning
As these rules are rather complicated, here are some examples:
@ObjectHolder
@ObjectHolder("minecraft") // Inheritable resource namespace: "minecraft" class AnnotatedHolder { public static final Block diamond_block = null; // No annotation. [public static final] is required. // Block has a corresponding registry: [Block] // Name path is the name of the field: "diamond_block" // Namespace is not explicitly defined. // So, namespace is inherited from class annotation: "minecraft" // To inject: "minecraft:diamond_block" from the [Block] registry @ObjectHolder("ambient.cave") public static SoundEvent ambient_sound = null; // Annotation present. [public static] is required. // SoundEvent has a corresponding registry: [SoundEvent] // Name path is the value of the annotation: "ambient.cave" // Namespace is not explicitly defined. // So, namespace is inherited from class annotation: "minecraft" // To inject: "minecraft:ambient.cave" from the [SoundEvent] registry // Assume for the next entry that [ManaType] is a valid registry. @ObjectHolder("neomagicae:coffeinum") public static final ManaType coffeinum = null; // Annotation present. [public static] is required. [final] is optional. // ManaType has a corresponding registry: [ManaType] (custom registry) // Resource location is explicitly defined: "neomagicae:coffeinum" // To inject: "neomagicae:coffeinum" from the [ManaType] registry public static final Item ENDER_PEARL = null; // No annotation. [public static final] is required. // Item has a corresponding registry: [Item]. // Name path is the name of the field: "ENDER_PEARL" -> "ender_pearl" // !! ^ Field name is valid, because they are // converted to lowercase automatically. // Namespace is not explicitly defined. // So, namespace is inherited from class annotation: "minecraft" // To inject: "minecraft:ender_pearl" from the [Item] registry @ObjectHolder("minecraft:arrow") public static final ArrowItem arrow = null; // Annotation present. [public static] is required. [final] is optional. // ArrowItem does not have a corresponding registry. // ArrowItem's supertype of Item has a corresponding registry: [Item] // Resource location is explicitly defined: "minecraft:arrow" // To inject: "minecraft:arrow" from the [Item] registry public static Block bedrock = null; // No annotation, so [public static final] is required. // Therefore, the field is ignored. public static final CreativeModeTab group = null; // No annotation. [public static final] is required. // CreativeModeTab does not have a corresponding registry. // No supertypes of CreativeModeTab has a corresponding registry. // Therefore, THIS WILL PRODUCE AN EXCEPTION. } class UnannotatedHolder { // Note the lack of an @ObjectHolder annotation on this class. @ObjectHolder("minecraft:flame") public static final Enchantment flame = null; // Annotation present. [public static] is required. [final] is optional. // Enchantment has corresponding registry: [Enchantment]. // Resource location is explicitly defined: "minecraft:flame" // To inject: "minecraft:flame" from the [Enchantment] registry public static final Biome ice_flat = null; // No annotation on the enclosing class. // Therefore, the field is ignored. @ObjectHolder("minecraft:creeper") public static Entity creeper = null; // Annotation present. [public static] is required. // Entity does not have a corresponding registry. // No supertypes of Entity has a corresponding registry. // Therefore, THIS WILL PRODUCE AN EXCEPTION. @ObjectHolder("levitation") public static final Potion levitation = null; // Annotation present. [public static] is required. [final] is optional. // Potion has a corresponding registry: [Potion]. // Name path is the value of the annotation: "levitation" // Namespace is not explicitly defined. // No annotation in enclosing class. // Therefore, THIS WILL PRODUCE AN EXCEPTION. }
Creating Custom Registries
Creating custom registries for your mod might be useful if you want other mods to add new things to your system. For example, you might have magic spells and want to allow other mods to add new spells. For this you will want to make a registry (eg. "mymagicmod:spells"). This way, other mods will be able to register things to that list, and you won't have to do anything else.
Just like with registering a new item or block you have two ways of making a new registry. Each method takes in a RegistryBuilder
which is used to build an IForgeRegistry
for an object class that implements IForgeRegistryEntry
. Each builder should have its name and type set via #setName
and #setType
respectively before being created.
For the class that implements IForgeRegistryEntry
, it is recommended in most cases to extend the default implementation of ForgeRegistryEntry
. For interfaces, it should extend IForgeRegistryEntry
with its implementations extending ForgeRegistryEntry
.
With DeferredRegister
The first method involves the second static constructor: DeferredRegister::create(Class, String)
. The class supplied must extend IForgeRegistryEntry
. From there, we can construct the registry using #makeRegistry
. This will already populate #setName
and #setType
for us. This method also returns a supplier of the registry which we can use after the RegistryEvent$NewRegistry
event.
Here is an example:
public static final DeferredRegister<ExampleRegistry> EXAMPLE = DeferredRegister.create(ExampleRegistry.class, MODID); public static final Lazy<IForgeRegistry<ExampleRegistry>> REGISTRY = Lazy.of(EXAMPLE.makeRegistry("example_registry", RegistryBuilder::new));
val EXAMPLE: DeferredRegister<ExampleRegistry> = DeferredRegister.create(ExampleRegistry::class.java, MODID) val REGISTRY: IForgeRegistry<ExampleRegistry> by lazy { EXAMPLE.makeRegistry("example_registry", ::RegistryBuilder).get() }
final val EXAMPLE = DeferredRegister.create(classOf[ExampleRegistry], MODID) final lazy val REGISTRY = EXAMPLE.makeRegistry("example_registry", () => new RegistryBuilder).get
Using RegistryEvent$NewRegistry
The second method can be done during the RegistryEvent$NewRegistry
event. This will call a new instance of the builder directly. From there, the registry can be built and stored via RegistryBuilder#create
. This will cause the registry to be registered to the RegistryManager
and returned to the caller for additional processing.
Here is an example: (the event handler is registered on the mod event bus)
public static IForgeRegistry<ExampleRegistry> registry = null; @SubscribeEvent public void onNewRegistry(RegistryEvent.NewRegistry event){ RegistryBuilder<ExampleRegistry> registryBuilder = new RegistryBuilder<>(); registryBuilder.setName(new ResourceLocation(MODID, "example_registry"); registryBuilder.setType(ExampleRegistry.class); registry = registryBuilder.create(); }
Handling Missing Entries
When loading a pre-existing world after removing mods or updating versions, there are cases where certain registry objects will cease to exist. In these cases, it is possible to specify actions to remove a mapping, prevent the world from loading, or remap the name as needed. This can be done through the third of the registry events: RegistryEvent$MissingMappings<T>
, where the type parameter T
is the object type being registered. Within the event, you can grab an immutable list of missing mappings associated with a mod id via #getMappings
or a list of all mappings via #getAllMappings
.
For each Mapping
, you can either execute one of the following methods:
#ignore
which abandons the entry when loading#warn
which warns the user about the missing entry but continues loading#fail
which prevents the world from loading#remap
which remaps the entry to the specified non-null object in the same registry
If none of the above are specified, then the default action of notifying the user about the missing mappings occur.
Here is an example: (the event handler is registered on the mod event bus)
// This will ignore any missing test items from the specified world @SubscribeEvent public void onMissingItems(RegistryEvent.MissingMappings<Item> event){ event.getMappings(MODID).stream() .filter(mapping -> mapping.key.getPath().contains("test")) .forEach(Mapping::ignore); }