Views
Actions
Difference between revisions of "Registration"
(Fix ObjectHolder info) |
(Fix ObjectHolder) |
||
Line 166: | Line 166: | ||
* A field is considered for injection if: | * A field is considered for injection if: | ||
** it has at least the modifiers <code>public static</code>; and | ** it has at least the modifiers <code>public static</code>; and | ||
− | + | ** the '''field''' is annotated with <code>@ObjectHolder</code>, and: | |
− | + | *** the name value is explicitly defined; and | |
− | + | *** the registry name value is explicitly defined | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
** ''An exception is thrown if a field does not have a corresponding registry.'' | ** ''An exception is thrown if a field does not have a corresponding registry.'' | ||
* ''An exception is thrown if the resulting <code>ResourceLocation</code> is incomplete or invalid (non-valid characters in path)'' | * ''An exception is thrown if the resulting <code>ResourceLocation</code> is incomplete or invalid (non-valid characters in path)'' | ||
Line 190: | Line 183: | ||
<div class="mw-collapsible-content" style="overflow: auto; white-space: nowrap;"> | <div class="mw-collapsible-content" style="overflow: auto; white-space: nowrap;"> | ||
<syntaxhighlight lang="java"> | <syntaxhighlight lang="java"> | ||
− | + | class Holder { | |
− | class | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
@ObjectHolder(registryName = "enchantment", value = "minecraft:flame") | @ObjectHolder(registryName = "enchantment", value = "minecraft:flame") | ||
public static final Enchantment flame = null; // Annotation present. [public static] is required. [final] is optional. | public static final Enchantment flame = null; // Annotation present. [public static] is required. [final] is optional. | ||
Line 250: | Line 190: | ||
// To inject: "minecraft:flame" from the [Enchantment] registry | // To inject: "minecraft:flame" from the [Enchantment] registry | ||
− | public static final Biome ice_flat = null; // No annotation on the | + | public static final Biome ice_flat = null; // No annotation on the field. |
// Therefore, the field is ignored. | // Therefore, the field is ignored. | ||
@ObjectHolder("minecraft:creeper") | @ObjectHolder("minecraft:creeper") | ||
public static Entity creeper = null; // Annotation present. [public static] is required. | public static Entity creeper = null; // Annotation present. [public static] is required. | ||
− | + | // The registry has not been specified on the field. | |
− | // The registry has not been specified on the | ||
// Therefore, THIS WILL PRODUCE AN EXCEPTION. | // Therefore, THIS WILL PRODUCE AN EXCEPTION. | ||
− | @ObjectHolder(registryName = "potion | + | @ObjectHolder(registryName = "potion") |
public static final Potion levitation = null; // Annotation present. [public static] is required. [final] is optional. | public static final Potion levitation = null; // Annotation present. [public static] is required. [final] is optional. | ||
// Registry name is explicitly defined: "minecraft:potion" | // Registry name is explicitly defined: "minecraft:potion" | ||
− | // | + | // Resource location is not specified on the field |
− | |||
− | |||
// Therefore, THIS WILL PRODUCE AN EXCEPTION. | // Therefore, THIS WILL PRODUCE AN EXCEPTION. | ||
} | } |
Revision as of 11:29, 14 June 2022
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.
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 RegisterEvent
lifecycle event.
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 RegisterEvent
for the associated registry. 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.
Important
DeferredRegister
s with non-vanilla registries, the registry key or the registry name should be supplied to the create
method. These include the custom Forge registries for entity data serializers, global loot modifier serializers, world presets, and biome modifier serializers. Calling Supplier#get
on a Supplier<IForgeRegistry<?>>
when making a DeferredRegister will return null because the registry does not exist yet.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) }
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))); ExampleMod() { BLOCKS.register(FMLJavaModLoadingContext.get().modEventBus); }
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
.RegisterEvent
The RegisterEvent
is the second way register objects. This event is fired for each registry synchronously in vanilla registry order after FMLConstructModEvent
and before the configs are loaded. Objects are registered using #register
by passing in the registry key, the name of the registry object, and the object itself. There is an additional #register
overload which takes in a consumed helper to register an object with a given name. It is recommended to use this method to avoid unnecessary object creation.
Here is an example: (the event handler is registered on the mod event bus)
@SubscribeEvent public void register(RegisterEvent event) { event.register(ForgeRegistries.Keys.BLOCKS, helper -> helper.register(new ResourceLocation(MODID, "example_block"), new Block(...)) ); }
@JvmStatic @SubscribeEvent private fun register(event: RegisterEvent) = event.register(ForgeRegistries.Keys.BLOCKS) { helper -> helper.register(ResourceLocation(MODID, "example_block"), Block(...)) }
@SubscribeEvent def register(event: RegisterEvent): Unit = event.register(ForgeRegistries.Keys.BLOCKS, helper => helper.register(new ResourceLocation(MODID, "example_block"), new Block(...)) )
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. To register objects to any one of these registries, create a DeferredRegister
via the #create
overload which takes in a resource key of the registry and the mod id to register the entries for. Then simply call #register
like any other DeferredRegister
.
private static final DeferredRegister<LootItemConditionType> REGISTER = DeferredRegister.create(Registry.LOOT_ITEM_REGISTRY, MODID); public static final RegistryObject<LootItemConditionType> EXAMPLE_LOOT_ITEM_CONDITION_TYPE = REGISTER.register("example_loot_item_condition_type", () -> new LootItemConditionType(...));
private val REGISTER = DeferredRegister.create(Registry.LOOT_ITEM_REGISTRY, MODID) val EXAMPLE_LOOT_ITEM_CONDITION_TYPE : RegistryObject<LootItemConditionType> = REGISTER.register("example_loot_item_condition_type") { LootItemConditionType(...) }
private final val REGISTER = DeferredRegister.create(Registry.LOOT_ITEM_REGISTRY, MODID) final val EXAMPLE_LOOT_ITEM_CONDITION_TYPE = REGISTER.register("example_loot_item_condition_type", () => new LootItemConditionType(...))
private static final DeferredRegister<LootItemConditionType> REGISTER = DeferredRegister.create(Registry.LOOT_ITEM_REGISTRY, MODID); public static final RegistryObject<LootItemConditionType> EXAMPLE_LOOT_ITEM_CONDITION_TYPE = REGISTER.register("example_loot_item_condition_type", () -> new LootItemConditionType(...));
If you attempt to make one of these instances require an instance of another registry object, you should 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
.
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 PlacedFeature
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 when RegisterEvent
is called for their registry. 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 registry that RegisterEvent
is called for is dispatched and frozen.
A RegistryObject
can be retrieved as a result of using DeferredRegister
or calling the static factory RegistryObject#create
. Each static factory takes in the "registry name" of the object being referenced and one of the following: a IForgeRegistry
, a registry name of the type ResourceLocation
, or a registry key of the type ResourceKey<? extends Registry<?>>
. 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.create(new ResourceLocation("examplemod", "example_item"), ForgeRegistries.ITEMS); // Assume that 'examplemod:example_registry' is a valid registry, and 'examplemod:example_object' is a valid object within that registry public static final RegistryObject<ExampleRegistry> EXAMPLE_OBJECT = RegistryObject.create(new ResourceLocation("examplemod", "example_object"), new ResourceLocation("examplemod", "example_registry"), "examplemod");
val EXAMPLE_ITEM: RegistryObject<Item> = RegistryObject.create(ResourceLocation("examplemod", "example_item"), ForgeRegistries.ITEMS) // Assume that 'examplemod:example_registry' is a valid registry, and 'examplemod:example_object' is a valid object within that registry val EXAMPLE_OBJECT: RegistryObject<ExampleRegistry> = RegistryObject.create(ResourceLocation("examplemod", "example_object"), new ResourceLocation("examplemod", "example_registry"), "examplemod")
final val EXAMPLE_ITEM = RegistryObject.create(new ResourceLocation("examplemod", "example_item"), ForgeRegistries.ITEMS) // Assume that 'examplemod:example_registry' is a valid registry, and 'examplemod:example_object' is a valid object within that registry final val EXAMPLE_OBJECT = RegistryObject.create(new ResourceLocation("examplemod", "example_object"), new ResourceLocation("examplemod", "example_registry"), "examplemod");
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 - the field is annotated with
@ObjectHolder
, and:- the name value is explicitly defined; and
- the registry name value is explicitly defined
- 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 RegisterEvent
is fired for their registry, along with RegistryObject
s.
Warning
As these rules are rather complicated, here are some examples:
@ObjectHolder
class Holder { @ObjectHolder(registryName = "enchantment", value = "minecraft:flame") public static final Enchantment flame = null; // Annotation present. [public static] is required. [final] is optional. // Registry name is explicitly defined: "minecraft: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 field. // Therefore, the field is ignored. @ObjectHolder("minecraft:creeper") public static Entity creeper = null; // Annotation present. [public static] is required. // The registry has not been specified on the field. // Therefore, THIS WILL PRODUCE AN EXCEPTION. @ObjectHolder(registryName = "potion") public static final Potion levitation = null; // Annotation present. [public static] is required. [final] is optional. // Registry name is explicitly defined: "minecraft:potion" // Resource location is not specified on the field // 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
. Each builder should have its name set via #setName
before being created.
With DeferredRegister
The first method involves the second static constructor: DeferredRegister#create(ResourceLocation, String)
. From there, we can construct the registry using #makeRegistry
. This will already populate #setName
for us. This method also returns a supplier of the registry which we can use after the NewRegistryEvent
is called.
Here is an example:
public static final DeferredRegister<ExampleRegistry> EXAMPLE = DeferredRegister.create(new ResourceLocation(MODID, "example_registry"), MODID); public static final Supplier<IForgeRegistry<ExampleRegistry>> REGISTRY = EXAMPLE.makeRegistry(RegistryBuilder::new);
val EXAMPLE: DeferredRegister<ExampleRegistry> = DeferredRegister.create(ResourceLocation(MODID, "example_registry"), MODID) val REGISTRY: IForgeRegistry<ExampleRegistry> by lazy { EXAMPLE.makeRegistry(::RegistryBuilder).get() }
final val EXAMPLE = DeferredRegister.create(new ResourceLocation(MODID, "example_registry"), MODID) final lazy val REGISTRY = EXAMPLE.makeRegistry(() => new RegistryBuilder).get
Using NewRegistryEvent
The second method can be done during the NewRegistryEvent
event. Using NewRegistryEvent#create
, you can pass in a RegistryBuilder
directly. This method will return a Supplier<IForgeRegistry<V>>
that can be stored and queried after the event is fired to gain access to your IForgeRegistry
instance.
Here is an example: (the event handler is registered on the mod event bus)
public static Supplier<IForgeRegistry<ExampleRegistry>> registrySupplier = null; @SubscribeEvent public void onNewRegistry(NewRegistryEvent event){ RegistryBuilder<ExampleRegistry> registryBuilder = new RegistryBuilder<>(); registryBuilder.setName(new ResourceLocation(MODID, "example_registry"); registrySupplier = event.create(registryBuilder); }
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: MissingMappingsEvent
. Within the event, you can grab an immutable list of missing mappings associated with a mod id for a given registry 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 forge event bus)
// This will ignore any missing test items from the specified world @SubscribeEvent public void onMissing(final MissingMappingsEvent event) { event.getMappings(ForgeRegistries.Keys.ITEMS, MODID).stream() .filter(mapping -> mapping.key.getPath().contains("test")) .forEach(Mapping::ignore); }
// This will ignore any missing test items from the specified world @SubscribeEvent void onMissing(final MissingMappingsEvent event) { event.getMappings(ForgeRegistries.Keys.ITEMS, MODID).stream() .filter(mapping -> mapping.key.path.contains("test")) .forEach(Mapping::ignore); }