Minecraft Forge — Reference
Core Concepts: Registries
Registries
Registration is the process of taking the objects of a mod (such as items, blocks, sounds, etc.) and making them known to the game. Registering things is important, as without registration the game will simply not know about these objects, which will cause unexplainable behaviors and crashes.
Most things that require registration in the game are handled by the Forge registries. A registry is an object similar to a map that assigns values to keys. Forge uses registries with ResourceLocation keys to register objects. This allows the ResourceLocation to act as the “registry name” for objects.
Every type of registrable object has its own registry. To see all registries wrapped by Forge, see the ForgeRegistries class. All registry names within a registry must be unique. However, names in different registries will not collide. For example, there’s a Block registry, and an Item registry. A Block and an Item may be registered with the same name example:thing without colliding; however, if two different Blocks or Items were registered with the same exact name, the second object will override the first.
Methods for Registering
There are two proper ways to register objects: the DeferredRegister class, and the RegisterEvent lifecycle event.
DeferredRegister
DeferredRegister is the recommended way to register objects. It allows the use and convenience of static initializers while avoiding the issues associated with it. It simply maintains a list of suppliers for entries and registers the objects from those suppliers during RegisterEvent.
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> ROCK_BLOCK = BLOCKS.register("rock", () -> new Block(BlockBehaviour.Properties.of().mapColor(MapColor.STONE)));
public ExampleMod(FMLJavaModLoadingContext context) {
BLOCKS.register(context.getModEventBus());
}
RegisterEvent
RegisterEvent is the second way to register objects. This event is fired for each registry after the mod constructors and before the loading of configs. 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(ResourceLocation.fromNamespaceAndPath(MODID, "example_block_1"), new Block(...));
helper.register(ResourceLocation.fromNamespaceAndPath(MODID, "example_block_2"), new Block(...));
helper.register(ResourceLocation.fromNamespaceAndPath(MODID, "example_block_3"), new Block(...));
// ...
}
);
}
Registries that aren’t Forge Registries
Not all registries are wrapped by Forge. These can be static registries, like LootItemConditionType, which are safe to use. There are also dynamic registries, like ConfiguredFeature and some other worldgen registries, which are typically represented in JSON. DeferredRegister#create has an overload which allows modders to specify the registry key of which vanilla registry to create a RegistryObject for. The registry method and attaching to the mod event bus is the same as other DeferredRegisters.
Important
Dynamic registry objects can only be registered through data files (e.g. JSON). They cannot be registered in-code.
private static final DeferredRegister<LootItemConditionType> REGISTER = DeferredRegister.create(Registries.LOOT_CONDITION_TYPE, "examplemod");
public static final RegistryObject<LootItemConditionType> EXAMPLE_LOOT_ITEM_CONDITION_TYPE = REGISTER.register("example_loot_item_condition_type", () -> new LootItemConditionType(...));
Note
Some classes cannot by themselves be registered. Instead, *Type classes are registered, and used in the formers’ constructors. For example, BlockEntity has BlockEntityType, and Entity has EntityType. These *Type classes are factories that simply create the containing type on demand.
These factories are created through the use of their *Type$Builder classes. An example: (REGISTER 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)
);
Referencing Registered Objects
Registered objects should not be stored in fields when they are created and registered. They are to be always newly created and registered whenever RegisterEvent is fired for that registry. This is to allow dynamic loading and unloading of mods in a future version of Forge.
Registered objects must always be referenced through a RegistryObject or a field with @ObjectHolder.
Using RegistryObjects
RegistryObjects can be used to retrieve references to registered objects once they are available. These are used by DeferredRegister to return a reference to the registered objects. Their references are updated after RegisterEvent is called for their registry, along with the @ObjectHolder annotations.
To get a RegistryObject, call RegistryObject#create with a ResourceLocation and the IForgeRegistry of the registrable object. Custom registries can also be used by supplying the registry name instead. Store the RegistryObject in a public static final field, and call #get whenever you need the registered object.
An example of using RegistryObject:
public static final RegistryObject<Item> BOW = RegistryObject.create(ResourceLocation.withDefaultNamespace("bow"), ForgeRegistries.ITEMS);
// assume that 'neomagicae:mana_type' is a valid registry, and 'neomagicae:coffeinum' is a valid object within that registry
public static final RegistryObject<ManaType> COFFEINUM = RegistryObject.create(ResourceLocation.fromNamespaceAndPath("neomagicae", "coffeinum"), ResourceLocation.fromNamespaceAndPath("neomagicae", "mana_type"), "neomagicae");
Using @ObjectHolder
Registered objects from registries can be injected into the public static fields by annotating classes or fields with @ObjectHolder and supplying enough information to construct a ResourceLocation to identify a specific object in 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; - the field is annotated with
@ObjectHolder, and: - the name value is explicitly defined; and
- the registry name value is explicitly defined
- A compile-time exception is thrown if a field does not have a corresponding registry or name.
- An exception is thrown if the resulting
ResourceLocationis 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 values after RegisterEvent is fired for their registry, along with the RegistryObjects.
Note
If the object does not exist in the registry when it is to be injected, a debug message will be logged and no value will be injected.
As these rules are rather complicated, here are some examples:
class Holder {
@ObjectHolder(registryName = "minecraft: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 A COMPILE-TIME 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 A COMPILE-TIME EXCEPTION.
}
Creating Custom Forge Registries
Custom registries can usually just be a simple map of key to value. This is a common style; however, it forces a hard dependency on the registry being present. It also requires that any data that needs to be synced between sides must be done manually. Custom Forge Registries provide a simple alternative for creating soft dependents along with better management and automatic syncing between sides (unless told otherwise). Since the objects also use a Forge registry, registration becomes standardized in the same way.
Custom Forge Registries are created with the help of a RegistryBuilder, through either NewRegistryEvent or the DeferredRegister. The RegistryBuilder class takes various parameters (such as the registry’s name, id range, and various callbacks for different events happening on the registry). New registries are registered to the RegistryManager after NewRegistryEvent finishes firing.
Any newly created registry should use its associated registration method to register the associated objects.
Using NewRegistryEvent
When using NewRegistryEvent, calling #create with a RegistryBuilder will return a supplier-wrapped registry. The supplied registry can be accessed after NewRegistryEvent has finished posting to the mod event bus. Getting the custom registry from the supplier before NewRegistryEvent finishes firing will result in a null value.
New Datapack Registries
New datapack registries can be added using the DataPackRegistryEvent$NewRegistry event on the mod event bus. The registry is created via #dataPackRegistry by passing in the ResourceKey representing the registry name and the Codec used to encode and decode the data from JSON. An optional Codec can be provided to sync the datapack registry to the client.
Important
Datapack Registries cannot be created with DeferredRegister. They can only be created through the event.
With DeferredRegister
The DeferredRegister method is once again another wrapper around the above event. Once a DeferredRegister is created in a constant field using the #create overload which takes in the registry name and the mod id, the registry can be constructed via DeferredRegister#makeRegistry. This takes in a supplied RegistryBuilder containing any additional configurations. The method already populates #setName by default. Since this method can be returned at any time, a supplied version of an IForgeRegistry is returned instead. Getting the custom registry from the supplier before NewRegistryEvent is fired will result in a null value.
Important
DeferredRegister#makeRegistry must be called before the DeferredRegister is added to the mod event bus via #register. #makeRegistry also uses the #register method to create the registry during NewRegistryEvent.
Handling Missing Entries
There are cases where certain registry objects will cease to exist whenever a mod is updated or, more likely, removed. It is possible to specify actions to handle the missing mapping through the third of the registry events: MissingMappingsEvent. Within this event, a list of missing mappings can be obtained either by #getMappings given a registry key and mod id or all mappings via #getAllMappings given a registry key.
Important
MissingMappingsEvent is fired on the Forge event bus.
For each Mapping, one of four mapping types can be selected to handle the missing entry:
| Action | Description |
|---|---|
| IGNORE | Ignores the missing entry and abandons the mapping. |
| WARN | Generates a warning in the log. |
| FAIL | Prevents the world from loading. |
| REMAP | Remaps the entry to an already registered, non-null object. |
If no action is specified, then the default action will occur by notifying the user about the missing entry and whether they still would like to load the world. All actions besides remapping will prevent any other registry object from taking the place of the existing id in case the associated entry ever gets added back into the game.
Core Concepts: Sides
Sides in Minecraft
A very important concept to understand when modding Minecraft are the two sides: client and server. There are many, many common misconceptions and mistakes regarding siding, which can lead to bugs that might not crash the game, but can rather have unintended and often unpredictable effects.
Different Kinds of Sides
When we say “client” or “server”, it usually follows with a fairly intuitive understanding of what part of the game we are talking about. After all, a client is what the user interacts with, and a server is where the user connects for a multiplayer game. Easy, right?
As it turns out, there can be some ambiguity even with two such terms. Here we disambiguate the four possible meanings of “client” and “server”:
- Physical client - The physical client is the entire program that runs whenever you launch Minecraft from the launcher. All threads, processes, and services that run during the game’s graphical, interactable lifetime are part of the physical client.
- Physical server - Often known as the dedicated server, the physical server is the entire program that runs whenever you launch any sort of
minecraft_server.jarthat does not bring up a playable GUI. - Logical server - The logical server is what runs game logic: mob spawning, weather, updating inventories, health, AI, and all other game mechanics. The logical server is present within a physical server, but it also can run inside a physical client together with a logical client, as a single player world. The logical server always runs in a thread named the
Server Thread. - Logical client - The logical client is what accepts input from the player and relays it to the logical server. In addition, it also receives information from the logical server and makes it available graphically to the player. The logical client runs in the
Render Thread, though often several other threads are spawned to handle things like audio and chunk render batching.
In the MinecraftForge codebase, the physical side is represented by an enum called Dist, while the logical side is represented by an enum called LogicalSide.
Performing Side-Specific Operations
Level#isClientSide
This boolean check will be your most used way to check sides. Querying this field on a Level object establishes the logical side the level belongs to. That is, if this field is true, the level is currently running on the logical client. If the field is false, the level is running on the logical server. It follows that the physical server will always contain false in this field, but we cannot assume that false implies a physical server, since this field can also be false for the logical server inside a physical client (in other words, a single player world).
Use this check whenever you need to determine if game logic and other mechanics should be run. For example, if you want to damage the player every time they click your block, or have your machine process dirt into diamonds, you should only do so after ensuring #isClientSide is false. Applying game logic to the logical client can cause desynchronization (ghost entities, desynchronized stats, etc.) in the best case, and crashes in the worst case.
This check should be used as your go-to default. Aside from DistExecutor, rarely will you need the other ways of determining side and adjusting behavior.
DistExecutor
Considering the use of a single “universal” jar for client and server mods, and the separation of the physical sides into two jars, an important question comes to mind: How do we use code that is only present on one physical side? All code in net.minecraft.client is only present on the physical client. If any class you write references those names in any way, they will crash the game when that respective class is loaded in an environment where those names do not exist. A very common mistake in beginners is to call Minecraft.getInstance().<doStuff>() in block or block entity classes, which will crash any physical server as soon as the class is loaded.
How do we resolve this? Luckily, FML has DistExecutor, which provides various methods to run different methods on different physical sides, or a single method only on one side.
Note
It is important to understand that FML checks based on the physical side. A single player world (logical server + logical client within a physical client) will always use Dist.CLIENT!
DistExecutor works by taking in a supplied supplier executing a method, effectively preventing classloading by taking advantage of the invokedynamic JVM instruction. The executed method should be static and within a different class. Additionally, if no parameters are present for the static method, a method reference should be used instead of a supplier executing a method.
There are two main methods within DistExecutor: #runWhenOn and #callWhenOn. The methods take in the physical side the executing method should run on and the supplied executing method which either runs or returns a result respectively.
These two methods are subdivided further into #safe* and #unsafe* variants. Safe and unsafe variants are misnomers for their purposes. The main difference is that when in a development environment, the #safe* methods will validate that the supplied executing method is a lambda returning a method reference to another class with an error being thrown otherwise. Within the production environment, #safe* and #unsafe* are functionally the same.
// In a client class: ExampleClass
public static void unsafeRunMethodExample(Object param1, Object param2) {
// ...
}
public static Object safeCallMethodExample() {
// ...
}
// In some common class
DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> ExampleClass.unsafeRunMethodExample(var1, var2));
DistExecutor.safeCallWhenOn(Dist.CLIENT, () -> ExampleClass::safeCallMethodExample);
Warning
Due to a change in how invokedynamic works in Java 9+, all #safe* variants of the DistExecutor methods throw the original exception wrapped within a BootstrapMethodError in the development environment. #unsafe* variants or a check to FMLEnvironment#dist should be used instead.
Thread Groups
If Thread.currentThread().getThreadGroup() == SidedThreadGroups.SERVER is true, it is likely the current thread is on the logical server. Otherwise, it is likely on the logical client. This is useful to retrieve the logical side when you do not have access to a Level object to check isClientSide. It guesses which logical side you are on by looking at the group of the currently running thread. Because it is a guess, this method should only be used when other options have been exhausted. In nearly every case, you should prefer checking Level#isClientSide.
FMLEnvironment#dist and @OnlyIn
FMLEnvironment#dist holds the physical side your code is running on. Since it is determined at startup, it does not rely on guessing to return its result. The number of use cases for this is limited, however.
Annotating a method or field with the @OnlyIn(Dist) annotation indicates to the loader that the respective member should be completely stripped out of the definition not on the specified physical side. Usually, these are only seen when browsing through the decompiled Minecraft code, indicating methods that the Mojang obfuscator stripped out. There is NO reason for using this annotation directly. Use DistExecutor or a check on FMLEnvironment#dist instead.
Common Mistakes
Reaching Across Logical Sides
Whenever you want to send information from one logical side to another, you must always use network packets. It is incredibly tempting, when in a single player scenario, to directly transfer data from the logical server to the logical client.
This is actually very commonly inadvertently done through static fields. Since the logical client and logical server share the same JVM in a single player scenario, both threads writing to and reading from static fields will cause all sorts of race conditions and the classical issues associated with threading.
This mistake can also be made explicitly by accessing physical client-only classes such as Minecraft from common code that runs or can run on the logical server. This mistake is easy to miss for beginners who debug in a physical client. The code will work there, but it will immediately crash on a physical server.
Writing One-Sided Mods
In recent versions, Minecraft Forge has removed a “sidedness” attribute from the mods.toml. This means that your mods are expected to work whether they are loaded on the physical client or the physical server. So for one-sided mods, you would typically register your event handlers inside a DistExecutor#safeRunWhenOn or DistExecutor#unsafeRunWhenOn instead of directly calling the relevant registration methods in your mod constructor. Basically, if your mod is loaded on the wrong side, it should simply do nothing, listen to no events, and so on. A one-sided mod by nature should not register blocks, items, … since they would need to be available on the other side, too.
Additionally, if your mod is one-sided, it typically does not forbid the user from joining a server that is lacking that mod. Therefore, you should set the displayTest property in your mods.toml to whatever value is necessary.
[[mods]]
# ...
# MATCH_VERSION means that your mod will cause a red X if the versions on client and server differ. This is the default behaviour and should be what you choose if you have server and client elements to your mod.
# IGNORE_SERVER_VERSION means that your mod will not cause a red X if it's present on the server but not on the client. This is what you should use if you're a server only mod.
# IGNORE_ALL_VERSION means that your mod will not cause a red X if it's present on the client or the server. This is a special case and should only be used if your mod has no server component.
# NONE means that no display test is set on your mod. You need to do this yourself, see IExtensionPoint.DisplayTest for more information. You can define any scheme you wish with this value.
# IMPORTANT NOTE: this is NOT an instruction as to which environments (CLIENT or DEDICATED SERVER) your mod loads on. Your mod should load (and maybe do nothing!) wherever it finds itself.
displayTest="IGNORE_ALL_VERSION" # MATCH_VERSION is the default if nothing is specified (#optional)
If a custom display test is to be used, then the displayTest option should be set to NONE, and an IExtensionPoint$DisplayTest extension should be registered:
//Make sure the mod being absent on the other network side does not cause the client to display the server as incompatible
ModLoadingContext.get().registerExtensionPoint(IExtensionPoint.DisplayTest.class, () -> new IExtensionPoint.DisplayTest(() -> NetworkConstants.IGNORESERVERONLY, (a, b) -> true));
This tells the client that it should ignore the server version being absent, and the server that it should not tell the client this mod should be present. So this snippet works both for client- and server-only-sided mods.
Core Concepts: Events
Events
Forge uses an event bus that allows mods to intercept events from various Vanilla and mod behaviors.
Example: An event can be used to perform an action when a Vanilla stick is right clicked.
The main event bus used for most events is located at MinecraftForge#EVENT_BUS. There is another event bus for mod specific events located at FMLJavaModLoadingContext#getModEventBus that you should only use in specific cases. More information about this bus can be found below.
Every event is fired on one of these buses: most events are fired on the main forge event bus, but some are fired on the mod specific event buses.
An event handler is some method that has been registered to an event bus.
Creating an Event Handler
Event handlers methods have a single parameter and do not return a result. The method could be static or instance depending on implementation.
Event handlers can be directly registered using IEventBus#addListener for or IEventBus#addGenericListener for generic events (as denoted by subclassing GenericEvent<T>). Either listener adder takes in a consumer representing the method reference. Generic event handlers need to specify the class of the generic as well. Event handlers must be registered within the constructor of the main mod class.
// In the main mod class ExampleMod
// This event is on the mod bus
private void modEventHandler(RegisterEvent event) {
// Do things here
}
// This event is on the forge bus
private static void forgeEventHandler(AttachCapabilitiesEvent<Entity> event) {
// ...
}
// In the mod constructor
modEventBus.addListener(this::modEventHandler);
forgeEventBus.addGenericListener(Entity.class, ExampleMod::forgeEventHandler);
Instance Annotated Event Handlers
This event handler listens for the EntityItemPickupEvent, which is, as the name states, posted to the event bus whenever an Entity picks up an item.
public class MyForgeEventHandler {
@SubscribeEvent
public void pickupItem(EntityItemPickupEvent event) {
System.out.println("Item picked up!");
}
}
To register this event handler, use MinecraftForge.EVENT_BUS.register(...) and pass it an instance of the class the event handler is within. If you want to register this handler to the mod specific event bus, you should use FMLJavaModLoadingContext.get().getModEventBus().register(...) instead.
Static Annotated Event Handlers
An event handler may also be static. The handling method is still annotated with @SubscribeEvent. The only difference from an instance handler is that it is also marked static. In order to register a static event handler, an instance of the class won’t do. The Class itself has to be passed in. An example:
public class MyStaticForgeEventHandler {
@SubscribeEvent
public static void arrowNocked(ArrowNockEvent event) {
System.out.println("Arrow nocked!");
}
}
which must be registered like this: MinecraftForge.EVENT_BUS.register(MyStaticForgeEventHandler.class).
Automatically Registering Static Event Handlers
A class may be annotated with the @Mod$EventBusSubscriber annotation. Such a class is automatically registered to MinecraftForge#EVENT_BUS when the @Mod class itself is constructed. This is essentially equivalent to adding MinecraftForge.EVENT_BUS.register(AnnotatedClass.class); at the end of the @Mod class’s constructor.
You can pass the bus you want to listen to the @Mod$EventBusSubscriber annotation. It is recommended you also specify the mod id, since the annotation process may not be able to figure it out, and the bus you are registering to, since it serves as a reminder to make sure you are on the correct one. You can also specify the Dists or physical sides to load this event subscriber on. This can be used to not load client specific event subscribers on the dedicated server.
An example for a static event listener listening to RenderLevelStageEvent which will only be called on the client:
@Mod.EventBusSubscriber(modid = "mymod", bus = Bus.FORGE, value = Dist.CLIENT)
public class MyStaticClientOnlyEventHandler {
@SubscribeEvent
public static void drawLast(RenderLevelStageEvent event) {
System.out.println("Drawing!");
}
}
Note
This does not register an instance of the class; it registers the class itself (i.e. the event handling methods must be static).
Canceling
If an event can be canceled, it will be marked with the @Cancelable annotation, and the method Event#isCancelable() will return true. The cancel state of a cancelable event may be modified by calling Event#setCanceled(boolean canceled), wherein passing the boolean value true is interpreted as canceling the event, and passing the boolean value false is interpreted as “un-canceling” the event. However, if the event cannot be canceled (as defined by Event#isCancelable()), an UnsupportedOperationException will be thrown regardless of the passed boolean value, since the cancel state of a non-cancelable event event is considered immutable.
Important
Not all events can be canceled! Attempting to cancel an event that is not cancelable will result in an unchecked UnsupportedOperationException being thrown, which is expected to result in the game crashing! Always check that an event can be canceled using Event#isCancelable() before attempting to cancel it!
Results
Some events have an Event$Result. A result can be one of three things: DENY which stops the event, DEFAULT which uses the Vanilla behavior, and ALLOW which forces the action to take place, regardless if it would have originally. The result of an event can be set by calling #setResult with an Event$Result on the event. Not all events have results; an event with a result will be annotated with @HasResult.
Important
Different events may use results in different ways, refer to the event’s JavaDoc before using the result.
Priority
Event handler methods (marked with @SubscribeEvent) have a priority. You can set the priority of an event handler method by setting the priority value of the annotation. The priority can be any value of the EventPriority enum (HIGHEST, HIGH, NORMAL, LOW, LOWEST and MONITOR). Event handlers with priority HIGHEST are executed first and from there in descending order until MONITOR events which are executed last.
The MONITOR priority is a special priority that runs after LOWEST but does not affect the result of the event. Attempting to cancel or otherwise mutate the event object during the MONITOR phase may cause an exception to be thrown. The MONITOR priority is useful for logging and other read-only actions that need to run last in a state that’s guaranteed to be final by the time your listener is called.
Sub Events
Many events have different variations of themselves. These can be different but all based around one common factor (e.g. PlayerEvent) or can be an event that has multiple phases (e.g. PotionBrewEvent). Take note that if you listen to the parent event class, you will receive calls to your method for all subclasses.
Mod Event Bus
The mod event bus is primarily used for listening to lifecycle events in which mods should initialize. Each event on the mod bus is required to implement IModBusEvent. Many of these events are also ran in parallel so mods can be initialized at the same time. This does mean you can’t directly execute code from other mods in these events. Use the InterModComms system for that.
These are the four most commonly used lifecycle events that are called during mod initialization on the mod event bus:
FMLCommonSetupEventFMLClientSetupEvent&FMLDedicatedServerSetupEventInterModEnqueueEventInterModProcessEvent
Note
The FMLClientSetupEvent and FMLDedicatedServerSetupEvent are only called on their respective distribution.
These four lifecycle events are all ran in parallel since they all are a subclass of ParallelDispatchEvent. If you want to run run code on the main thread during any ParallelDispatchEvent, you can use the #enqueueWork to do so.
Next to the lifecycle events, there are a few miscellaneous events that are fired on the mod event bus where you can register, set up, or initialize various things. Most of these events are not ran in parallel in contrast to the lifecycle events. A few examples:
RegisterColorHandlersEventModelEvent$BakingCompletedTextureStitchEventRegisterEvent
A good rule of thumb: events are fired on the mod event bus when they should be handled during initialization of a mod.
Core Concepts: Mod Lifecycle
Mod Lifecycle
During the mod loading process, the various lifecycle events are fired on the mod-specific event bus. Many actions are performed during these events, such as registering objects, preparing for data generation, or communicating with other mods.
Event listeners should be registered either using @EventBusSubscriber(bus = Bus.MOD) or in the mod constructor:
@Mod.EventBusSubscriber(modid = "mymod", bus = Mod.EventBusSubscriber.Bus.MOD)
public class MyModEventSubscriber {
@SubscribeEvent
static void onCommonSetup(FMLCommonSetupEvent event) { ... }
}
@Mod("mymod")
public class MyMod {
public MyMod(FMLModLoadingContext context) {
context.getModEventBus().addListener(this::onCommonSetup);
}
private void onCommonSetup(FMLCommonSetupEvent event) { ... }
}
Warning
Most of the lifecycle events are fired in parallel: all mods will concurrently receive the same event.
Mods must take care to be thread-safe, like when calling other mods’ APIs or accessing vanilla systems. Defer code for later execution via ParallelDispatchEvent#enqueueWork.
Registry Events
The registry events are fired after the mod instance construction. There are three: NewRegistryEvent, DataPackRegistryEvent$NewRegistry and RegisterEvent. These events are fired synchronously during mod loading.
NewRegistryEvent allows modders to register their own custom registries, using the RegistryBuilder class.
DataPackRegistryEvent$NewRegistry allows modders to register custom datapack registries by providing a Codec to encode and decode the object from JSON.
RegisterEvent is for registering objects into the registries. The event is fired for each registry.
Note
You should prefer using DeferredRegister over the registry events wherever possible. DeferredRegister handles timing on your behalf and is less error-prone.
Data Generation
If the game is setup to run data generators, then the GatherDataEvent will be the last event to fire. This event is for registering mods’ data providers to their associated data generator. This event is also fired synchronously.
Common Setup
FMLCommonSetupEvent is for actions that are common to both physical client and server, such as registering capabilities.
Sided Setup
The sided-setup events are fired on their respective physical sides: FMLClientSetupEvent on the physical client, and FMLDedicatedServerSetupEvent for the dedicated server. This is where physical side-specific initialization should occur, such as registering client-side key bindings.
InterModComms
This is where messages can be sent to mods for cross-mod compatibility. There are two events: InterModEnqueueEvent and InterModProcessEvent.
InterModComms is the class responsible for holding messages for mods. The methods are safe to call during the lifecycle events, as it is backed by a ConcurrentMap.
During the InterModEnqueueEvent, use InterModComms#sendTo to send messages to different mods. These methods take in the mod id that will be sent the message, the key associated with the message data, and a supplier holding the message data. Additionally, the sender of the message can also be specified, but by default it will be the mod id of the caller.
Then during the InterModProcessEvent, use InterModComms#getMessages to get a stream of all received messages. The mod id supplied will almost always be the mod id of the mod the method is called on. Additionally, a predicate can be specified to filter out the message keys. This will return a stream of IMCMessages which hold the sender of the data, the receiver of the data, the data key, and the supplied data itself.
Note
There are two other lifecycle events: FMLConstructModEvent, fired directly after mod instance construction but before the RegisterEvent, and FMLLoadCompleteEvent, fired after the InterModComms events, for when the mod loading process is complete.
Core Concepts: Resources
Resources
A resource is extra data used by the game, and is stored in a data file, instead of being in the code.
Minecraft has two primary resource systems active: one on the logical client used for visuals such as models, textures, and localization called assets, and one on the logical server used for gameplay such as recipes and loot tables called data.
Resource packs control the former, while Datapacks control the latter.
In the default mod development kit, assets and data directories are located under the src/main/resources directory of the project.
When multiple resource packs or data packs are enabled, they are merged. Generally, files from packs at the top of the stack override those below; however, for certain files, such as localization files and tags, data is actually merged contentwise. Mods define resource and data packs in their resources directories, but they are seen as subsets of the “Mod Resources” pack. Mod resource packs cannot be disabled, but they can be overridden by other resource packs. Mod datapacks can be disabled with the vanilla /datapack command.
All resources should have snake case paths and filenames (lowercase, using “_” for word boundaries), which is enforced in 1.11 and above.
ResourceLocation
Minecraft identifies resources using ResourceLocations. A ResourceLocation contains two parts: a namespace and a path. It generally points to the resource at assets/<namespace>/<ctx>/<path>, where ctx is a context-specific path fragment that depends on how the ResourceLocation is being used. When a ResourceLocation is written/read as from a string, it is seen as <namespace>:<path>. If the namespace and the colon are left out, then when the string is read into an ResourceLocation the namespace will always default to "minecraft". A mod should put its resources into a namespace with the same name as its mod id (e.g. a mod with the id examplemod should place its resources in assets/examplemod and data/examplemod respectively, and ResourceLocations pointing to those files would look like examplemod:<path>.). This is not a requirement, and in some cases it can be desirable to use a different (or even more than one) namespace. ResourceLocations are used outside the resource system, too, as they happen to be a great way to uniquely identify objects (e.g. registries).
Core Concepts: Internationalization
Internationalization and Localization
Internationalization, i18n for short, is a way of designing code so that it requires no changes to be adapted for various languages. Localization is the process of adapting displayed text to the user’s language.
I18n is implemented using translation keys. A translation key is a string that identifies a piece of displayable text in no specific language. For example, block.minecraft.dirt is the translation key referring to the name of the Dirt block. This way, displayable text may be referenced with no concern for a specific language. The code requires no changes to be adapted in a new language.
Localization will happen in the game’s locale. In a Minecraft client the locale is specified by the language settings. On a dedicated server, the only supported locale is en_us. A list of available locales can be found on the Minecraft Wiki.
Language files
Language files are located by assets/[namespace]/lang/[locale].json (e.g. all US English translations provided by examplemod would be within assets/examplemod/lang/en_us.json). The file format is simply a json map from translation keys to values. The file must be encoded in UTF-8. Old .lang files can be converted to json using a converter.
{
"item.examplemod.example_item": "Example Item Name",
"block.examplemod.example_block": "Example Block Name",
"commands.examplemod.examplecommand.error": "Example Command Errored!"
}
Usage with Blocks and Items
Block, Item and a few other Minecraft classes have built-in translation keys used to display their names. These translation keys are specified by overriding #getDescriptionId. Item also has #getDescriptionId(ItemStack) which can be overridden to provide different translation keys depending on ItemStack NBT.
By default, #getDescriptionId will return block. or item. prepended to the registry name of the block or item, with the colon replaced by a dot. BlockItems override this method to take their corresponding Block’s translation key by default. For example, an item with ID examplemod:example_item effectively requires the following line in a language file:
{
"item.examplemod.example_item": "Example Item Name"
}
Note
The only purpose of a translation key is internationalization. Do not use them for logic. Use registry names instead.
Localization methods
Warning
A common issue is having the server localize for clients. The server can only localize in its own locale, which does not necessarily match the locale of connected clients.
To respect the language settings of clients, the server should have clients localize text in their own locale using TranslatableComponent or other methods preserving the language neutral translation keys.
net.minecraft.client.resources.language.I18n (client only)
This I18n class can only be found on a Minecraft client! It is intended to be used by code that only runs on the client. Attempts to use this on a server will throw exceptions and crash.
get(String, Object...)localizes in the client’s locale with formatting. The first parameter is a translation key, and the rest are formatting arguments forString.format(String, Object...).
TranslatableContents
TranslatableContents is a ComponentContents that is localized and formatted lazily. It is very useful when sending messages to players because it will be automatically localized in their own locale.
The first parameter of the TranslatableContents(String, Object...) constructor is a translation key, and the rest are used for formatting. The only supported format specifiers are %s and %1$s, %2$s, %3$s etc. Formatting arguments may be Components that will be inserted into the resulting formatted text with all their attributes preserved.
A MutableComponent can be created using Component#translatable by passing in the TranslatableContents’s parameters. It can also be created using MutableComponent#create by passing in the ComponentContents itself.
TextComponentHelper
createComponentTranslation(CommandSource, String, Object...)creates a localized and formattedMutableComponentdepending on a receiver. The localization and formatting is done eagerly if the receiver is a vanilla client. If not, the localization and formatting is done lazily with aComponentcontainingTranslatableContents. This is only useful if the server should allow vanilla clients to connect.
Blocks: Introduction
Blocks
Blocks are, obviously, essential to the Minecraft world. They make up all of the terrain, structures, and machines. Chances are if you are interested in making a mod, then you will want to add some blocks. This page will guide you through the creation of blocks, and some of the things you can do with them.
Creating a Block
Basic Blocks
For simple blocks, which need no special functionality (think cobblestone, wooden planks, etc.), a custom class is not necessary. You can create a block by instantiating the Block class with a BlockBehaviour$Properties object. This BlockBehaviour$Properties object can be made using BlockBehaviour$Properties#of, and it can be customized by calling its methods. For instance:
strength- The hardness controls the time it takes to break the block. It is an arbitrary value. For reference, stone has a hardness of 1.5, and dirt 0.5. If the block should be unbreakable a hardness of -1.0 should be used, see the definition ofBlocks#BEDROCKas an example. The resistance controls the explosion resistance of the block. For reference, stone has a resistance of 6.0, and dirt 0.5.sound- Controls the sound the block makes when it is punched, broken, or placed. Requires aSoundTypeargument, see the sounds page for more details.lightLevel- Controls the light emission of the block. Takes a function with aBlockStateparameter that returns a value from zero to fifteen.friction- Controls how slippery the block is. For reference, ice has a slipperiness of 0.98.
All these methods are chainable which means you can call them in series. See the Blocks class for examples of this.
Note
Blocks have no setter for their CreativeModeTab. This is handled by the BuildCreativeModeTabContentsEvent if the block has an associated item (e.g. BlockItem). Furthermore, there is no setter for translation key of the block as it is generated from the registry name via Block#getDescriptionId.
Advanced Blocks
Of course, the above only allows for extremely basic blocks. If you want to add functionality, like player interaction, a custom class is required. However, the Block class has many methods and unfortunately not every single one can be documented here. See the rest of the pages in this section for things you can do with blocks.
Registering a Block
Blocks must be registered to function.
Important
A block in the level and a “block” in an inventory are very different things. A block in the level is represented by an BlockState, and its behavior defined by an instance of Block. Meanwhile, an item in an inventory is an ItemStack, controlled by an Item. As a bridge between the different worlds of Block and Item, there exists the class BlockItem. BlockItem is a subclass of Item that has a field block that holds a reference to the Block it represents. BlockItem defines some of the behavior of a “block” as an item, like how a right click places the block. It’s possible to have a Block without an BlockItem. (E.g. minecraft:water exists a block, but not an item. It is therefore impossible to hold it in an inventory as one.)
When a block is registered, only a block is registered. The block does not automatically have an BlockItem. To create a basic BlockItem for a block, one should set the registry name of the BlockItem to that of its Block. Custom subclasses of BlockItem may be used as well. Once an BlockItem has been registered for a block, Block#asItem can be used to retrieve it. Block#asItem will return Items#AIR if there is no BlockItem for the Block, so if you are not certain that there is an BlockItem for the Block you are using, check for if Block#asItem returns Items#AIR.
Optionally Registering Blocks
In the past there have been several mods that have allowed users to disable blocks/items in a configuration file. However, you shouldn’t do this. There is no limit on the amount of blocks that can be register, so register all blocks in your mod! If you want a block to be disabled through a configuration file, you should disable the crafting recipe. If you would like to disable the block in the creative tab, use a FeatureFlag when building the contents within BuildCreativeModeTabContentsEvent.
Further Reading
For information about block properties, such as those used for vanilla blocks like fences, walls, and many more, see the section on blockstates.
Blocks: Block States
Block States
Legacy Behavior
In Minecraft 1.7 and previous versions, blocks which need to store placement or state data that did not have BlockEntities used metadata. Metadata was an extra number stored with the block, allowing different rotations, facings, or even completely separate behaviors within a block.
However, the metadata system was confusing and limited, since it was stored as only a number alongside the block ID, and had no meaning except what was commented in the code. For example, to implement a block that can face a direction and be on either the upper or lower half of a block space (such as a stair):
switch (meta) {
case 0: { ... } // south and on the lower half of the block
case 1: { ... } // south on the upper side of the block
case 2: { ... } // north and on the lower half of the block
case 3: { ... } // north and on the upper half of the block
// ... etc. ...
}
Because the numbers carry no meaning by themselves, no one could know what they represent unless they had access to the source code and comments.
Introduction of States
In Minecraft 1.8 and above, the metadata system, along with the block ID system, was deprecated and eventually replaced with the block state system. The block state system abstracts out the details of the block’s properties from the other behaviors of the block.
Each property of a block is described by an instance of Property<?>. Examples of block properties include instruments (EnumProperty<NoteBlockInstrument>), facing (DirectionProperty), poweredness (Property<Boolean>), etc. Each property has the value of the type T parametrized by Property<T>.
A unique pair can be constructed from the Block and a map of the Property<?> to their associated values. This unique pair is called a BlockState.
The previous system of meaningless metadata values were replaced by a system of block properties, which are easier to interpret and deal with. Previously, a stone button which is facing east and is powered or held down was represented by “minecraft:stone_button with metadata 9”. Now, this is represented by “minecraft:stone_button[facing=east,powered=true]”.
Proper Usage of Block States
The BlockState system is a flexible and powerful system, but it also has limitations. BlockStates are immutable, and all combinations of their properties are generated on startup of the game. This means that having a BlockState with many properties and possible values will slow down the loading of the game, and befuddle anyone trying to make sense of your block logic.
Not all blocks and situations require the usage of BlockState; only the most basic properties of a block should be put into a BlockState, and any other situation is better off with having a BlockEntity or being a separate Block. Always consider if you actually need to use blockstates for your purposes.
Note
A good rule of thumb is: if it has a different name, it should be a separate block.
An example is making chair blocks: the direction of the chair should be a property, while the different types of wood should be separated into different blocks.
An “Oak Chair” facing east (oak_chair[facing=east]) is different from a “Spruce Chair” facing west (spruce_chair[facing=west]).
Implementing Block States
In your Block class, create or reference static final Property<?> objects for every property that your Block has. You are free to make your own Property<?> implementations, but the means to do that are not covered in this article. The vanilla code provides several convenience implementations:
IntegerProperty- Implements
Property<Integer>. Defines a property that holds an integer value. - Created by calling
IntegerProperty#create(String propertyName, int minimum, int maximum). BooleanProperty- Implements
Property<Boolean>. Defines a property that holds atrueorfalsevalue. - Created by calling
BooleanProperty#create(String propertyName). EnumProperty<E extends Enum<E>>- Implements
Property<E>. Defines a property that can take on the values of an Enum class. - Created by calling
EnumProperty#create(String propertyName, Class<E> enumClass). - It is also possible to use only a subset of the Enum values (e.g. 4 out of 16
DyeColors). See the overloads ofEnumProperty#create. DirectionProperty- This is a convenience implementation of
EnumProperty<Direction> - Several convenience predicates are also provided. For example, to get a property that represents the cardinal directions, call
DirectionProperty.create("<name>", Direction.Plane.HORIZONTAL); to get the X directions,DirectionProperty.create("<name>", Direction.Axis.X).
The class BlockStateProperties contains shared vanilla properties which should be used or referenced whenever possible, in place of creating your own properties.
When you have your desired Property<> objects, override Block#createBlockStateDefinition(StateDefinition$Builder) in your Block class. In that method, call StateDefinition$Builder#add(...); with the parameters as every Property<?> you wish the block to have.
Every block will also have a “default” state that is automatically chosen for you. You can change this “default” state by calling the Block#registerDefaultState(BlockState) method from your constructor. When your block is placed it will become this “default” state. An example from DoorBlock:
this.registerDefaultState(
this.stateDefinition.any()
.setValue(FACING, Direction.NORTH)
.setValue(OPEN, false)
.setValue(HINGE, DoorHingeSide.LEFT)
.setValue(POWERED, false)
.setValue(HALF, DoubleBlockHalf.LOWER)
);
If you wish to change what BlockState is used when placing your block, you can overwrite Block#getStateForPlacement(BlockPlaceContext). This can be used to, for example, set the direction of your block depending on where the player is standing when they place it.
Because BlockStates are immutable, and all combinations of their properties are generated on startup of the game, calling BlockState#setValue(Property<T>, T) will simply go to the Block’s StateHolder and request the BlockState with the set of values you want.
Because all possible BlockStates are generated at startup, you are free and encouraged to use the reference equality operator (==) to check if two BlockStates are equal.
Using BlockState’s
You can get the value of a property by calling BlockState#getValue(Property<?>), passing it the property you want to get the value of.
If you want to get a BlockState with a different set of values, simply call BlockState#setValue(Property<T>, T) with the property and its value.
You can get and place BlockState’s in the level using Level#setBlockAndUpdate(BlockPos, BlockState) and Level#getBlockState(BlockPos). If you are placing a Block, call Block#defaultBlockState() to get the “default” state, and use subsequent calls to BlockState#setValue(Property<T>, T) as stated above to achieve the desired state.
Items: Introduction
Items
Along with blocks, items are a key component of most mods. While blocks make up the level around you, items exist within inventories.
Creating an Item
Basic Items
Basic items that need no special functionality (think sticks or sugar) do not need custom classes. You can create an item by instantiating the Item class with an Item$Properties object. This Item$Properties object can be made via the constructor and customized by calling its methods. For instance:
| Method | Description |
|---|---|
requiredFeatures |
Sets the required FeatureFlags needed to see this item in the CreativeModeTab it is added to. |
durability |
Sets the maximum damage value for this item. If it is over 0, two item properties “damaged” and “damage” are added. |
stacksTo |
Sets the maximum stack size. You cannot have an item that is both damageable and stackable. |
setNoRepair |
Makes this item impossible to repair, even if it is damageable. |
craftRemainder |
Sets this item’s container item, the way that lava buckets give you back an empty bucket when they are used. |
The above methods are chainable, meaning they return this to facilitate calling them in series.
Advanced Items
Setting the properties of an item as above only works for simple items. If you want more complicated items, you should subclass Item and override its methods.
Creative Tabs
An item can be added to a CreativeModeTab via BuildCreativeModeTabContentsEvent on the mod event bus. An item(s) can be added without any additional configurations via #accept.
// Registered on the MOD event bus
// Assume we have RegistryObject<Item> and RegistryObject<Block> called ITEM and BLOCK
@SubscribeEvent
public void buildContents(BuildCreativeModeTabContentsEvent event) {
// Add to ingredients tab
if (event.getTabKey() == CreativeModeTabs.INGREDIENTS) {
event.accept(ITEM);
event.accept(BLOCK); // Takes in an ItemLike, assumes block has registered item
}
}
You can also enable or disable items being added through a FeatureFlag in the FeatureFlagSet or a boolean determining whether the player has permissions to see operator creative tabs.
Custom Creative Tabs
A custom CreativeModeTab must be registered. The builder can be created via CreativeModeTab#builder. The tab can set the title, icon, default items, and a number of other properties. In addition, Forge provides additional methods to customize the tab’s image, label and slot colors, where the tab should be ordered, etc.
// Assume we have a DeferredRegister<CreativeModeTab> called REGISTRAR
// Assume we have RegistryObject<Item> and RegistryObject<Block> called ITEM and BLOCK
public static final RegistryObject<CreativeModeTab> EXAMPLE_TAB = REGISTRAR.register("example", () -> CreativeModeTab.builder()
// Set name of tab to display
.title(Component.translatable("item_group." + MOD_ID + ".example"))
// Set icon of creative tab
.icon(() -> new ItemStack(ITEM.get()))
// Add default items to tab
.displayItems((params, output) -> {
output.accept(ITEM.get());
output.accept(BLOCK.get());
})
.build()
);
Registering an Item
Items must be registered to function.
Items: BlockEntityWithoutLevelRenderer
BlockEntityWithoutLevelRenderer
BlockEntityWithoutLevelRenderer is a method to handle dynamic rendering on items. This system is much simpler than the old ItemStack system, which required a BlockEntity, and did not allow access to the ItemStack.
Using BlockEntityWithoutLevelRenderer
BlockEntityWithoutLevelRenderer allows you to render your item using public void renderByItem(ItemStack itemStack, ItemDisplayContext ctx, PoseStack poseStack, MultiBufferSource bufferSource, int combinedLight, int combinedOverlay).
In order to use an BEWLR, the Item must first satisfy the condition that its model returns true for BakedModel#isCustomRenderer. If it does not have one, it will use the default ItemRenderer#getBlockEntityRenderer. Once that returns true, the Item’s BEWLR will be accessed for rendering.
Note
Blocks also render using a BEWLR if Block#getRenderShape is set to RenderShape#ENTITYBLOCK_ANIMATED.
To set the BEWLR for an Item, an anonymous instance of IClientItemExtensions must be consumed within Item#initializeClient. Within the anonymous instance, IClientItemExtensions#getCustomRenderer should be overridden to return the instance of your BEWLR:
// In your item class
@Override
public void initializeClient(Consumer<IClientItemExtensions> consumer) {
consumer.accept(new IClientItemExtensions() {
@Override
public BlockEntityWithoutLevelRenderer getCustomRenderer() {
return myBEWLRInstance;
}
});
}
Important
Each mod should only have one instance of a custom BEWLR.
That is it, no additional setup is necessary to use a BEWLR.
Block Entities: Introduction
BlockEntities
BlockEntities are like simplified Entities that are bound to a Block.
They are used to store dynamic data, execute tick based tasks, and dynamic rendering.
Some examples from vanilla Minecraft would be handling of inventories on chests, smelting logic on furnaces, or area effects on beacons.
More advanced examples exist in mods, such as quarries, sorting machines, pipes, and displays.
Note
BlockEntities aren’t a solution for everything and they can cause lag when used wrongly.
When possible, try to avoid them.
Registering
Block Entities are created and removed dynamically and as such are not registry objects on their own.
In order to create a BlockEntity, you need to extend the BlockEntity class. As such, another object is registered instead to easily create and refer to the type of the dynamic object. For a BlockEntity, these are known as BlockEntityTypes.
A BlockEntityType can be registered like any other registry object. To construct a BlockEntityType, its builder form can be used via BlockEntityType$Builder#of. This takes in two arguments: a BlockEntityType$BlockEntitySupplier which takes in a BlockPos and BlockState to create a new instance of the associated BlockEntity, and a varargs of Blocks which this BlockEntity can be attached to. Building the BlockEntityType is done by calling BlockEntityType$Builder#build. This takes in a Type which represents the type-safe reference used to refer to this registry object in a DataFixer. Since DataFixers are an optional system to use for mods, this can be passed as null.
// For some DeferredRegister<BlockEntityType<?>> REGISTER
public static final RegistryObject<BlockEntityType<MyBE>> MY_BE = REGISTER.register("mybe", () -> BlockEntityType.Builder.of(MyBE::new, validBlocks).build(null));
// In MyBE, a BlockEntity subclass
public MyBE(BlockPos pos, BlockState state) {
super(MY_BE.get(), pos, state);
}
Creating a BlockEntity
To create a BlockEntity and attach it to a Block, the EntityBlock interface must be implemented on your Block subclass. The method EntityBlock#newBlockEntity(BlockPos, BlockState) must be implemented and return a new instance of your BlockEntity.
Storing Data within your BlockEntity
In order to save data, override the following two methods:
BlockEntity#saveAdditional(CompoundTag tag)
BlockEntity#load(CompoundTag tag)
These methods are called whenever the LevelChunk containing the BlockEntity gets loaded from/saved to a tag.
Use them to read and write to the fields in your block entity class.
Note
Whenever your data changes, you need to call `BlockEntity#setChanged`; otherwise, the `LevelChunk` containing your `BlockEntity` might be skipped while the level is saved.
Important
It is important that you call the `super` methods!
The tag names id, x, y, z, ForgeData and ForgeCaps are reserved by the super methods.
Ticking BlockEntities
If you need a ticking BlockEntity, for example to keep track of the progress during a smelting process, another method must be implemented and overridden within EntityBlock: EntityBlock#getTicker(Level, BlockState, BlockEntityType). This can implement different tickers depending on which logical side the user is on, or just implement one general ticker. In either case, a BlockEntityTicker must be returned. Since this is a functional interface, it can just take in a method representing the ticker instead:
// Inside some Block subclass
@Nullable
@Override
public <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level level, BlockState state, BlockEntityType<T> type) {
return type == MyBlockEntityTypes.MYBE.get() ? MyBlockEntity::tick : null;
}
// Inside MyBlockEntity
public static void tick(Level level, BlockPos pos, BlockState state, MyBlockEntity blockEntity) {
// Do stuff
}
Note
This method is called each tick; therefore, you should avoid having complicated calculations in here. If possible, you should make more complex calculations every X ticks. (The amount of ticks in a second may be lower then 20 (twenty) but won’t be higher)
Synchronizing the Data to the Client
There are three ways of syncing data to the client: synchronizing on chunk load, on block updates, and with a custom network message.
Synchronizing on LevelChunk Load
For this you need to override
BlockEntity#getUpdateTag()
IForgeBlockEntity#handleUpdateTag(CompoundTag tag)
Again, this is pretty simple, the first method collects the data that should be sent to the client,
while the second one processes that data. If your BlockEntity doesn’t contain much data, you might be able to use the methods out of the Storing Data within your BlockEntity section.
Important
Synchronizing excessive/useless data for block entities can lead to network congestion. You should optimize your network usage by sending only the information the client needs when the client needs it. For instance, it is more often than not unnecessary to send the inventory of a block entity in the update tag, as this can be synchronized via its AbstractContainerMenu.
Synchronizing on Block Update
This method is a bit more complicated, but again you just need to override two or three methods. Here is a tiny example implementation of it:
@Override
public CompoundTag getUpdateTag() {
CompoundTag tag = new CompoundTag();
//Write your data into the tag
return tag;
}
@Override
public Packet<ClientGamePacketListener> getUpdatePacket() {
// Will get tag from #getUpdateTag
return ClientboundBlockEntityDataPacket.create(this);
}
// Can override IForgeBlockEntity#onDataPacket. By default, this will defer to the #load.
The static constructors ClientboundBlockEntityDataPacket#create takes:
- The
BlockEntity. - An optional function to get the
CompoundTagfrom theBlockEntity. By default, this usesBlockEntity#getUpdateTag.
Now, to send the packet, an update notification must be given on the server.
Level#sendBlockUpdated(BlockPos pos, BlockState oldState, BlockState newState, int flags)
The pos should be your BlockEntity’s position.
For oldState and newState, you can pass the current BlockState at that position.
flags is a bitmask that should contain 2, which will sync the changes to the client. See Block for more info as well as the rest of the flags. The flag 2 is equivalent to Block#UPDATE_CLIENTS.
Synchronizing Using a Custom Network Message
This way of synchronizing is probably the most complicated but is usually the most optimized,
as you can make sure that only the data you need to be synchronized is actually synchronized.
You should first check out the Networking section and especially SimpleImpl before attempting this.
Once you’ve created your custom network message, you can send it to all users that have the BlockEntity loaded with SimpleChannel#send(PacketDistributor$PacketTarget, MSG).
Warning
It is important that you do safety checks, the BlockEntity might already be destroyed/replaced when the message arrives at the player! You should also check if the chunk is loaded (Level#hasChunkAt(BlockPos)).
Block Entities: BlockEntityRenderer
BlockEntityRenderer
A BlockEntityRenderer or BER is used to render blocks in a way that cannot be represented with a static baked model (JSON, OBJ, B3D, others). A block entity renderer requires the block to have a BlockEntity.
Creating a BER
To create a BER, create a class that inherits from BlockEntityRenderer. It takes a generic argument specifying the block’s BlockEntity class. The generic argument is used in the BER’s render method.
Only one BER exists for a given BlockEntityType. Therefore, values that are specific to a single instance in the level should be stored in the block entity being passed to the renderer rather than in the BER itself. For example, an integer that increments every frame, if stored in the BER, will increment every frame for every block entity of this type in the level.
render
This method is called every frame in order to render the block entity.
Parameters
blockEntity: This is the instance of the block entity being rendered.partialTick: The amount of time, in fractions of a tick, that has passed since the last full tick.poseStack: A stack holding four-dimensional matrix entries offset to the current position of the block entity.bufferSource: A rendering buffer able to access a vertex consumer.combinedLight: An integer of the current light value on the block entity.combinedOverlay: An integer set to the current overlay of the block entity, usuallyOverlayTexture#NO_OVERLAYor 655,360.
Registering a BER
In order to register a BER, you must subscribe to the EntityRenderersEvent$RegisterRenderers event on the mod event bus and call #registerBlockEntityRenderer.
Rendering: Item Overrides
ItemOverrides
ItemOverrides provides a way for an BakedModel to process the state of an ItemStack and return a new BakedModel; thereafter, the returned model replaces the old one. ItemOverrides represents an arbitrary function (BakedModel, ItemStack, ClientLevel, LivingEntity, int) → BakedModel, making it useful for dynamic models. In vanilla, it is used to implement item property overrides.
ItemOverrides()
Given a list of ItemOverrides, the constructor copies and bakes the list. The baked overrides may be accessed with #getOverrides.
resolve
This takes an BakedModel, an ItemStack, a ClientLevel, a LivingEntity, and an int to produce another BakedModel to use for rendering. This is where models can handle the state of their items.
This should not mutate the level.
getOverrides
Returns an immutable list containing all the BakedOverrides used by this ItemOverrides. If none are applicable, this returns the empty list.
BakedOverride
This class represents a vanilla item override, which holds several ItemOverrides$PropertyMatcher for the properties on an item and a model to use in case those matchers are satisfied. They are the objects in the overrides array of a vanilla item JSON model:
{
// Inside a vanilla JSON item model
"overrides": [
{
// This is an ItemOverride
"predicate": {
// This is the Map<ResourceLocation, Float>, containing the names of properties and their minimum values
"example1:prop": 0.5
},
// This is the 'location', or target model, of the override, which is used if the predicate above matches
"model": "example1:item/model"
},
{
// This is another ItemOverride
"predicate": {
"example2:prop": 1
},
"model": "example2:item/model"
}
]
}
Resources: Item Properties
Item Properties
Item properties are a way for the “properties” of items to be exposed to the model system. An example is the bow, where the most important property is how far the bow has been pulled. This information is then used to choose a model for the bow, creating an animation for pulling it.
An item property assigns a certain float value to every ItemStack it is registered for, and vanilla item model definitions can use these values to define “overrides”, where an item defaults to a certain model, but if an override matches, it overrides the model and uses another. They are useful mainly because they are continuous. For example, bows use item properties to define their pull animation. The item models are decided by the ‘float’ number predicates, it is not limited but generally between 0.0F and 1.0F. This allows resource packs to add as many models as they want for the bow pulling animation along that spectrum, instead of being stuck with four “slots” for their models in the animation. The same is true of the compass and clock.
Adding Properties to Items
ItemProperties#register is used to add a property to a certain item. The Item parameter is the item the property is being attached to (e.g. ExampleItems#APPLE). The ResourceLocation parameter is the name given to the property (e.g. new ResourceLocation("pull")). The ItemPropertyFunction is a functional interface that takes the ItemStack, the ClientLevel it is in (may be null), the LivingEntity that holds it (may be null), and the int containing the id of the holding entity (may be 0), returning the float value for the property. For modded item properties, it is recommended that the mod id of the mod is used as the namespace (e.g. examplemod:property and not just property, as that really means minecraft:property). These should be done in FMLClientSetupEvent.
There’s also another method ItemProperties#registerGeneric that is used to add properties to all items, and it does not take Item as its parameter since all items will apply this property.
Important
Use FMLClientSetupEvent#enqueueWork to proceed with the tasks, since the data structures in ItemProperties are not thread-safe.
Note
ItemPropertyFunction is deprecated by Mojang in favor of using the subinterface ClampedItemPropertyFunction which clamps the result between 0 and 1.
Using Overrides
The format of an override can be seen on the wiki, and a good example can be found in model/item/bow.json. For reference, here is a hypothetical example of an item with an examplemod:power property. If the values have no match, the default is the current model, but if there are multiple matches, the last match in the list will be selected.
Important
A predicate applies to all values greater than or equal to the given value.
{
"parent": "item/generated",
"textures": {
// Default
"layer0": "examplemod:items/example_partial"
},
"overrides": [
{
// power >= .75
"predicate": {
"examplemod:power": 0.75
},
"model": "examplemod:item/example_powered"
}
]
}
And here is a hypothetical snippet from the supporting code. Unlike the older versions (lower than 1.16.x), this needs to be done on client side only because ItemProperties does not exist on server.
private void setup(final FMLClientSetupEvent event)
{
event.enqueueWork(() ->
{
ItemProperties.register(ExampleItems.APPLE,
ResourceLocation.fromNamespaceAndPath(ExampleMod.MODID, "pulling"), (stack, level, living, id) -> {
return living != null && living.isUsingItem() && living.getUseItem() == stack ? 1.0F : 0.0F;
});
});
}
Resources: Recipes Intro
Recipes
Recipes are a way to transform some number of objects into other objects within a Minecraft world. Although the vanilla system deals purely with item transformations, the system as a whole can be expanded to use any object the programmer creates.
Data-Driven Recipes
Most recipe implementations within vanilla are data driven via JSON. This means that a mod is not necessary to create a new recipe, only a Data pack. A full list on how to create and put these recipes within the mod’s resources folder can be found on the Minecraft Wiki.
A recipe can be obtained within the Recipe Book as a reward for completing an advancement. Recipe advancements always have minecraft:recipes/root as their parent, to not to appear on the advancement screen. The default criteria to gain the recipe advancement is a check if the user has unlocked the recipe from using it once or receiving it through a command like /recipe:
// Within some recipe advancement json
"has_the_recipe": { // Criteria label
// Succeeds if examplemod:example_recipe is used
"trigger": "minecraft:recipe_unlocked",
"conditions": {
"recipe": "examplemod:example_recipe"
}
}
//...
"requirements": [
[
"has_the_recipe"
// ... Other criteria labels to be ORed against to unlock recipe
]
]
Data-driven recipes and their unlocking advancement can be generated via RecipeProvider.
Recipe Manager
Recipes are loaded and stored via the RecipeManager. Any operations relating to getting available recipe(s) are handled by this manager. There are two important methods to know of:
| Method | Description |
|---|---|
getRecipeFor |
Gets the first recipe that matches the current input. |
getRecipesFor |
Gets all recipes that match the current input. |
Each method takes in a RecipeType, which denotes what method is being applied to use the recipe (crafting, smelting, etc.), a Container which holds the configuration of the inputs, and the current level which is passed to Recipe#matches along with the container.
Important
Forge provides the RecipeWrapper utility class which extends Container for wrapping around IItemHandlers and passing them to methods which requires a Container parameter.
// Within some method with IItemHandlerModifiable handler
recipeManger.getRecipeFor(RecipeType.CRAFTING, new RecipeWrapper(handler), level);
Additional Features
Forge provides some additional behavior to the recipe schema and its implementations for greater control of the system.
Recipe ItemStack Result
Except for minecraft:stonecutting recipes, all vanilla recipe serializers expand the result tag to take in a full ItemStack as a JsonObject instead of just the item name and amount in some cases.
// In some recipe JSON
"result": {
// The name of the registry item to give as a result
"item": "examplemod:example_item",
// The number of items to return
"count": 4,
// The tag data of the stack, can also be a string
"nbt": {
// Add tag data here
}
}
Note
The nbt tag can alternatively be a string containing a stringified NBT (or SNBT) for data which cannot be properly represented as a JSON object (such as IntArrayTags).
Conditional Recipes
Recipes and their unlocking advancement can be loaded conditionally and defaulted depending on what information is present (mod loaded, item exists, etc.).
Larger Crafting Grids
By default, vanilla declares a maximum width and height for a crafting grid to be a 3x3 square. This can be expanded by calling ShapedRecipe#setCraftingSize with the new width and height in FMLCommonSetupEvent.
Warning
ShapedRecipe#setCraftingSize is NOT thread-safe. As such, it should be enqueued to the synchronous work queue via FMLCommonSetupEvent#enqueueWork.
Larger crafting grids in recipes can be data generated.
Ingredient Types
A few additional ingredient types are added to allow recipes to have inputs which check tag data or combine multiple ingredients into a single input checker.
Resources: Custom Recipes
Custom Recipes
Every recipe definition is made up of three components: the Recipe implementation which holds the data and handles the execution logic with the provided inputs, the RecipeType which represents the category or context the recipe will be used in, and the RecipeSerializer which handles decoding and network communication of the recipe data. How one chooses to use the recipe is up to the implementor.
Recipe
The Recipe interface describes the recipe data and the execution logic. This includes matching the inputs and providing the associated result. As the recipe subsystem performs item transformations by default, the inputs are supplied through a Container subtype.
Important
The Containers passed into the recipe should be treated as if its contents were immutable. Any mutable operations should be performed on a copy of the input through ItemStack#copy.
To be able to obtain a recipe instance from the manager, #matches must return true. This method checks against the provided container to see whether the associated inputs are valid. Ingredients can be used for validation by calling Ingredient#test.
If the recipe has been chosen, it is then built using #assemble which may use data from the inputs to create the result.
Tip
#assemble should always produce a unique ItemStack. If unsure whether #assemble does so, call ItemStack#copy on the result before returning.
Most of the other methods are purely for integration with the recipe book.
public record ExampleRecipe(Ingredient input, int data, ItemStack output) implements Recipe<Container> {
// Implement methods here
}
Note
While a record is used in the above example, it is not required to do so in your own implementation.
RecipeType
RecipeType is responsible for defining the category or context the recipe will be used within. For example, if a recipe was going to be smelted in a furnace, it would have a type of RecipeType#SMELTING. Being blasted in a blast furnace would have a type of RecipeType#BLASTING.
If none of the existing types match what context the recipe will be used within, then a new RecipeType must be registered.
The RecipeType instance must then be returned by Recipe#getType in the new recipe subtype.
// For some RegistryObject<RecipeType> EXAMPLE_TYPE
// In ExampleRecipe
@Override
public RecipeType<?> getType() {
return EXAMPLE_TYPE.get();
}
RecipeSerializer
A RecipeSerializer is responsible for decoding JSONs and communicating across the network for an associated Recipe subtype. Each recipe decoded by the serializer is saved as a unique instance within the RecipeManager. A RecipeSerializer must be registered.
Only three methods need to be implemented for a RecipeSerializer:
| Method | Description |
|---|---|
| fromJson | Decodes a JSON into the Recipe subtype. |
| toNetwork | Encodes a Recipe to the buffer to send to the client. The recipe identifier does not need to be encoded. |
| fromNetwork | Decodes a Recipe from the buffer sent from the server. The recipe identifier does not need to be decoded. |
The RecipeSerializer instance must then be returned by Recipe#getSerializer in the new recipe subtype.
// For some RegistryObject<RecipeSerializer> EXAMPLE_SERIALIZER
// In ExampleRecipe
@Override
public RecipeSerializer<?> getSerializer() {
return EXAMPLE_SERIALIZER.get();
}
Tip
There are some useful methods to make reading and writing data for recipes easier. Ingredients can use #fromJson, #toNetwork, and #fromNetwork while ItemStacks can use CraftingHelper#getItemStack, FriendlyByteBuf#writeItem, and FriendlyByteBuf#readItem.
Building the JSON
Custom Recipe JSONs are stored in the same place as other recipes. The specified type should represent the registry name of the recipe serializer. Any additional data is specified by the serializer during decoding.
{
// The custom serializer registry name
"type": "examplemod:example_serializer",
"input": {
// Some ingredient input
},
"data": 0, // Some data wanted for the recipe
"output": {
// Some stack output
}
}
Non-Item Logic
If items are not used as part of the input or result of a recipe, then the normal methods provided in RecipeManager will not be useful. Instead, an additional method for testing a recipe’s validity and/or supplying the result should be added to the custom Recipe instance. From there, all the recipes for that specific RecipeType can be obtained via RecipeManager#getAllRecipesFor and then checked and/or supplied the result using the newly implemented methods.
// In some Recipe subimplementation ExampleRecipe
// Checks the block at the position to see if it matches the stored data
boolean matches(Level level, BlockPos pos);
// Creates the block state to set the block at the specified position to
BlockState assemble(RegistryAccess access);
// In some manager class
public Optional<ExampleRecipe> getRecipeFor(Level level, BlockPos pos) {
return level.getRecipeManager()
.getAllRecipesFor(exampleRecipeType) // Gets all recipes
.stream() // Looks through all recipes for types
.filter(recipe -> recipe.matches(level, pos)) // Checks if the recipe inputs are valid
.findFirst(); // Finds the first recipe whose inputs match
}
Data Generation
All custom recipes, regardless of input or output data, can be created into a FinishedRecipe for data generation using the RecipeProvider.
Resources: Non-Datapack Recipes
Non-Datapack Recipes
Not all recipes are simplistic enough or migrated to using data-driven recipes. Some subsystems still need to be patched within the codebase to provide support for adding new recipes.
Brewing Recipes
Brewing is one of the few recipes that still exist in code. Brewing recipes are added as part of a bootstrap within PotionBrewing for their containers, container recipes, and potion mixes. To expand upon the existing system, Forge allows brewing recipes to be added by calling BrewingRecipeRegistry#addRecipe in FMLCommonSetupEvent.
Warning
BrewingRecipeRegistry#addRecipe must be called within the synchronous work queue via #enqueueWork as the method is not thread-safe.
The default implementation takes in an input ingredient, a catalyst ingredient, and a stack output for a standard implementation. Additionally, an IBrewingRecipe instance can be supplied instead to do the transformations.
IBrewingRecipe
IBrewingRecipe is a pseudo-Recipe interface that checks whether the input and catalyst is valid and provides the associated output if so. This is provided through #isInput, #isIngredient, and #getOutput respectively. The output method has access to the input and catalyst stacks to construct the result.
Important
When copying data between ItemStacks or CompoundTags, make sure to use their respective #copy methods to create unique instances.
There is no wrapper for adding additional potion containers or potion mixes similar to vanilla. A new IBrewingRecipe implementation will need to be added to replicate this behavior.
Anvil Recipes
Anvils are responsible for taking a damaged input and given some material or a similar input, remove some of the damage on the input result. As such, its system is not easily data-driven. However, as anvil recipes are an input with some number of materials equals some output when the user has the required experience levels, it can be modified to create a pseudo-recipe system via AnvilUpdateEvent. This takes in the input and materials and allows the modder to specify the output, experience level cost, and number of materials to use for the output. The event can also prevent any output by canceling it.
// Checks whether the left and right items are correct
// When true, sets the output, level experience cost, and material amount
public void updateAnvil(AnvilUpdateEvent event) {
if (event.getLeft().is(...) && event.getRight().is(...)) {
event.setOutput(...);
event.setCost(...);
event.setMaterialCost(...);
}
}
The update event must be attached to the Forge event bus.
Loom Recipes
Looms are responsible for applying a dye and pattern (either from the loom or from an item) to a banner. While the banner and the dye must be a BannerItem or DyeItem respectively, custom patterns can be created and applied in the loom. Banner Patterns can be created by registering a BannerPattern.
Important
BannerPatterns which are in the minecraft:no_item_required tag appear as an option in the loom. Patterns not in this tag must have an accompanying BannerPatternItem to be used along with an associated tag.
private static final DeferredRegister<BannerPattern> REGISTER = DeferredRegister.create(Registries.BANNER_PATTERN, "examplemod");
// Takes in the pattern name to send over the network
public static final BannerPattern EXAMPLE_PATTERN = REGISTER.register("example_pattern", () -> new BannerPattern("examplemod:ep"));
Data Generation: Recipe Providers
Recipe Generation
Recipes can be generated for a mod by subclassing RecipeProvider and implementing #buildRecipes. A recipe is supplied for data generation once a FinishedRecipe view is accepted by the consumer. FinishedRecipes can either be created and supplied manually or, for convenience, created using a RecipeBuilder.
After implementation, the provider must be added to the DataGenerator.
// On the MOD event bus
@SubscribeEvent
public void gatherData(GatherDataEvent event) {
event.getGenerator().addProvider(
// Tell generator to run only when server data are generating
event.includeServer(),
MyRecipeProvider::new
);
}
RecipeBuilder
RecipeBuilder is a convenience implementation for creating FinishedRecipes to generate. It provides basic definitions for unlocking, grouping, saving, and getting the result of a recipe. This is done through #unlockedBy, #group, #save, and #getResult respectively.
Important
ItemStack outputs in recipes are not supported within vanilla recipe builders. A FinishedRecipe must be built in a different manner for existing vanilla recipe serializers to generate this data.
Warning
The item results being generated must have a valid RecipeCategory specified; otherwise, a NullPointerException will be thrown.
All recipe builders except for [SpecialRecipeBuilder] require an advancement criteria to be specified. All recipes generate a criteria unlocking the recipe if the player has used the recipe previously. However, an additional criteria must be specified that allows the player to obtain the recipe without any prior knowledge. If any of the criteria specified is true, then the played will obtain the recipe for the recipe book.
Tip
Recipe criteria commonly use InventoryChangeTrigger to unlock their recipe when certain items are present in the user’s inventory.
ShapedRecipeBuilder
ShapedRecipeBuilder is used to generate shaped recipes. The builder can be initialized via #shaped. The recipe group, input symbol pattern, symbol definition of ingredients, and the recipe unlock criteria can be specified before saving.
// In RecipeProvider#buildRecipes(writer)
ShapedRecipeBuilder builder = ShapedRecipeBuilder.shaped(RecipeCategory.MISC, result)
.pattern("a a") // Create recipe pattern
.define('a', item) // Define what the symbol represents
.unlockedBy("criteria", criteria) // How the recipe is unlocked
.save(writer); // Add data to builder
Additional Validation Checks
Shaped recipes have some additional validation checks performed before building:
- A pattern must be defined and take in more than one item.
- All pattern rows must be the same width.
- A symbol cannot be defined more than once.
- The space character (
' ') is reserved for representing no item in a slot and, as such, cannot be defined. - A pattern must use all symbols defined by the user.
ShapelessRecipeBuilder
ShapelessRecipeBuilder is used to generate shapeless recipes. The builder can be initialized via #shapeless. The recipe group, input ingredients, and the recipe unlock criteria can be specified before saving.
// In RecipeProvider#buildRecipes(writer)
ShapelessRecipeBuilder builder = ShapelessRecipeBuilder.shapeless(RecipeCategory.MISC, result)
.requires(item) // Add item to the recipe
.unlockedBy("criteria", criteria) // How the recipe is unlocked
.save(writer); // Add data to builder
SimpleCookingRecipeBuilder
SimpleCookingRecipeBuilder is used to generate smelting, blasting, smoking, and campfire cooking recipes. Additionally, custom cooking recipes using the SimpleCookingSerializer can also be data generated using this builder. The builder can be initialized via #smelting, #blasting, #smoking, #campfireCooking, or #cooking respectively. The recipe group and the recipe unlock criteria can be specified before saving.
// In RecipeProvider#buildRecipes(writer)
SimpleCookingRecipeBuilder builder = SimpleCookingRecipeBuilder.smelting(input, RecipeCategory.MISC, result, experience, cookingTime)
.unlockedBy("criteria", criteria) // How the recipe is unlocked
.save(writer); // Add data to builder
SingleItemRecipeBuilder
SingleItemRecipeBuilder is used to generate stonecutting recipes. Additionally, custom single item recipes using a serializer like SingleItemRecipe$Serializer can also be data generated using this builder. The builder can be initialized via #stonecutting or through the constructor respectively. The recipe group and the recipe unlock criteria can be specified before saving.
// In RecipeProvider#buildRecipes(writer)
SingleItemRecipeBuilder builder = SingleItemRecipeBuilder.stonecutting(input, RecipeCategory.MISC, result)
.unlockedBy("criteria", criteria) // How the recipe is unlocked
.save(writer); // Add data to builder
Non-RecipeBuilder Builders
Some recipe builders do not implement RecipeBuilder due to lacking features used by all previously mentioned recipes.
SmithingTransformRecipeBuilder
SmithingTransformRecipeBuilder is used to generate smithing recipes which transform an item. Additionally, custom recipes using a serializer like SmithingTransformRecipe$Serializer can also be data generated using this builder. The builder can be initialized via #smithing or through the constructor respectively. The recipe unlock criteria can be specified before saving.
// In RecipeProvider#buildRecipes(writer)
SmithingTransformRecipeBuilder builder = SmithingTransformRecipeBuilder.smithing(template, base, addition, RecipeCategory.MISC, result)
.unlocks("criteria", criteria) // How the recipe is unlocked
.save(writer, name); // Add data to builder
SmithingTrimRecipeBuilder
SmithingTrimRecipeBuilder is used to generate smithing recipes for armor trims. Additionally, custom upgrade recipes using a serializer like SmithingTrimRecipe$Serializer can also be data generated using this builder. The builder can be initialized via #smithingTrim or through the constructor respectively. The recipe unlock criteria can be specified before saving.
// In RecipeProvider#buildRecipes(writer)
SmithingTrimRecipe builder = SmithingTrimRecipe.smithingTrim(template, base, addition, RecipeCategory.MISC)
.unlocks("criteria", criteria) // How the recipe is unlocked
.save(writer, name); // Add data to builder
SpecialRecipeBuilder
SpecialRecipeBuilder is used to generate empty JSONs for dynamic recipes that cannot easily be constrained to the recipe JSON format (dying armor, firework, etc.). The builder can be initialized via #special.
// In RecipeProvider#buildRecipes(writer)
SpecialRecipeBuilder.special(dynamicRecipeSerializer)
.save(writer, name); // Add data to builder
Conditional Recipes
Conditional recipes can also be data generated via ConditionalRecipe$Builder. The builder can be obtained using #builder.
Conditions for each recipe can be specified by first calling #addCondition and then calling #addRecipe after all conditions have been specified. This process can be repeated as many times as the programmer would like.
After all recipes have been specified, advancements can be added for each recipe at the end using #generateAdvancement. Alternatively, the conditional advancement can be set using #setAdvancement.
// In RecipeProvider#buildRecipes(writer)
ConditionalRecipe.builder()
// Add the conditions for the recipe
.addCondition(...)
// Add recipe to return when conditions are true
.addRecipe(...)
// Add the next conditions for the next recipe
.addCondition(...)
// Add next recipe to return when the next conditions are true
.addRecipe(...)
// Create conditional advancement which uses the conditions
// and unlocking advancement in the recipes above
.generateAdvancement()
.build(writer, name);
IConditionBuilder
To simplify adding conditions to conditional recipes without having to construct the instances of each condition instance manually, the extended RecipeProvider can implement IConditionBuilder. The interface adds methods to easily construct condition instances.
// In ConditionalRecipe$Builder#addCondition
(
// If either 'examplemod:example_item'
// OR 'examplemod:example_item2' exists
// AND
// NOT FALSE
// Methods are defined by IConditionBuilder
and(
or(
itemExists("examplemod", "example_item"),
itemExists("examplemod", "example_item2")
),
not(
FALSE()
)
)
)
Custom Recipe Serializers
Custom recipe serializers can be data generated by creating a builder that can construct a FinishedRecipe. The finished recipe encodes the recipe data and its unlocking advancement, when present, to JSON. Additionally, the name and serializer of the recipe is also specified to know where to write to and what can decode the object when loading. Once a FinishedRecipe is constructed, it simply needs to be passed to the Consumer supplied by RecipeProvider#buildRecipes.
Tip
FinishedRecipes are flexible enough that any object transformation can be data generated, not just items.