Minecraft Forge — Guide
Getting Started
Getting Started with Forge
If you have never made a Forge mod before, this section will provide the minimum amount of information needed to setup a Forge development environment. The rest of the documentation is about where to go from here.
Prerequisites
- An installation of the Java 21 Development Kit (JDK) and 64-bit Java Virtual Machine (JVM). Forge recommends and officially supports Eclipse Temurin.
- Familiarity with an Integrated Development Environment (IDE).
- It is recommended to use an IDE with Gradle integration.
From Zero to Modding
- Download the Mod Developer Kit (MDK) from the Forge file site by clicking ‘Mdk’ followed by the ‘Skip’ button in the top right after waiting for a period of time. It is recommended to download the latest version of Forge whenever possible.
- Extract the downloaded MDK into an empty directory. This will be your mod’s directory, which should now contain some gradle files and a
srcsubdirectory containing the example mod.
Note
A number of files can be reused across different mods. These files are:
- the
gradlesubdirectory build.gradlegradlewgradlew.batsettings.gradle
The src subdirectory does not need to be copied across workspaces; however, you may need to refresh the Gradle project if the java (src/main/java) and resource (src/main/resources) are created later.
3. Open your selected IDE:
- Forge only explicitly supports development on Eclipse and IntelliJ IDEA, but there are additional run configurations for Visual Studio Code. Regardless, any environment, from Apache NetBeans to Vim / Emacs, can be used.
- Eclipse and IntelliJ IDEA’s Gradle integration, both installed and enabled by default, will handle the rest of the initial workspace setup on import or open. This includes downloading the necessary packages from Mojang, MinecraftForge, etc. The ‘Gradle for Java’ plugin is needed for Visual Studio Code to do the same.
- Gradle will need to be invoked to re-evaluate the project for almost all changes to its associated files (e.g.,
build.gradle,settings.gradle, etc.). Some IDEs come with ‘Refresh’ buttons to do this; however, it can be done through the terminal viagradlew. 4. Generate run configurations for your selected IDE: - Eclipse: Run the
genEclipseRunstask. - IntelliJ IDEA: Run the
genIntellijRunstask. If a “module not specified” error occurs, set theideaModuleproperty to your ‘main’ module (typically${project.name}.main). - Visual Studio Code: Run the
genVSCodeRunstask. - Other IDEs: You can run the configurations directly using
gradle run*(e.g.,runClient,runServer,runData,runGameTestServer). These can also be used with the supported IDEs.
Customizing Your Mod Information
Edit the build.gradle file to customize how your mod is built (e.g., file name, artifact version, etc.).
Important
Do not edit the settings.gradle unless you know what you are doing. The file specifies the repository that ForgeGradle is uploaded to.
Recommended build.gradle Customizations
Mod Id Replacement
Replace all occurrences of examplemod, including mods.toml and the main mod file with the mod id of your mod. This also includes changing the name of the file you build by setting base.archivesName (this is typically set to your mod id).
// In some build.gradle
base.archivesName = 'mymod'
Group Id
The group property should be set to your top-level package, which should either be a domain you own or your email address:
| Type | Value | Top-Level Package |
|---|---|---|
| Domain | example.com | com.example |
| Subdomain | example.github.io | io.github.example |
| [email protected] | com.gmail.example |
// In some build.gradle
group = 'com.example'
The packages within your java source (src/main/java) should also now conform to this structure, with an inner package representing the mod id:
com
- example (top-level package specified in group property)
- mymod (the mod id)
- MyMod.java (renamed ExampleMod.java)
Version
Set the version property to the current version of your mod. We recommend using a variation of Maven versioning.
// In some build.gradle
version = '1.21.1-1.0.0.0'
Additional Configurations
Additional configurations can be found on the ForgeGradle docs.
Building and Testing Your Mod
- To build your mod, run
gradlew build. This will output a file inbuild/libswith the name[archivesBaseName]-[version].jar, by default. This file can be placed in themodsfolder of a Forge-enabled Minecraft setup or distributed. - To run your mod in a test environment, you can either use the generated run configurations or use the associated tasks (e.g.
gradlew runClient). This will launch Minecraft from the run directory (default ‘run’) along with any source sets specified. The default MDK includes themainsource set, so any code written insrc/main/javawill be applied. - If you are running a dedicated server, whether through the run configuration or
gradlew runServer, the server will initially shut down immediately. You will need to accept the Minecraft EULA by editing theeula.txtfile in the run directory. Once accepted, the server will load, which can then be accessed via a direct connect tolocalhost.
Note
You should always test your mod in a dedicated server environment. This includes client-only mods as they should not do anything when loaded on the server.
The Mod Files
Mod Files
The mod files are responsible for determining what mods are packaged into your JAR, what information to display within the ‘Mods’ menu, and how your mod should be loaded in the game.
mods.toml
The mods.toml file defines the metadata of your mod(s). It also contains additional information that is displayed within the ‘Mods’ menu and how your mod(s) should be loaded into the game.
The file uses the Tom’s Obvious Minimal Language, or TOML, format. The file must be stored under the META-INF folder in the resource directory of the source set you are using (src/main/resources/META-INF/mods.toml for the main source set). A mods.toml file may look something like this:
modLoader="javafml"
loaderVersion="[52,)"
issueTrackerURL="
showAsResourcePack=false
clientSideOnly=false
[[mods]]
modId="examplemod"
version="1.0.0.0"
displayName="Example Mod"
updateJSONURL="
displayURL="
logoFile="logo.png"
credits="I'd like to thank my mother and father."
authors="Author"
description='''
Lets you craft dirt into diamonds. This is a traditional mod that has existed for eons. It is ancient. The holy Notch created it. Jeb rainbowfied it. Dinnerbone made it upside down. Etc.
'''
displayTest="MATCH_VERSION"
[[dependencies.examplemod]]
modId="forge"
mandatory=true
versionRange="[52,)"
ordering="NONE"
side="BOTH"
[[dependencies.examplemod]]
modId="minecraft"
mandatory=true
versionRange="[1.21.1,)"
ordering="NONE"
side="BOTH"
mods.toml is broken into three parts: the non-mod-specific properties, which are linked to the mod file; the mod properties, with a section for each mod; and the dependency configurations, with a section for each mod’s or mods’ dependencies. Each of the properties associated with the mods.toml file will be explained below, where required means that a value must be specified or an exception will be thrown.
Non-Mod-Specific Properties
Non-mod-specific properties are properties associated with the JAR itself, indicating how to load the mod(s) and any additional global metadata.
| Property | Type | Default | Description | Example |
|---|---|---|---|---|
modLoader |
string | mandatory | The language loader used by the mod(s). Can be used to support alternative language structures, such as Kotlin objects for the main file, or different methods of determining the entrypoint, such as an interface or method. Forge provides the Java loader "javafml" and low/no code loader "lowcodefml". |
"javafml" |
loaderVersion |
string | mandatory | The acceptable version range of the language loader, expressed as a Maven Version Range. For javafml and lowcodefml, the version is the major version of the Forge version. |
"[46,)" |
license |
string | mandatory | The license the mod(s) in this JAR are provided under. It is suggested that this is set to the SPDX identifier you are using and/or a link to the license. You can visit to help pick the license you want to use. | "MIT" |
showAsResourcePack |
boolean | false |
When true, the mod(s)’s resources will be displayed as a separate resource pack on the ‘Resource Packs’ menu, rather than being combined with the ‘Mod resources’ pack. |
true |
clientSideOnly |
boolean | false |
When true, Forge will skip loading all mods declared in the mods.toml when running on a dedicated server, and set a correct displayTest for each of them when running on a client. |
true |
services |
array | [] |
An array of services your mod uses. This is consumed as part of the created module for the mod from Forge’s implementation of the Java Platform Module System. This is deprecated in favour of the standard Java methods for declaring services, namely individual service files or module-info.java uses directive |
["net.minecraftforge.forgespi.language.IModLanguageProvider"] |
properties |
table | {} |
A table of substitution properties. This is used by StringSubstitutor to replace ${file.<key>} with its corresponding value. This is currently only used to replace the version in the mod-specific properties. |
{ "example" = "1.2.3" } referenced by ${file.example} |
issueTrackerURL |
string | nothing | A URL representing the place to report and track issues with the mod(s). | "https://forums.minecraftforge.net/" |
Important
The services property is functionally equivalent to specifying the uses directive in a module, which allows loading a service of a given type.
Mod-Specific Properties
Mod-specific properties are tied to the specified mod using the [[mods]] header. This is an array of tables; all key/value properties will be attached to that mod until the next header.
# Properties for examplemod1
[[mods]]
modId = "examplemod1"
# Properties for examplemod2
[[mods]]
modId = "examplemod2"
| Property | Type | Default | Description | Example |
|---|---|---|---|---|
modId |
string | mandatory | The unique identifier representing this mod. The id must match ^[a-z][a-z0-9_]{1,63}$ (a string 2-64 characters; starts with a lowercase letter; made up of lowercase letters, numbers, or underscores). |
"examplemod" |
namespace |
string | value of modId |
An override namespace for the mod. The namespace much match ^[a-z][a-z0-9_.-]{1,63}$ (a string 2-64 characters; starts with a lowercase letter; made up of lowercase letters, numbers, underscores, dots, or dashes). Currently unused. |
"example" |
version |
string | "1" |
The version of the mod, preferably in a variation of Maven versioning. When set to ${file.jarVersion}, it will be replaced with the value of the Implementation-Version property in the JAR’s manifest (displays as 0.0NONE in a development environment). |
"1.21.1-1.0.0.0" |
displayName |
string | value of modId |
The pretty name of the mod. Used when representing the mod on a screen (e.g., mod list, mod mismatch). | "Example Mod" |
description |
string | "MISSING DESCRIPTION" |
The description of the mod shown in the mod list screen. It is recommended to use a multiline literal string. | "This is an example." |
logoFile |
string | nothing | The name and extension of an image file used on the mods list screen. The logo must be in the root of the JAR or directly in the root of the source set (e.g., src/main/resources for the main source set). |
"example_logo.png" |
logoBlur |
boolean | true |
Whether to use GL_LINEAR* (true) or GL_NEAREST* (false) to render the logoFile. |
false |
updateJSONURL |
string | nothing | A URL to a JSON used by the update checker to make sure the mod you are playing is the latest version. | "https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json" |
features |
table | {} |
See ‘features’. | { java_version = "17" } |
modproperties |
table | {} |
A table of key/values associated with this mod. Currently unused by Forge, but is mainly for use by mods. | { example = "value" } |
modUrl |
string | nothing | A URL to the download page of the mod. Currently unused. | "https://files.minecraftforge.net/" |
credits |
string | nothing | Credits and acknowledges for the mod shown on the mod list screen. | "The person over here and there." |
authors |
string | nothing | The authors of the mod shown on the mod list screen. | "Example Person" |
displayURL |
string | nothing | A URL to the display page of the mod shown on the mod list screen. | "https://minecraftforge.net/" |
displayTest |
string | "MATCH_VERSION" |
See ‘sides’. | "NONE" |
Features
The features system allows mods to demand that certain settings, software, or hardware are available when loading the system. When a feature is not satisfied, mod loading will fail, informing the user about the requirement. Currently, Forge provides the following features:
| Feature | Description | Example |
|---|---|---|
java_version |
The acceptable version range of the Java version, expressed as a Maven Version Range. This should be the supported version used by Minecraft. | "[17,)" |
Dependency Configurations
Mods can specify their dependencies, which are checked by Forge before loading the mods. These configurations are created using the array of tables [[dependencies.<modid>]] where modid is the identifier of the mod the dependency is for.
| Property | Type | Default | Description | Example |
|---|---|---|---|---|
modId |
string | mandatory | The identifier of the mod added as a dependency. | "example_library" |
mandatory |
boolean | mandatory | Whether the game should crash when this dependency is not met. | true |
versionRange |
string | "" |
The acceptable version range of the language loader, expressed as a Maven Version Range. An empty string matches any version. | "[1, 2)" |
ordering |
string | "NONE" |
Defines if the mod must load before ("BEFORE") or after ("AFTER") this dependency. If the ordering does not matter, return "NONE" |
"AFTER" |
side |
string | "BOTH" |
The physical side the dependency must be present on: "CLIENT", "SERVER", or "BOTH". |
"CLIENT" |
referralUrl |
string | nothing | A URL to the download page of the dependency. Currently unused. | "https://library.example.com/" |
Warning
The ordering of two mods may cause a crash due to a cyclic dependency: for example, mod A must load "BEFORE" mod B and mod B "BEFORE" mod A.
Mod Entrypoints
Now that the mods.toml is filled out, we need to provide an entrypoint to being programming the mod. Entrypoints are essentially the starting point for executing the mod. The entrypoint itself is determined by the language loader used in the mods.toml.
javafml and @Mod
javafml is a language loader provided by Forge for the Java programming language. The entrypoint is defined using a public class with the @Mod annotation. The value of @Mod must contain one of the mod ids specified within the mods.toml. From there, all initialization logic (e.g., registering events, adding DeferredRegisters) can be specified within the constructor of the class. The mod bus can be obtained from FMLJavaModLoadingContext which is fed through as a constructor parameter.
@Mod("examplemod") // Must match mods.toml
public class Example {
public Example(FMLJavaModLoadingContext context) {
// Initialize logic here
var modBus = context.getModEventBus();
// ...
}
}
lowcodefml
lowcodefml is a language loader used as a way to distribute datapacks and resource packs as mods without the need of an in-code entrypoint. It is specified as lowcodefml rather than nocodefml for minor additions in the future that might require minimal coding.
Structuring Your Mod
Structuring Your Mod
Structured mods are beneficial for maintenance, making contributions, and providing a clearer understanding of the underlying codebase. Some of the recommendations from Java, Minecraft, and Forge are listed below.
Note
You do not have to follow the advice below; you can structure your mod any way you see fit. However, it is still highly recommended to do so.
Packaging
When structuring your mod, pick a unique, top-level package structure. Many programmers will use the same name for different classes, interfaces, etc. Java allows classes to have the same name as long as they are in different packages. As such, if two classes have the same package with the same name, only one would be loaded, most likely causing the game to crash.
a.jar
- com.example.ExampleClass
b.jar
- com.example.ExampleClass // This class will not normally be loaded
This is even more relevant when it comes to loading modules. If there are class files in two packages under the same name in separate modules, this will cause the mod loader to crash on startup since mod modules are exported to the game and other mods.
module A
- package X
- class I
- class J
module B
- package X // This package will cause the mod loader to crash, as there already is a module with package X being exported
- class R
- class S
- class T
As such, your top level package should be something that you own: a domain, email address, a subdomain of where your website, etc. It can even be your name or username as long as you can guarantee that it will be uniquely identifiable within the expected target.
| Type | Value | Top-Level Package |
|---|---|---|
| Domain | example.com | com.example |
| Subdomain | example.github.io | io.github.example |
| [email protected] | com.gmail.example |
The next level package should then be your mod’s id (e.g. com.example.examplemod where examplemod is the mod id). This will guarantee that, unless you have two mods with the same id (which should never be the case), your packages should not have any issues loading.
You can find some additional naming conventions on Oracle’s tutorial page.
Sub-package Organization
In addition to the top-level package, it is highly recommend to break your mod’s classes between subpackages. There are two major methods on how to do so:
- Group By Function: Make subpackages for classes with a common purpose. For example, blocks can be under
blockorblocks, entities underentityorentities, etc. Mojang uses this structure with the singular version of the word. - Group By Logic: Make subpackages for classes with a common logic. For example, if you were creating a new type of crafting table, you would put its block, menu, item, and more under
feature.crafting_table.
Client, Server, and Data Packages
In general, code only for a given side or runtime should be isolated from the other classes in a separate subpackage. For example, code related to data generation should go in a data package while code only on the dedicated server should go in a server package.
However, it is highly recommended that client-only code should be isolated in a client subpackage. This is because dedicated servers have no access to any of the client-only packages in Minecraft. As such, having a dedicated package would provide a decent sanity check to verify you are not reaching across sides within your mod.
Class Naming Schemes
A common class naming scheme makes it easier to decipher the purpose of the class or to easily locate specific classes.
Classes are commonly suffixed with its type, for example:
- An
ItemcalledPowerRing->PowerRingItem. - A
BlockcalledNotDirt->NotDirtBlock. - A menu for an
Oven->OvenMenu.
Note
Mojang typically follows a similar structure for all classes except entities. Those are represented by just their names (e.g. Pig, Zombie, etc.).
Choose One Method from Many
There are many methods for performing a certain task: registering an object, listening for events, etc. It’s generally recommended to be consistent by using a single method to accomplish a given task. While this does improve code formatting, it also avoid any weird interactions or redundancies that may occur (e.g. your event listener executing twice).
Versioning
Versioning
In general projects, semantic versioning is often used (which has the format MAJOR.MINOR.PATCH). However, in the case of modding it may be more beneficial to use the format MCVERSION-MAJORMOD.MAJORAPI.MINOR.PATCH to be able to differentiate between world-breaking and API-breaking changes of a mod.
Important
Forge uses Maven version ranges to compare version strings, which is not fully compatible with the Semantic Versioning 2.0.0 spec, such as the ‘prerelease’ tag.
Examples
Here is a list of examples that can increment the various variables.
MCVERSION- Always matches the Minecraft version the mod is for.
MAJORMOD- Removing items, blocks, block entities, etc.
- Changing or removing previously existing mechanics.
- Updating to a new Minecraft version.
MAJORAPI- Changing the order or variables of enums.
- Changing return types of methods.
- Removing public methods altogether.
MINOR- Adding items, blocks, block entities, etc.
- Adding new mechanics.
- Deprecating public methods. (This is not a
MAJORAPIincrement since it doesn’t break an API.) PATCH- Bugfixes.
When incrementing any variable, all lesser variables should reset to 0. For instance, if MINOR would increment, PATCH would become 0. If MAJORMOD would increment, all other variables would become 0.
Work In Progress
If you are in the initial development stage of your mod (before any official releases), the MAJORMOD and MAJORAPI should always be 0. Only MINOR and PATCH should be updated every time you build your mod. Once you build an official release (most of the time with a stable API), you should increment MAJORMOD to version 1.0.0.0. For any further development stages, refer to the Prereleases and Release candidates section of this document.
Multiple Minecraft Versions
If the mod upgrades to a new version of Minecraft, and the old version will only receive bug fixes, the PATCH variable should be updated based on the version before the upgrade. If the mod is still in active development in both the old and the new version of Minecraft, it is advised to append the version to both build numbers. For example, if the mod is upgraded to version 3.0.0.0 due to a Minecraft version change, the old mod should also be updated to 3.0.0.0. The old version will become, for example, version 1.7.10-3.0.0.0, while the new version will become 1.8-3.0.0.0. If there are no changes at all when building for a newer Minecraft version, all variables except for the Minecraft version should stay the same.
Final Release
When dropping support for a Minecraft version, the last build for that version should get the -final suffix. This denotes that the mod will no longer be supported for the denoted MCVERSION and that players should upgrade to a newer version of the mod to continue receiving updates and bug fixes.
Pre-releases
It is also possible to prerelease work-in-progress features, which means new features are released that are not quite done yet. These can be seen as a sort of “beta”. These versions should be appended with -betaX, where X is the number of the prerelease. (This guide does not use -pre since, at the time of writing, it is not a valid alias for -beta.) Note that already released versions and versions before the initial release can not go into prerelease; variables (mostly MINOR, but MAJORAPI and MAJORMOD can also prerelease) should be updated accordingly before adding the -beta suffix. Versions before the initial release are simply work-in-progress builds.
Release Candidates
Release candidates act as prereleases before an actual version change. These versions should be appended with -rcX, where X is the number of the release candidate which should, in theory, only be increased for bugfixes. Already released versions can not receive release candidates; variables (mostly MINOR, but MAJORAPI and MAJORMOD can also prerelease) should be updated accordingly before adding the -rc suffix. When releasing a release candidate as stable build, it can either be exactly the same as the last release candidate or have a few more bug fixes.
Networking: Introduction
Networking
Communication between servers and clients is the backbone of a successful mod implementation.
There are two primary goals in network communication:
- Making sure the client view is “in sync” with the server view - The flower at coordinates (X, Y, Z) just grew
- Giving the client a way to tell the server that something has changed about the player - the player pressed a key
The most common way to accomplish these goals is to pass messages between the client and the server. These messages will usually be structured, containing data in a particular arrangement, for easy sending and receiving.
There are a variety of techniques provided by Forge to facilitate communication mostly built on top of netty.
The simplest, for a new mod, would be SimpleImpl, where most of the complexity of the netty system is abstracted away. It uses a message and handler style system.
Networking: SimpleImpl
SimpleImpl
SimpleImpl is the name given to the packet system that revolves around the SimpleChannel class. Using this system is by far the easiest way to send custom data between clients and the server.
Getting Started
First you need to create your SimpleChannel object. We recommend that you do this in a separate class, possibly something like ModidPacketHandler. Create your SimpleChannel as a static field in this class, like so:
private static final String PROTOCOL_VERSION = "1";
public static final SimpleChannel INSTANCE = NetworkRegistry.newSimpleChannel(
ResourceLocation.fromNamespaceAndPath("mymodid", "main"),
() -> PROTOCOL_VERSION,
PROTOCOL_VERSION::equals,
PROTOCOL_VERSION::equals
);
The first argument is a name for the channel. The second argument is a Supplier<String> returning the current network protocol version. The third and fourth arguments respectively are Predicate<String> checking whether an incoming connection protocol version is network-compatible with the client or server, respectively.
Here, we simply compare with the PROTOCOL_VERSION field directly, meaning that the client and server PROTOCOL_VERSIONs must always match or FML will deny login.
The Version Checker
If your mod does not require the other side to have a specific network channel, or to be a Forge instance at all, you should take care that you properly define your version compatibility checkers (the Predicate<String> parameters) to handle additional “meta-versions” (defined in NetworkRegistry) that can be received by the version checker. These are:
ABSENT- if this channel is missing on the other endpoint. Note that in this case, the endpoint is still a Forge endpoint, and may have other mods.ACCEPTVANILLA- if the endpoint is a vanilla (or non-Forge) endpoint.
Returning false for both means that this channel must be present on the other endpoint. If you just copy the code above, this is what it does. Note that these values are also used during the list ping compatibility check, which is responsible for showing the green check / red cross in the multiplayer server select screen.
Registering Packets
Next, we must declare the types of messages that we would like to send and receive. This is done using INSTANCE#registerMessage, which takes 5 parameters:
- The first parameter is the discriminator for the packet. This is a per-channel unique ID for the packet. We recommend you use a local variable to hold the ID, and then call registerMessage using
id++. This will guarantee 100% unique IDs. - The second parameter is the actual packet class
MSG. - The third parameter is a
BiConsumer<MSG, FriendlyByteBuf>responsible for encoding the message into the providedFriendlyByteBuf. - The fourth parameter is a
Function<FriendlyByteBuf, MSG>responsible for decoding the message from the providedFriendlyByteBuf. - The final parameter is a
BiConsumer<MSG, Supplier<NetworkEvent.Context>>responsible for handling the message itself.
The last three parameters can be method references to either static or instance methods in Java. Remember that an instance method MSG#encode(FriendlyByteBuf) still satisfies BiConsumer<MSG, FriendlyByteBuf>; the MSG simply becomes the implicit first argument.
Handling Packets
There are a couple things to highlight in a packet handler. A packet handler has both the message object and the network context available to it. The context allows access to the player that sent the packet (if on the server), and a way to enqueue thread-safe work.
public static void handle(MyMessage msg, Supplier<NetworkEvent.Context> ctx) {
ctx.get().enqueueWork(() -> {
// Work that needs to be thread-safe (most work)
ServerPlayer sender = ctx.get().getSender(); // the client that sent this packet
// Do stuff
});
ctx.get().setPacketHandled(true);
}
Packets sent from the server to the client should be handled in another class and wrapped via DistExecutor#unsafeRunWhenOn.
// In Packet class
public static void handle(MyClientMessage msg, Supplier<NetworkEvent.Context> ctx) {
ctx.get().enqueueWork(() ->
// Make sure it's only executed on the physical client
DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> ClientPacketHandlerClass.handlePacket(msg, ctx))
);
ctx.get().setPacketHandled(true);
}
// In ClientPacketHandlerClass
public static void handlePacket(MyClientMessage msg, Supplier<NetworkEvent.Context> ctx) {
// Do stuff
}
Note the presence of #setPacketHandled, which is used to tell the network system that the packet has successfully completed handling.
Warning
As of Minecraft 1.8 packets are by default handled on the network thread.
That means that your handler can not interact with most game objects directly. Forge provides a convenient way to make your code execute on the main thread instead through the supplied NetworkEvent$Context. Simply call NetworkEvent$Context#enqueueWork(Runnable), which will call the given Runnable on the main thread at the next opportunity.
Warning
Be defensive when handling packets on the server. A client could attempt to exploit the packet handling by sending unexpected data.
A common problem is vulnerability to arbitrary chunk generation. This typically happens when the server is trusting a block position sent by a client to access blocks and block entities. When accessing blocks and block entities in unloaded areas of the level, the server will either generate or load this area from disk, then promptly write it to disk. This can be exploited to cause catastrophic damage to a server’s performance and storage space without leaving a trace.
To avoid this problem, a general rule of thumb is to only access blocks and block entities if Level#hasChunkAt is true.
Sending Packets
Sending to the Server
There is but one way to send a packet to the server. This is because there is only ever one server the client can be connected to at once. To do so, we must again use that SimpleChannel that was defined earlier. Simply call INSTANCE.sendToServer(new MyMessage()). The message will be sent to the handler for its type, if one exists.
Sending to Clients
Packets can be sent directly to a client using the SimpleChannel: HANDLER.sendTo(new MyClientMessage(), serverPlayer.connection.getConnection(), NetworkDirection.PLAY_TO_CLIENT). However, this can be quite inconvenient. Forge has some convenience functions that can be used:
// Send to one player
INSTANCE.send(PacketDistributor.PLAYER.with(serverPlayer), new MyMessage());
// Send to all players tracking this level chunk
INSTANCE.send(PacketDistributor.TRACKING_CHUNK.with(levelChunk), new MyMessage());
// Send to all connected players
INSTANCE.send(PacketDistributor.ALL.noArg(), new MyMessage());
There are additional PacketDistributor types available; check the documentation on the PacketDistributor class for more details.
Networking: Synchronizing Entities
Entities
In addition to regular network messages, there are various other systems provided to handle synchronizing entity data.
Spawn Data
In general, the spawning of modded entities is handled separately, by Forge.
Note
This means that simply extending a vanilla entity class may not inherit all its behavior. You may need to implement certain vanilla behaviors yourself.
You can add extra data to the spawn packet Forge sends by implementing the following interface.
IEntityAdditionalSpawnData
If your entity has data that is needed on the client, but does not change over time, then it can be added to the entity spawn packet using this interface. #writeSpawnData and #readSpawnData control how the data should be encoded to/decoded from the network buffer.
Dynamic Data
Data Parameters
This is the main vanilla system for synchronizing entity data from the server to the client. As such, a number of vanilla examples are available to refer to.
Firstly, you need a EntityDataAccessor<T> for the data you wish to keep synchronized. This should be stored as a static final field in your entity class, obtained by calling SynchedEntityData#defineId and passing the entity class and a serializer for that type of data. The available serializer implementations can be found as static constants within the EntityDataSerializers class.
Warning
You should only create data parameters for your own entities, within that entity’s class. Adding parameters to entities you do not control can cause the IDs used to send that data over the network to become desynchronized, causing difficult to debug crashes.
Then, override Entity#defineSynchedData and call this.entityData.define(...) for each of your data parameters, passing the parameter and an initial value to use. Remember to always call the super method first!
You can then get and set these values via your entity’s entityData instance. Changes made will be synchronized to the client automatically.
Game Effects: Particles
Particles
Particles are an effect within the game used as polish to better improve immersion. Their usefulness also requires great caution because of their methods of creation and reference.
Creating a Particle
Particles are broken up between its client only implementation to display the particle and its common implementation to reference the particle or sync data from the server.
| Class | Side | Description |
|---|---|---|
| ParticleType | BOTH | The registry object of a particle’s type definition used to reference the particle on either side |
| ParticleOptions | BOTH | A data holder used to sync information from the network or a command to the associated client(s) |
| ParticleProvider | CLIENT | A factory registered by the ParticleType used to construct a Particle from the associated ParticleOptions. |
| Particle | CLIENT | The renderable logic to display on the associated client(s) |
ParticleType
A ParticleType is the registry object defining what a particular particle type is and provides an available reference to the specific particle on both sides. As such, every ParticleType must be registered.
Each ParticleType takes in two parameters: an overrideLimiter which determines whether the particle renders regardless of distance, and a ParticleOptions$Deserializer which is used to read the sent ParticleOptions on the client. As the base ParticleType is abstract, a single method needs to be implemented: #codec. This represents how to encode and decode the associated ParticleOptions of the type.
Note
ParticleType#codec is only used within the biome codec for vanilla implementations.
In most cases, there is no need to have any particle data sent to the client. For these instances, it is easier to create a new instance of SimpleParticleType: an implementation of ParticleType and ParticleOptions which does not send any custom data to the client besides the type. Most vanilla implementations use SimpleParticleType besides redstone dust for coloring and block/item dependent particles.
Important
A ParticleType is not needed to make a particle spawn if only referenced on the client. However, it is necessary to use any of the prebuilt logic within ParticleEngine or spawn a particle from the server.
ParticleOptions
An ParticleOptions represents the data that each particle takes in. It is also used to send data from particles spawned via the server. All particle spawning methods take in a ParticleOptions such that it knows the type of the particle and the data associated with spawning one.
ParticleOptions is broken down into three methods:
| Method | Description |
|---|---|
| getType | Gets the type definition of the particle, or the ParticleType |
| writeToNetwork | Writes the particle data to a buffer on the server to send to the client |
| writeToString | Writes the particle data to a string |
These objects are either constructed on the fly as needed, or they are singletons as a result of being a SimpleParticleType.
ParticleOptions$Deserializer
To receive the ParticleOptions on the client, or to reference the data within a command, the particle data must be deserialized via ParticleOptions$Deserializer. Each method within ParticleOptions$Deserializer has a parity encoding method within ParticleOptions:
| Method | ParticleOptions Encoder | Description |
|---|---|---|
| fromCommand | writeToString | Decodes a particle data from a string, usually from a command. |
| fromNetwork | writeToNetwork | Decodes a particle data from a buffer on the client. |
This object, when needing to send custom particle data, is passed into the constructor of the ParticleType.
Particle
A Particle provides the rendering logic needed to draw said data onto the screen. To create any Particle, two methods must be implemented:
| Method | Description |
|---|---|
| render | Renders the particle onto the screen. |
| getRenderType | Gets the render type of the particle. |
A common subclass of Particle to render textures is TextureSheetParticle. While #getRenderType needs to be implemented, whatever the texture sprite is set will be rendered at the particle’s location.
ParticleRenderType
ParticleRenderType is a variation on RenderType which constructs the startup and teardown phase for every particle of that type and then renders them all at once via the Tesselator. There are six different render types a particle can be in.
| Render Type | Description |
|---|---|
| TERRAIN_SHEET | Renders a particle whose texture is located within the available blocks. |
| PARTICLE_SHEET_OPAQUE | Renders a particle whose texture is opaque and located within the available particles. |
| PARTICLE_SHEET_TRANSLUCENT | Renders a particle whose texture is translucent and located within the available particles. |
| PARTICLE_SHEET_LIT | Same as PARTICLE_SHEET_OPAQUE except without using the particle shader. |
| CUSTOM | Provides setup for blending and depth mask but provides no rendering functionality as that would be implemented within Particle#render. |
| NO_RENDER | The particle will never render. |
Implementing a custom render type will be left as an exercise to the reader.
ParticleProvider
Finally, a particle is usually created via an ParticleProvider. A factory has a single method #createParticle which is used to create a particle given the particle data, client level, position, and movement delta. Since a Particle is not beholden to any particular ParticleType, it can be reused in different factories as necessary.
An ParticleProvider must be registered by subscribing to the RegisterParticleProvidersEvent on the mod event bus. Within the event, the factory can be registered via #registerSpecial by supplying an instance of the factory to the method.
Important
RegisterParticleProvidersEvent should only be called on the client and thus sided off in some isolated client class, referenced by either DistExecutor or @EventBusSubscriber.
ParticleDescription, SpriteSet, and SpriteParticleRegistration
There are three particle render types that cannot use the above method of registration: PARTICLE_SHEET_OPAQUE, PARTICLE_SHEET_TRANSLUCENT, and PARTICLE_SHEET_LIT. This is because all three of these particle render types use a sprite set that is loaded by the ParticleEngine directly. As such, the textures supplied must be obtained and registered through a different method. This will assume your particle is a subtype of TextureSheetParticle as that is the only vanilla implementation for this logic.
To add a texture to a particle, a new JSON file must be added to assets/<modid>/particles. This is known as the ParticleDescription. The name of this file will represent the registry name of the ParticleType the factory is being attached to. Each particle JSON is an object. The object stores a single key textures which holds an array of ResourceLocations. Any <modid>:<path> texture represented here will point to a texture at assets/<modid>/textures/particle/<path>.png.
{
"textures": [
// Will point to a texture located in
// assets/mymod/textures/particle/particle_texture.png
"mymod:particle_texture",
// Textures should by ordered by drawing order
// e.g. particle_texture will render first, then particle_texture2
// after some time
"mymod:particle_texture2"
]
}
To reference a particle texture, the subtype of TextureSheetParticle should either take in an SpriteSet or a TextureAtlasSprite obtained from SpriteSet. SpriteSet holds a list of textures which refer to the sprites as defined by our ParticleDescription. SpriteSet has two methods, both of which grab a TextureAtlasSprite in different methods. The first method takes in two integers. The backing implementation allows the sprite to have a texture change as it ages. The second method takes in a Random instance to get a random texture from the sprite set. The sprite can be set within TextureSheetParticle by using one of the helper methods that takes in the SpriteSet: #pickSprite which uses the random method of picking a texture, and #setSpriteFromAge which uses the percentage method of two integers to pick the texture.
To register these particle textures, a SpriteParticleRegistration needs to be supplied to the RegisterParticleProvidersEvent#registerSpriteSet method. This method takes in an SpriteSet holding the associated sprite set for the particle and creates an ParticleProvider to create the particle. The simplest method of implementation can be done by implementing ParticleProvider on some class and having the constructor take in an SpriteSet. Then the SpriteSet can be passed to the particle as normal.
Note
If you are registering a TextureSheetParticle subtype which only contains one texture, then you can supply a ParticleProvider$Sprite instead to the #registerSprite method, which has essentially the same functional interface method as ParticleProvider.
Spawning a Particle
Particles can be spawned from either level instance. However, each side has a specific way to spawn a particle. If on the ClientLevel, #addParticle can be called to spawn a particle or #addAlwaysVisibleParticle can be called to spawn a particle that is visible from any distance. If on the ServerLevel, #sendParticles can be called to send a packet to the client to spawn the particle. Calling the two ClientLevel methods on the server will result in nothing.
Game Effects: Sounds
Sounds
Terminology
| Term | Description |
|---|---|
| Sound Events | Something that triggers a sound effect. Examples include minecraft:block.anvil.hit or botania:spreader_fire. |
| Sound Category | The category of the sound, for example player, block or simply master. The sliders in the sound settings GUI represent these categories. |
| Sound File | The literal file on disk that is played: an .ogg file. |
sounds.json
This JSON defines sound events, and defines which sound files they play, the subtitle, etc. Sound events are identified with ResourceLocations. sounds.json should be located at the root of a resource namespace (assets/<namespace>/sounds.json), and it defines sound events in that namespace (assets/<namespace>/sounds.json defines sound events in the namespace namespace.).
A full specification is available on the vanilla wiki, but this example highlights the important parts:
{
"open_chest": {
"subtitle": "mymod.subtitle.open_chest",
"sounds": [ "mymod:open_chest_sound_file" ]
},
"epic_music": {
"sounds": [
{
"name": "mymod:music/epic_music",
"stream": true
}
]
}
}
Underneath the top-level object, each key corresponds to a sound event. Note that the namespace is not given, as it is taken from the namespace of the JSON itself. Each event specifies a localization key to be shown when subtitles are enabled. Finally, the actual sound files to be played are specified. Note that the value is an array; if multiple sound files are specified, the game will randomly choose one to play whenever the sound event is triggered.
The two examples represent two different ways to specify a sound file. The wiki has precise details, but generally, long sound files such as background music or music discs should use the second form, because the “stream” argument tells Minecraft to not load the entire sound file into memory but to stream it from disk. The second form can also specify the volume, pitch, and weight of a sound file.
In all cases, the path to a sound file for namespace namespace and path path is assets/<namespace>/sounds/<path>.ogg. Therefore mymod:open_chest_sound_file points to assets/mymod/sounds/open_chest_sound_file.ogg, and mymod:music/epic_music points to assets/mymod/sounds/music/epic_music.ogg.
A sounds.json can be data generated.
Creating Sound Events
In order to reference sounds on the server, a SoundEvent holding a corresponding entry in sounds.json must be created. This SoundEvent must then be registered. Normally, the location used to create a sound event should be set as it’s registry name.
The SoundEvent acts as a reference to the sound and is passed around to play them. If a mod has an API, it should expose its SoundEvents in the API.
Note
As long as a sound is registered within the sounds.json, it can still be referenced on the logical client regardless of whether there is a referencing SoundEvent.
Playing Sounds
Vanilla has lots of methods for playing sounds, and it is unclear which to use at times.
Note that each takes a SoundEvent, the ones registered above. Additionally, the terms “Server Behavior” and “Client Behavior” refer to the respective logical side.
Level
playSound(Player, BlockPos, SoundEvent, SoundSource, volume, pitch)
-
Simply forwards to overload (2), adding 0.5 to each coordinate of the
BlockPosgiven. 2.playSound(Player, double x, double y, double z, SoundEvent, SoundSource, volume, pitch) -
Client Behavior: If the passed in player is the client player, plays the sound event to the client player.
- Server Behavior: Plays the sound event to everyone nearby except the passed in player. Player can be
null. -
Usage: The correspondence between the behaviors implies that these two methods are to be called from some player-initiated code that will be run on both logical sides at the same time: the logical client handles playing it to the user, and the logical server handles everyone else hearing it without re-playing it to the original user. They can also be used to play any sound in general at any position server-side by calling it on the logical server and passing in a
nullplayer, thus letting everyone hear it. 3.playLocalSound(double x, double y, double z, SoundEvent, SoundSource, volume, pitch, distanceDelay) -
Client Behavior: Just plays the sound event in the client level. If
distanceDelayistrue, then delays the sound based on how far it is from the player. - Server Behavior: Does nothing.
- Usage: This method only works client-side, and thus is useful for sounds sent in custom packets, or other client-only effect-type sounds. Used for thunder.
ClientLevel
playLocalSound(BlockPos, SoundEvent, SoundSource, volume, pitch, distanceDelay)- Simply forwards toLevel’s overload (3), adding 0.5 to each coordinate of theBlockPosgiven.
Entity
playSound(SoundEvent, volume, pitch)- Forwards toLevel’s overload (2), passing innullas the player. - Client Behavior: Does nothing. - Server Behavior: Plays the sound event to everyone at this entity’s position. - Usage: Emitting any sound from any non-player entity server-side.
Player
playSound(SoundEvent, volume, pitch)(overriding the one inEntity) - Forwards toLevel’s overload (2), passing inthisas the player. - Client Behavior: Does nothing, see override inLocalPlayer. - Server Behavior: Plays the sound to everyone nearby except this player. - Usage: SeeLocalPlayer.
LocalPlayer
playSound(SoundEvent, volume, pitch)(overriding the one inPlayer) - Forwards toLevel’s overload (2), passing inthisas the player. - Client Behavior: Just plays the Sound Event. - Server Behavior: Method is client-only. - Usage: Just like the ones inLevel, these two overrides in the player classes seem to be for code that runs together on both sides. The client handles playing the sound to the user, while the server handles everyone else hearing it without re-playing to the original user.
Data Storage: Capabilities
The Capability System
Capabilities allow exposing features in a dynamic and flexible way without having to resort to directly implementing many interfaces.
In general terms, each capability provides a feature in the form of an interface.
Forge adds capability support to BlockEntities, Entities, ItemStacks, Levels, and LevelChunks, which can be exposed either by attaching them through an event or by overriding the capability methods in your own implementations of the objects. This will be explained in more detail in the following sections.
Forge-provided Capabilities
Forge provides three capabilities: IItemHandler, IFluidHandler and IEnergyStorage
IItemHandler exposes an interface for handling inventory slots. It can be applied to BlockEntities (chests, machines, etc.), Entities (extra player slots, mob/creature inventories/bags), or ItemStacks (portable backpacks and such). It replaces the old Container and WorldlyContainer with an automation-friendly system.
IFluidHandler exposes an interface for handling fluid inventories. It can also be applied to BlockEntities, Entities, or ItemStacks.
IEnergyStorage exposes an interface for handling energy containers. It can be applied to BlockEntities, Entities, or ItemStacks. It is based on the RedstoneFlux API by TeamCoFH.
Using an Existing Capability
As mentioned earlier, BlockEntities, Entities, and ItemStacks implement the capability provider feature through the ICapabilityProvider interface. This interface adds the method #getCapability, which can be used to query the capabilities present in the associated provider objects.
In order to obtain a capability, you will need to refer it by its unique instance. In the case of the IItemHandler, this capability is primarily stored in ForgeCapabilities#ITEM_HANDLER, but it is possible to get other instance references by using CapabilityManager#get
public static final Capability<IItemHandler> ITEM_HANDLER = CapabilityManager.get(new CapabilityToken<>(){});
When called, CapabilityManager#get provides a non-null capability for your associated type. The anonymous CapabilityToken allows Forge to keep a soft dependency system while still having the necessary generic information to get the correct capability.
Important
Even if you have a non-null capability available to you at all times, it does not mean the capability itself is usable or registered yet. This can be checked via Capability#isRegistered.
The #getCapability method has a second parameter, of type Direction, which can be used to request the specific instance for that one face. If passed null, it can be assumed that the request comes either from within the block or from some place where the side has no meaning, such as a different dimension. In this case a general capability instance that does not care about sides will be requested instead. The return type of #getCapability will correspond to a LazyOptional of the type declared in the capability passed to the method. For the Item Handler capability, this is LazyOptional<IItemHandler>. If the capability is not available for a particular provider, it will return an empty LazyOptional instead.
Exposing a Capability
In order to expose a capability, you will first need an instance of the underlying capability type. Note that you should assign a separate instance to each object that keeps the capability, since the capability will most probably be tied to the containing object.
In the case of IItemHandler, the default implementation uses the ItemStackHandler class, which has an optional argument in the constructor, to specify a number of slots. However, relying on the existence of these default implementations should be avoided, as the purpose of the capability system is to prevent loading errors in contexts where the capability is not present, so instantiation should be protected behind a check testing if the capability has been registered (see the remarks about CapabilityManager#get in the previous section).
Once you have your own instance of the capability interface, you will want to notify users of the capability system that you expose this capability and provide a LazyOptional of the interface reference. This is done by overriding the #getCapability method, and comparing the capability instance with the capability you are exposing. If your machine has different slots based on which side is being queried, you can test this with the side parameter. For Entities and ItemStacks, this parameter can be ignored, but it is still possible to have side as a context, such as different armor slots on a player (Direction#UP exposing the player’s helmet slot), or about the surrounding blocks in the inventory (Direction#WEST exposing the input slot of a furnace). Do not forget to fall back to super, otherwise existing attached capabilities will stop working.
Capabilities must be invalidated at the end of the provider’s lifecycle via LazyOptional#invalidate. For owned BlockEntities and Entities, the LazyOptional can be invalidated within #invalidateCaps. For non-owned providers, a runnable supplying the invalidation should be passed into AttachCapabilitiesEvent#addListener.
// Somewhere in your BlockEntity subclass
LazyOptional<IItemHandler> inventoryHandlerLazyOptional;
// Supplied instance (e.g. () -> inventoryHandler)
// Ensure laziness as initialization should only happen when needed
inventoryHandlerLazyOptional = LazyOptional.of(inventoryHandlerSupplier);
@Override
public <T> LazyOptional<T> getCapability(Capability<T> cap, Direction side) {
if (cap == ForgeCapabilities.ITEM_HANDLER) {
return inventoryHandlerLazyOptional.cast();
}
return super.getCapability(cap, side);
}
@Override
public void invalidateCaps() {
super.invalidateCaps();
inventoryHandlerLazyOptional.invalidate();
}
Tip
If only one capability is exposed on a given object, you can use Capability#orEmpty as an alternative to the if/else statement.
@Override
public <T> LazyOptional<T> getCapability(Capability<T> cap, Direction side) {
return ForgeCapabilities.ITEM_HANDLER.orEmpty(cap, inventoryHandlerLazyOptional);
}
Items are a special case since their capability providers are stored on an ItemStack. Instead, a provider should be attached through Item#initCapabilities. This should hold your capabilities for the lifecycle of the stack.
It is strongly suggested that direct checks in code are used to test for capabilities instead of attempting to rely on maps or other data structures, since capability tests can be done by many objects every tick, and they need to be as fast as possible in order to avoid slowing down the game.
Attaching Capabilities
As mentioned, attaching capabilities to existing providers, Levels, and LevelChunks can be done using AttachCapabilitiesEvent. The same event is used for all objects that can provide capabilities. AttachCapabilitiesEvent has 5 valid generic types providing the following events:
AttachCapabilitiesEvent<Entity>: Fires only for entities.AttachCapabilitiesEvent<BlockEntity>: Fires only for block entities.AttachCapabilitiesEvent<ItemStack>: Fires only for item stacks.AttachCapabilitiesEvent<Level>: Fires only for levels.AttachCapabilitiesEvent<LevelChunk>: Fires only for level chunks.
The generic type cannot be more specific than the above types. For example: If you want to attach capabilities to Player, you have to subscribe to the AttachCapabilitiesEvent<Entity>, and then determine that the provided object is an Player before attaching the capability.
In all cases, the event has a method #addCapability which can be used to attach capabilities to the target object. Instead of adding capabilities themselves to the list, you add capability providers, which have the chance to return capabilities only from certain sides. While the provider only needs to implement ICapabilityProvider, if the capability needs to store data persistently, it is possible to implement ICapabilitySerializable<T extends Tag> which, on top of returning the capabilities, will provide tag save/load functions.
For information on how to implement ICapabilityProvider, refer to the Exposing a Capability section.
Creating Your Own Capability
A capability can be registered using one of two ways: RegisterCapabilitiesEvent or @AutoRegisterCapability.
RegisterCapabilitiesEvent
A capability can be registered using RegisterCapabilitiesEvent by supplying the class of the capability type to the #register method. The event is handled on the mod event bus.
@SubscribeEvent
public void registerCaps(RegisterCapabilitiesEvent event) {
event.register(IExampleCapability.class);
}
@AutoRegisterCapability
A capability is registered using @AutoRegisterCapability by annotating the capability type.
@AutoRegisterCapability
public interface IExampleCapability {
// ...
}
Persisting LevelChunk and BlockEntity capabilities
Unlike Levels, Entities, and ItemStacks, LevelChunks and BlockEntities are only written to disk when they have been marked as dirty. A capability implementation with persistent state for a LevelChunk or a BlockEntity should therefore ensure that whenever its state changes, its owner is marked as dirty.
ItemStackHandler, commonly used for inventories in BlockEntities, has an overridable method void onContentsChanged(int slot) designed to be used to mark the BlockEntity as dirty.
public class MyBlockEntity extends BlockEntity {
private final IItemHandler inventory = new ItemStackHandler(...) {
@Override
protected void onContentsChanged(int slot) {
super.onContentsChanged(slot);
setChanged();
}
}
// ...
}
Synchronizing Data with Clients
By default, capability data is not sent to clients. In order to change this, the mods have to manage their own synchronization code using packets.
There are three different situations in which you may want to send synchronization packets, all of them optional:
- When the entity spawns in the level, or the block is placed, you may want to share the initialization-assigned values with the clients.
- When the stored data changes, you may want to notify some or all of the watching clients.
- When a new client starts viewing the entity or block, you may want to notify it of the existing data.
Refer to the Networking page for more information on implementing network packets.
Persisting across Player Deaths
By default, the capability data does not persist on death. In order to change this, the data has to be manually copied when the player entity is cloned during the respawn process.
This can be done via PlayerEvent$Clone by reading the data from the original entity and assigning it to the new entity. In this event, the #isWasDeath method can be used to distinguish between respawning after death and returning from the End. This is important because the data will already exist when returning from the End, so care has to be taken to not duplicate values in this case.
Data Storage: Saved Data
Saved Data
The Saved Data (SD) system is an alternative to level capabilities that can attach data per level.
Declaration
Each SD implementation must subtype the SavedData class. There are two important methods to be aware of:
save: Allows the implementation to write NBT data to the level.setDirty: A method that must be called after changing the data, to notify the game that there are changes that need to be written. If not called,#savewill not get called and the existing data will persist.
Attaching to a Level
Any SavedData is loaded and/or attached to a level dynamically. As such, if one is never created on a level, then it will not exist.
SavedDatas are created and loaded from the DimensionDataStorage, which can be accessed by either ServerChunkCache#getDataStorage or ServerLevel#getDataStorage. From there, you can get or create an instance of your SD by calling DimensionDataStorage#computeIfAbsent. This will attempt to get the current instance of the SD if present or create a new one and load all available data.
DimensionDataStorage#computeIfAbsent takes in three arguments: a function to load NBT data into a SD and return it, a supplier to construct a new instance of the SD, and the name of the .dat file stored within the data folder for the implemented level.
For example, if a SD was named “example” within the Nether, then a file would be created at ./<level_folder>/DIM-1/data/example.dat and would be implemented like so:
// In some class
public ExampleSavedData create() {
return new ExampleSavedData();
}
public ExampleSavedData load(CompoundTag tag) {
ExampleSavedData data = this.create();
// Load saved data
return data;
}
// In some method within the class
netherDataStorage.computeIfAbsent(this::load, this::create, "example");
To persist a SD across levels, a SD should be attached to the Overworld, which can be obtained from MinecraftServer#overworld. The Overworld is the only dimension that is never fully unloaded and as such makes it perfect to store multi-level data on.
Data Storage: Codecs
Codecs
Codecs are a serialization tool from Mojang’s DataFixerUpper used to describe how objects can be transformed between different formats, such as JsonElements for JSON and Tags for NBT.
Using Codecs
Codecs are primarily used to encode, or serialize, Java objects to some data format type and decode, or deserialize, formatted data objects back to its associated Java type. This is typically accomplished using Codec#encodeStart and Codec#parse, respectively.
DynamicOps
To determine what intermediate file format to encode and decode to, both #encodeStart and #parse require a DynamicOps instance to define the data within that format.
The DataFixerUpper library contains JsonOps to codec JSON data stored in Gson’s JsonElement instances. JsonOps supports two versions of JsonElement serialization: JsonOps#INSTANCE which defines a standard JSON file, and JsonOps#COMPRESSED which allows data to be compressed into a single string.
// Let exampleCodec represent a Codec<ExampleJavaObject>
// Let exampleObject be a ExampleJavaObject
// Let exampleJson be a JsonElement
// Encode Java object to regular JsonElement
exampleCodec.encodeStart(JsonOps.INSTANCE, exampleObject);
// Encode Java object to compressed JsonElement
exampleCodec.encodeStart(JsonOps.COMPRESSED, exampleObject);
// Decode JsonElement into Java object
// Assume JsonElement was parsed normally
exampleCodec.parse(JsonOps.INSTANCE, exampleJson);
Minecraft also provides NbtOps to codec NBT data stored in Tag instances. This can be referenced using NbtOps#INSTANCE.
// Let exampleCodec represent a Codec<ExampleJavaObject>
// Let exampleObject be a ExampleJavaObject
// Let exampleNbt be a Tag
// Encode Java object to Tag
exampleCodec.encodeStart(JsonOps.INSTANCE, exampleObject);
// Decode Tag into Java object
exampleCodec.parse(JsonOps.INSTANCE, exampleNbt);
Format Conversion
DynamicOps can also be used separately to convert between two different encoded formats. This can be done using #convertTo and supplying the DynamicOps format and the encoded object to convert.
// Convert Tag to JsonElement
// Let exampleTag be a Tag
JsonElement convertedJson = NbtOps.INSTANCE.convertTo(JsonOps.INSTANCE, exampleTag);
DataResult
Encoded or decoded data using codecs return a DataResult which holds the converted instance or some error data depending on whether the conversion was successful. When the conversion is successful, the Optional supplied by #result will contain the successfully converted object. If the conversion fails, the Optional supplied by #error will contain the PartialResult, which holds the error message and a partially converted object depending on the codec.
Additionally, there are many methods on DataResult that can be used to transform the result or error into the desired format. For example, #resultOrPartial will return an Optional containing the result on success, and the partially converted object on failure. The method takes in a string consumer to determine how to report the error message if present.
// Let exampleCodec represent a Codec<ExampleJavaObject>
// Let exampleJson be a JsonElement
// Decode JsonElement into Java object
DataResult<ExampleJavaObject> result = exampleCodec.parse(JsonOps.INSTANCE, exampleJson);
result
// Get result or partial on error, report error message
.resultOrPartial(errorMessage -> /* Do something with error message */)
// If result or partial is present, do something
.ifPresent(decodedObject -> /* Do something with decoded object */);
Existing Codecs
Primitives
The Codec class contains static instances of codecs for certain defined primitives.
| Codec | Java Type |
|---|---|
BOOL |
Boolean |
BYTE |
Byte |
SHORT |
Short |
INT |
Integer |
LONG |
Long |
FLOAT |
Float |
DOUBLE |
Double |
STRING |
String |
BYTE_BUFFER |
ByteBuffer |
INT_STREAM |
IntStream |
LONG_STREAM |
LongStream |
PASSTHROUGH |
Dynamic<?>* |
EMPTY |
Unit** |
* Dynamic is an object which holds a value encoded in a supported DynamicOps format. These are typically used to convert encoded object formats into other encoded object formats.
** Unit is an object used to represent null objects.
Vanilla and Forge
Minecraft and Forge define many codecs for objects that are frequently encoded and decoded. Some examples include ResourceLocation#CODEC for ResourceLocations, ExtraCodecs#INSTANT_ISO8601 for Instants in the DateTimeFormatter#ISO_INSTANT format, and CompoundTag#CODEC for CompoundTags.
Warning
CompoundTags cannot decode lists of numbers from JSON using JsonOps. JsonOps, when converting, sets a number to its most narrow type. ListTags force a specific type for its data, so numbers with different types (e.g. 64 would be byte, 384 would be short) will throw an error on conversion.
Vanilla and Forge registries also have codecs for the type of object the registry contains (e.g. Registry#BLOCK or ForgeRegistries#BLOCKS have a Codec<Block>). Registry#byNameCodec and IForgeRegistry#getCodec will encode the registry object to their registry name, or an integer identifier if compressed. Vanilla registries also have a Registry#holderByNameCodec which encodes to a registry name and decodes to the registry object wrapped in a Holder.
Creating Codecs
Codecs can be created for encoding and decoding any object. For understanding purposes, the equivalent encoded JSON will be shown.
Records
Codecs can define objects through the use of records. Each record codec defines any object with explicit named fields. There are many ways to create a record codec, but the simplest is via RecordCodecBuilder#create.
RecordCodecBuilder#create takes in a function which defines an Instance and returns an application (App) of the object. A correlation can be drawn to creating a class instance and the constructors used to apply the class to the constructed object.
// Some object to create a codec for
public class SomeObject {
public SomeObject(String s, int i, boolean b) { /* ... */ }
public String s() { /* ... */ }
public int i() { /* ... */ }
public boolean b() { /* ... */ }
}
Fields
An Instance can define up to 16 fields using #group. Each field must be an application defining the instance the object is being made for and the type of the object. The simplest way to meet this requirement is by taking a Codec, setting the name of the field to decode from, and setting the getter used to encode the field.
A field can be created from a Codec using #fieldOf, if the field is required, or #optionalFieldOf, if the field is wrapped in an Optional or defaulted. Either method requires a string containing the name of the field in the encoded object. The getter used to encode the field can then be set using #forGetter, taking in a function which given the object, returns the field data.
From there, the resulting product can be applied via #apply to define how the instance should construct the object for the application. For ease of convenience, the grouped fields should be listed in the same order they appear in the constructor such that the function can simply be a constructor method reference.
public static final Codec<SomeObject> RECORD_CODEC = RecordCodecBuilder.create(instance -> // Given an instance
instance.group( // Define the fields within the instance
Codec.STRING.fieldOf("s").forGetter(SomeObject::s), // String
Codec.INT.optionalFieldOf("i", 0).forGetter(SomeObject::i), // Integer, defaults to 0 if field not present
Codec.BOOL.fieldOf("b").forGetter(SomeObject::b) // Boolean
).apply(instance, SomeObject::new) // Define how to create the object
);
// Encoded SomeObject
{
"s": "value",
"i": 5,
"b": false
}
// Another encoded SomeObject
{
"s": "value2",
// i is omitted, defaults to 0
"b": true
}
Transformers
Codecs can be transformed into equivalent, or partially equivalent, representations through mapping methods. Each mapping method takes in two functions: one to transform the current type into the new type, and one to transform the new type back to the current type. This is done through the #xmap function.
// A class
public class ClassA {
public ClassB toB() { /* ... */ }
}
// Another equivalent class
public class ClassB {
public ClassA toA() { /* ... */ }
}
// Assume there is some codec A_CODEC
public static final Codec<ClassB> B_CODEC = A_CODEC.xmap(ClassA::toB, ClassB::toA);
If a type is partially equivalent, meaning that there are some restrictions during conversion, there are mapping functions which return a DataResult which can be used to return an error state whenever an exception or invalid state is reached.
| Is A Fully Equivalent to B | Is B Fully Equivalent to A | Transform Method |
|---|---|---|
| Yes | Yes | #xmap |
| Yes | No | #flatComapMap |
| No | Yes | #comapFlatMap |
| No | No | #flatXMap |
// Given an string codec to convert to a integer
// Not all strings can become integers (A is not fully equivalent to B)
// All integers can become strings (B is fully equivalent to A)
public static final Codec<Integer> INT_CODEC = Codec.STRING.comapFlatMap(
s -> { // Return data result containing error on failure
try {
return DataResult.success(Integer.valueOf(s));
} catch (NumberFormatException e) {
return DataResult.error(s + " is not an integer.");
}
},
Integer::toString // Regular function
);
// Will return 5
"5"
// Will error, not an integer
"value"
Range Codecs
Range codecs are an implementation of #flatXMap which returns an error DataResult if the value is not inclusively between the set minimum and maximum. The value is still provided as a partial result if outside the bounds. There are implementations for integers, floats, and doubles via #intRange, #floatRange, and #doubleRange respectively.
public static final Codec<Integer> RANGE_CODEC = Codec.intRange(0, 4);
// Will be valid, inside [0, 4]
4
// Will error, outside [0, 4]
5
Defaults
If the result of encoding or decoding fails, a default value can be supplied instead via Codec#orElse or Codec#orElseGet.
public static final Codec<Integer> DEFAULT_CODEC = Codec.INT.orElse(0); // Can also be a supplied value via #orElseGet
// Not an integer, defaults to 0
"value"
Unit
A codec which supplies an in-code value and encodes to nothing can be represented using Codec#unit. This is useful if a codec uses a non-encodable entry within the data object.
public static final Codec<IForgeRegistry<Block>> UNIT_CODEC = Codec.unit(
() -> ForgeRegistries.BLOCKS // Can also be a raw value
);
// Nothing here, will return block registry codec
List
A codec for a list of objects can be generated from an object codec via Codec#listOf.
// BlockPos#CODEC is a Codec<BlockPos>
public static final Codec<List<BlockPos>> LIST_CODEC = BlockPos.CODEC.listOf();
// Encoded List<BlockPos>
[
[1, 2, 3], // BlockPos(1, 2, 3)
[4, 5, 6], // BlockPos(4, 5, 6)
[7, 8, 9] // BlockPos(7, 8, 9)
]
List objects decoded using a list codec are stored in an immutable list. If a mutable list is needed, a transformer should be applied to the list codec.
Map
A codec for a map of keys and value objects can be generated from two codecs via Codec#unboundedMap. Unbounded maps can specify any string-based or string-transformed value to be a key.
// BlockPos#CODEC is a Codec<BlockPos>
public static final Codec<Map<String, BlockPos>> MAP_CODEC = Codec.unboundedMap(Codec.STRING, BlockPos.CODEC);
// Encoded Map<String, BlockPos>
{
"key1": [1, 2, 3], // key1 -> BlockPos(1, 2, 3)
"key2": [4, 5, 6], // key2 -> BlockPos(4, 5, 6)
"key3": [7, 8, 9] // key3 -> BlockPos(7, 8, 9)
}
Map objects decoded using a unbounded map codec are stored in an immutable map. If a mutable map is needed, a transformer should be applied to the map codec.
Warning
Unbounded maps only support keys that encode/decode to/from strings. A key-value pair list codec can be used to get around this restriction.
Pair
A codec for pairs of objects can be generated from two codecs via Codec#pair.
A pair codec decodes objects by first decoding the left object in the pair, then taking the remaining part of the encoded object and decodes the right object from that. As such, the codecs must either express something about the encoded object after decoding (such as records), or they have to be augmented into a MapCodec and transformed into a regular codec via #codec. This can typically done by making the codec a field of some object.
public static final Codec<Pair<Integer, String>> PAIR_CODEC = Codec.pair(
Codec.INT.fieldOf("left").codec(),
Codec.STRING.fieldOf("right").codec()
);
// Encoded Pair<Integer, String>
{
"left": 5, // fieldOf looks up 'left' key for left object
"right": "value" // fieldOf looks up 'right' key for right object
}
Tip
A map codec with a non-string key can be encoded/decoded using a list of key-value pairs applied with a transformer.
Either
A codec for two different methods of encoding/decoding some object data can be generated from two codecs via Codec#either.
An either codec attempts to decode the object using the first codec. If it fails, it attempts to decode using the second codec. If that also fails, then the DataResult will only contain the error from the second codec failure.
public static final Codec<Either<Integer, String>> EITHER_CODEC = Codec.either(
Codec.INT,
Codec.STRING
);
// Encoded Either$Left<Integer, String>
5
// Encoded Either$Right<Integer, String>
"value"
Tip
This can be used in conjunction with a transformer to get a specific object from two different methods of encoding.
Dispatch
Codecs can have subcodecs which can decode a particular object based upon some specified type via Codec#dispatch. This is typically used in registries which contain codecs, such as rule tests or block placers.
A dispatch codec first attempts to get the encoded type from some string key (usually type). From there, the type is decoded, calling a getter for the specific codec used to decode the actual object. If the DynamicOps used to decode the object compresses its maps, or the object codec itself is not augmented into a MapCodec (such as records or fielded primitives), then the object needs to be stored within a value key. Otherwise, the object is decoded at the same level as the rest of the data.
// Define our object
public abstract class ExampleObject {
// Define the method used to specify the object type for encoding
public abstract Codec<? extends ExampleObject> type();
}
// Create simple object which stores a string
public class StringObject extends ExampleObject {
public StringObject(String s) { /* ... */ }
public String s() { /* ... */ }
public Codec<? extends ExampleObject> type() {
// A registered registry object
// "string":
// Codec.STRING.xmap(StringObject::new, StringObject::s)
return STRING_OBJECT_CODEC.get();
}
}
// Create complex object which stores a string and integer
public class ComplexObject extends ExampleObject {
public ComplexObject(String s, int i) { /* ... */ }
public String s() { /* ... */ }
public int i() { /* ... */ }
public Codec<? extends ExampleObject> type() {
// A registered registry object
// "complex":
// RecordCodecBuilder.create(instance ->
// instance.group(
// Codec.STRING.fieldOf("s").forGetter(ComplexObject::s),
// Codec.INT.fieldOf("i").forGetter(ComplexObject::i)
// ).apply(instance, ComplexObject::new)
// )
return COMPLEX_OBJECT_CODEC.get();
}
}
// Assume there is an IForgeRegistry<Codec<? extends ExampleObject>> DISPATCH
public static final Codec<ExampleObject> = DISPATCH.getCodec() // Gets Codec<Codec<? extends ExampleObject>>
.dispatch(
ExampleObject::type, // Get the codec from the specific object
Function.identity() // Get the codec from the registry
);
// Simple object
{
"type": "string", // For StringObject
"value": "value" // Codec type is not augmented from MapCodec, needs field
}
// Complex object
{
"type": "complex", // For ComplexObject
// Codec type is augmented from MapCodec, can be inlined
"s": "value",
"i": 0
}
GUI: Menus
Menus
Menus are one type of backend for Graphical User Interfaces, or GUIs; they handle the logic involved in interacting with some represented data holder. Menus themselves are not data holders. They are views which allow to user to indirectly modify the internal data holder state. As such, a data holder should not be directly coupled to any menu, instead passing in the data references to invoke and modify.
MenuType
Menus are created and removed dynamically and as such are not registry objects. As such, another factory object is registered instead to easily create and refer to the type of the menu. For a menu, these are MenuTypes.
MenuTypes must be registered.
MenuSupplier
A MenuType is created by passing in a MenuSupplier and a FeatureFlagSet to its constructor. A MenuSupplier represents a function which takes in the id of the container and the inventory of the player viewing the menu, and returns a newly created AbstractContainerMenu.
// For some DeferredRegister<MenuType<?>> REGISTER
public static final RegistryObject<MenuType<MyMenu>> MY_MENU = REGISTER.register("my_menu", () -> new MenuType(MyMenu::new, FeatureFlags.DEFAULT_FLAGS));
// In MyMenu, an AbstractContainerMenu subclass
public MyMenu(int containerId, Inventory playerInv) {
super(MY_MENU.get(), containerId);
// ...
}
Note
The container identifier is unique for an individual player. This means that the same container id on two different players will represent two different menus, even if they are viewing the same data holder.
The MenuSupplier is usually responsible for creating a menu on the client with dummy data references used to store and interact with the synced information from the server data holder.
IContainerFactory
If additional information is needed on the client (e.g. the position of the data holder in the world), then the subclass IContainerFactory can be used instead. In addition to the container id and the player inventory, this also provides a FriendlyByteBuf which can store additional information that was sent from the server. A MenuType can be created using an IContainerFactory via IForgeMenuType#create.
// For some DeferredRegister<MenuType<?>> REGISTER
public static final RegistryObject<MenuType<MyMenuExtra>> MY_MENU_EXTRA = REGISTER.register("my_menu_extra", () -> IForgeMenuType.create(MyMenu::new));
// In MyMenuExtra, an AbstractContainerMenu subclass
public MyMenuExtra(int containerId, Inventory playerInv, FriendlyByteBuf extraData) {
super(MY_MENU_EXTRA.get(), containerId);
// Store extra data from buffer
// ...
}
AbstractContainerMenu
All menus are extended from AbstractContainerMenu. A menu takes in two parameters, the MenuType, which represents the type of the menu itself, and the container id, which represents the unique identifier of the menu for the current accessor.
Important
The player can only have 100 unique menus open at once.
Each menu should contain two constructors: one used to initialize the menu on the server and one used to initialize the menu on the client. The constructor used to initialize the menu on the client is the one supplied to the MenuType. Any fields that the server menu constructor contains should have some default for the client menu constructor.
// Client menu constructor
public MyMenu(int containerId, Inventory playerInventory) {
this(containerId, playerInventory);
}
// Server menu constructor
public MyMenu(int containerId, Inventory playerInventory) {
// ...
}
Each menu implementation must implement two methods: #stillValid and #quickMoveStack.
#stillValid and ContainerLevelAccess
#stillValid determines whether the menu should remain open for a given player. This is typically directed to the static #stillValid which takes in a ContainerLevelAccess, the player, and the Block this menu is attached to. The client menu must always return true for this method, which the static #stillValid does default to. This implementation checks whether the player is within eight blocks of where the data storage object is located.
A ContainerLevelAccess supplies the current level and location of the block within an enclosed scope. When constructing the menu on the server, a new access can be created by calling ContainerLevelAccess#create. The client menu constructor can pass in ContainerLevelAccess#NULL, which will do nothing.
// Client menu constructor
public MyMenuAccess(int containerId, Inventory playerInventory) {
this(containerId, playerInventory, ContainerLevelAccess.NULL);
}
// Server menu constructor
public MyMenuAccess(int containerId, Inventory playerInventory, ContainerLevelAccess access) {
// ...
}
// Assume this menu is attached to RegistryObject<Block> MY_BLOCK
@Override
public boolean stillValid(Player player) {
return AbstractContainerMenu.stillValid(this.access, player, MY_BLOCK.get());
}
Data Synchronization
Some data needs to be present on both the server and the client to display to the player. To do this, the menu implements a basic layer of data synchronization such that whenever the current data does not match the data last synced to the client. For players, this is checked every tick.
Minecraft supports two forms of data synchronization by default: ItemStacks via Slots and integers via DataSlots. Slots and DataSlots are views which hold references to data storages that can be be modified by the player in a screen, assuming the action is valid. These can be added to a menu within the constructor through #addSlot and #addDataSlot.
Note
Since Containers used by Slots are deprecated by Forge in favor of using the IItemHandler capability, the rest of the explanation will revolve around using the capability variant: SlotItemHandler.
A SlotItemHandler contains four parameters: the IItemHandler representing the inventory the stacks are within, the index of the stack this slot is specifically representing, and the x and y position of where the top-left position of the slot will render on the screen relative to AbstractContainerScreen#leftPos and #topPos. The client menu constructor should always supply an empty instance of an inventory of the same size.
In most cases, any slots the menu contains is first added, followed by the player’s inventory, and finally concluded with the player’s hotbar. To access any individual Slot from the menu, the index must be calculated based upon the order of which slots were added.
A DataSlot is an abstract class which should implement a getter and setter to reference the data stored in the data storage object. The client menu constructor should always supply a new instance via DataSlot#standalone.
These, along with slots, should be recreated every time a new menu is initialized.
Warning
Although a DataSlot stores an integer, it is effectively limited to a short (-32768 to 32767) because of how it sends the value across the network. The 16 high-order bits of the integer are ignored.
// Assume we have an inventory from a data object of size 5
// Assume we have a DataSlot constructed on each initialization of the server menu
// Client menu constructor
public MyMenuAccess(int containerId, Inventory playerInventory) {
this(containerId, playerInventory, new ItemStackHandler(5), DataSlot.standalone());
}
// Server menu constructor
public MyMenuAccess(int containerId, Inventory playerInventory, IItemHandler dataInventory, DataSlot dataSingle) {
// Check if the data inventory size is some fixed value
// Then, add slots for data inventory
this.addSlot(new SlotItemHandler(dataInventory, /*...*/));
// Add slots for player inventory
this.addSlot(new Slot(playerInventory, /*...*/));
// Add data slots for handled integers
this.addDataSlot(dataSingle);
// ...
}
ContainerData
If multiple integers need to be synced to the client, a ContainerData can be used to reference the integers instead. This interface functions as an index lookup such that each index represents a different integer. ContainerDatas can also be constructed in the data object itself if the ContainerData is added to the menu through #addDataSlots. The method creates a new DataSlot for the amount of data specified by the interface. The client menu constructor should always supply a new instance via SimpleContainerData.
// Assume we have a ContainerData of size 3
// Client menu constructor
public MyMenuAccess(int containerId, Inventory playerInventory) {
this(containerId, playerInventory, new SimpleContainerData(3));
}
// Server menu constructor
public MyMenuAccess(int containerId, Inventory playerInventory, ContainerData dataMultiple) {
// Check if the ContainerData size is some fixed value
checkContainerDataCount(dataMultiple, 3);
// Add data slots for handled integers
this.addDataSlots(dataMultiple);
// ...
}
Warning
As ContainerData delegates to DataSlots, these are also limited to a short (-32768 to 32767).
#quickMoveStack
#quickMoveStack is the second method that must be implemented by any menu. This method is called whenever a stack has been shift-clicked, or quick moved, out of its current slot until the stack has been fully moved out of its previous slot or there is no other place for the stack to go. The method returns a copy of the stack in the slot being quick moved.
Stacks are typically moved between slots using #moveItemStackTo, which moves the stack into the first available slot. It takes in the stack to be moved, the first slot index (inclusive) to try and move the stack to, the last slot index (exclusive), and whether to check the slots from first to last (when false) or from last to first (when true).
Across Minecraft implementations, this method is fairly consistent in its logic:
// Assume we have a data inventory of size 5
// The inventory has 4 inputs (index 1 - 4) which outputs to a result slot (index 0)
// We also have the 27 player inventory slots and the 9 hotbar slots
// As such, the actual slots are indexed like so:
// - Data Inventory: Result (0), Inputs (1 - 4)
// - Player Inventory (5 - 31)
// - Player Hotbar (32 - 40)
@Override
public ItemStack quickMoveStack(Player player, int quickMovedSlotIndex) {
// The quick moved slot stack
ItemStack quickMovedStack = ItemStack.EMPTY;
// The quick moved slot
Slot quickMovedSlot = this.slots.get(quickMovedSlotIndex)
// If the slot is in the valid range and the slot is not empty
if (quickMovedSlot != null && quickMovedSlot.hasItem()) {
// Get the raw stack to move
ItemStack rawStack = quickMovedSlot.getItem();
// Set the slot stack to a copy of the raw stack
quickMovedStack = rawStack.copy();
/*
The following quick move logic can be simplified to if in data inventory,
try to move to player inventory/hotbar and vice versa for containers
that cannot transform data (e.g. chests).
*/
// If the quick move was performed on the data inventory result slot
if (quickMovedSlotIndex == 0) {
// Try to move the result slot into the player inventory/hotbar
if (!this.moveItemStackTo(rawStack, 5, 41, true)) {
// If cannot move, no longer quick move
return ItemStack.EMPTY;
}
// Perform logic on result slot quick move
slot.onQuickCraft(rawStack, quickMovedStack);
}
// Else if the quick move was performed on the player inventory or hotbar slot
else if (quickMovedSlotIndex >= 5 && quickMovedSlotIndex < 41) {
// Try to move the inventory/hotbar slot into the data inventory input slots
if (!this.moveItemStackTo(rawStack, 1, 5, false)) {
// If cannot move and in player inventory slot, try to move to hotbar
if (quickMovedSlotIndex < 32) {
if (!this.moveItemStackTo(rawStack, 32, 41, false)) {
// If cannot move, no longer quick move
return ItemStack.EMPTY;
}
}
// Else try to move hotbar into player inventory slot
else if (!this.moveItemStackTo(rawStack, 5, 32, false)) {
// If cannot move, no longer quick move
return ItemStack.EMPTY;
}
}
}
// Else if the quick move was performed on the data inventory input slots, try to move to player inventory/hotbar
else if (!this.moveItemStackTo(rawStack, 5, 41, false)) {
// If cannot move, no longer quick move
return ItemStack.EMPTY;
}
if (rawStack.isEmpty()) {
// If the raw stack has completely moved out of the slot, set the slot to the empty stack
quickMovedSlot.set(ItemStack.EMPTY);
} else {
// Otherwise, notify the slot that that the stack count has changed
quickMovedSlot.setChanged();
}
/*
The following if statement and Slot#onTake call can be removed if the
menu does not represent a container that can transform stacks (e.g.
chests).
*/
if (rawStack.getCount() == quickMovedStack.getCount()) {
// If the raw stack was not able to be moved to another slot, no longer quick move
return ItemStack.EMPTY;
}
// Execute logic on what to do post move with the remaining stack
quickMovedSlot.onTake(player, rawStack);
}
return quickMovedStack; // Return the slot stack
}
Opening a Menu
Once a menu type has been registered, the menu itself has been finished, and a screen has been attached, a menu can then be opened by the player. Menus can be opened by calling ServerPlayer#openMenu on the logical server. The method takes in the MenuProvider of the server side menu, and optionally a FriendlyByteBuf if extra data needs to be synced to the client.
Note
ServerPlayer#openMenu with the FriendlyByteBuf parameter should only be used if a menu type was created using an IContainerFactory.
MenuProvider
A MenuProvider is an interface that contains two methods: #createMenu, which creates the server instance of the menu, and #getDisplayName, which returns a component containing the title of the menu to pass to the screen. The #createMenu method contains three parameter: the container id of the menu, the inventory of the player who opened the menu, and the player who opened the menu.
A MenuProvider can easily be created using SimpleMenuProvider, which takes in a method reference to create the server menu and the title of the menu.
// In some implementation
serverPlayer.openMenu(new SimpleMenuProvider(
(containerId, playerInventory, player) -> new MyMenu(containerId, playerInventory),
Component.translatable("menu.title.examplemod.mymenu")
));
Common Implementations
Menus are typically opened on a player interaction of some kind (e.g. when a block or entity is right-clicked).
Block Implementation
Blocks typically implement a menu by overriding BlockBehaviour#use. If on the logical client, the interaction returns InteractionResult#SUCCESS. Otherwise, it opens the menu and returns InteractionResult#CONSUME.
The MenuProvider should be implemented by overriding BlockBehaviour#getMenuProvider. Vanilla methods use this to view the menu in spectator mode.
// In some Block subclass
@Override
public MenuProvider getMenuProvider(BlockState state, Level level, BlockPos pos) {
return new SimpleMenuProvider(/* ... */);
}
@Override
public InteractionResult use(BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, BlockHitResult result) {
if (!level.isClientSide && player instanceof ServerPlayer serverPlayer) {
serverPlayer.openMenu(state.getMenuProvider(level, pos));
}
return InteractionResult.sidedSuccess(level.isClientSide);
}
Note
This is the simplest way to implement the logic, not the only way. If you want the block to only open the menu under certain conditions, then some data will need to be synced to the client beforehand to return InteractionResult#PASS or #FAIL if the conditions are not met.
Mob Implementation
Mobs typically implement a menu by overriding Mob#mobInteract. This is done similarly to the block implementation with the only difference being that the Mob itself should implement MenuProvider to support spectator mode viewing.
public class MyMob extends Mob implements MenuProvider {
// ...
@Override
public InteractionResult mobInteract(Player player, InteractionHand hand) {
if (!this.level.isClientSide && player instanceof ServerPlayer serverPlayer) {
serverPlayer.openMenu(this);
}
return InteractionResult.sidedSuccess(this.level.isClientSide);
}
}
Note
Once again, this is the simplest way to implement the logic, not the only way.
GUI: Screens
Screens
Screens are typically the base of all Graphical User Interfaces (GUIs) in Minecraft: taking in user input, verifying it on the server, and syncing the resulting action back to the client. They can be combined with menus to create an communication network for inventory-like views, or they can be standalone which modders can handle through their own network implementations.
Screens are made up of numerous parts, making it difficult to fully understand what a ‘screen’ actually is in Minecraft. As such, this document will go over each of the screen’s components and how it is applied before discussing the screen itself.
Relative Coordinates
Whenever anything is rendered, there needs to be some identifier which specifies where it will appear. With numerous abstractions, most of Minecraft’s rendering calls takes in an x, y, and z value in a coordinate plane. X values increase from left to right, y from top to bottom, and z from far to near. However, the coordinates are not fixed to a specified range. They can change depending on the size of the screen and the scale at which is specified within the options. As such, extra care must be taken to make sure the values of the coordinates while rendering scale properly to the changeable screen size.
Information on how to relativize your coordinates will be within the screen section.
Important
If you choose to use fixed coordinates or incorrectly scale the screen, the rendered objects may look strange or misplaced. An easy way to check if you relativized your coordinates correctly is to click the ‘Gui Scale’ button in your video settings. This value is used as the divisor to the width and height of your display when determining the scale at which a GUI should render.
Gui Graphics
Any GUI rendered by Minecraft is typically done using GuiGraphics. GuiGraphics is the first parameter to almost all rendering methods; it contains basic methods to render commonly used objects. These fall into five categories: colored rectangles, strings, and textures, items, and tooltips. There is also an additional method for rendering a snippet of a component (#enableScissor / #disableScissor). GuiGraphics also exposes the PoseStack which applies the transformations necessary to properly render where the component should be rendered. Additionally, colors are in the ARGB format.
Colored Rectangles
Colored rectangles are drawn through a position color shader. There are three types of colored rectangles that can be drawn.
First, there is a colored horizontal and vertical one-pixel wide line, #hLine and #vLine respectively. #hLine takes in two x coordinates defining the left and right (inclusively), the top y coordinate, and the color. #vLine takes in the left x coordinate, two y coordinates defining the top and bottom (inclusively), and the color.
Second, there is the #fill method, which draws a rectangle to the screen. The line methods internally call this method. This takes in the left x coordinate, the top y coordinate, the right x coordinate, the bottom y coordinate, and the color.
Finally, there is the #fillGradient method, which draws a rectangle with a vertical gradient. This takes in the right x coordinate, the bottom y coordinate, the left x coordinate, the top y coordinate, the z coordinate, and the bottom and top colors.
Strings
Strings are drawn through its Font, typically consisting of their own shaders for normal, see through, and offset mode. There are two alignment of strings that can be rendered, each with a back shadow: a left-aligned string (#drawString) and a center-aligned string (#drawCenteredString). These both take in the font the string will be rendered in, the string to draw, the x coordinate representing the left or center of the string respectively, the top y coordinate, and the color.
Note
Strings should typically be passed in as Components as they handle a variety of usecases, including the two other overloads of the method.
Textures
Textures are drawn through blitting, hence the method name #blit, which, for this purpose, copies the bits of an image and draws them directly to the screen. These are drawn through a position texture shader. While there are many different #blit overloads, we will only discuss two static #blits.
The first static #blit takes in six integers and assumes the texture being rendered is on a 256 x 256 PNG file. It takes in the left x and top y screen coordinate, the left x and top y coordinate within the PNG, and the width and height of the image to render.
Note
The size of the PNG file must be specified so that the coordinates can be normalized to obtain the associated UV values.
The static #blit which the first calls expands this to nine integers, only assuming the image is on a PNG file. It takes in the left x and top y screen coordinate, the z coordinate (referred to as the blit offset), the left x and top y coordinate within the PNG, the width and height of the image to render, and the width and height of the PNG file.
Blit Offset
The z coordinate when rendering a texture is typically set to the blit offset. The offset is responsible for properly layering renders when viewing a screen. Renders with a smaller z coordinate are rendered in the background and vice versa where renders with a larger z coordinate are rendered in the foreground. The z offset can be set directly on the PoseStack itself via #translate. Some basic offset logic is applied internally in some methods of GuiGraphics (e.g. item rendering).
Important
When setting the blit offset, you must reset it after rendering your object. Otherwise, other objects within the screen may be rendered in an incorrect layer causing graphical issues. It is recommended to push the current pose before translating and then popping after all rendering at the offset is completed.
Renderable
Renderables are essentially objects that are rendered. These include screens, buttons, chat boxes, lists, etc. Renderables only have one method: #render. This takes in the GuiGraphics used to render things to the screen, the x and y positions of the mouse scaled to the relative screen size, and the tick delta (how many ticks have passed since the last frame).
Some common renderables are screens and ‘widgets’: interactable elements which typically render on the screen such as Button, its subtype ImageButton, and EditBox which is used to input text on the screen.
GuiEventListener
Any screen rendered in Minecraft implements GuiEventListener. GuiEventListeners are responsible for handling user interaction with the screen. These include inputs from the mouse (movement, clicked, released, dragged, scrolled, mouseover) and keyboard (pressed, released, typed). Each method returns whether the associated action affected the screen successfully. Widgets like buttons, chat boxes, lists, etc. also implement this interface.
ContainerEventHandler
Almost synonymous with GuiEventListeners are their subtype: ContainerEventHandlers. These are responsible for handling user interaction on screens which contain widgets, managing which is currently focused and how the associated interactions are applied. ContainerEventHandlers add three additional features: interactable children, dragging, and focusing.
Event handlers hold children which are used to determine the interaction order of elements. During the mouse event handlers (excluding dragging), the first child in the list that the mouse hovers over has their logic executed.
Dragging an element with the mouse, implemented via #mouseClicked and #mouseReleased, provides more precisely executed logic.
Focusing allows for a specific child to be checked first and handled during an event’s execution, such as during keyboard events or dragging the mouse. Focus is typically set through #setFocused. In addition, interactable children can be cycled using #nextFocusPath, selecting the child based upon the FocusNavigationEvent passed in.
Note
Screens implement ContainerEventHandler through AbstractContainerEventHandler, which adds in the setter and getter logic for dragging and focusing children.
NarratableEntry
NarratableEntrys are elements which can be spoken about through Minecraft’s accessibility narration feature. Each element can provide different narration depending on what is hovered or selected, prioritized typically by focus, hovering, and then all other cases.
NarratableEntrys have three methods: one which determines the priority of the element (#narrationPriority), one which determines whether to speak the narration (#isActive), and finally one which supplies the narration to its associated output, spoken or read (#updateNarration).
Note
All widgets from Minecraft are NarratableEntrys, so it typically does not need to be manually implemented if using an available subtype.
The Screen Subtype
With all of the above knowledge, a basic screen can be constructed. To make it easier to understand, the components of a screen will be mentioned in the order they are typically encountered.
First, all screens take in a Component which represents the title of the screen. This component is typically drawn to the screen by one of its subtypes. It is only used in the base screen for the narration message.
// In some Screen subclass
public MyScreen(Component title) {
super(title);
}
Initialization
Once a screen has been initialized, the #init method is called. The #init method sets the initial settings inside the screen from the ItemRenderer and Minecraft instance to the relative width and height as scaled by the game. Any setup such as adding widgets or precomputing relative coordinates should be done in this method. If the game window is resized, the screen will be reinitialized by calling the #init method.
There are three ways to add a widget to a screen, each serving a separate purpose:
| Method | Description |
|---|---|
#addWidget |
Adds a widget that is interactable and narrated, but not rendered. |
#addRenderableOnly |
Adds a widget that will only be rendered; it is not interactable or narrated. |
#addRenderableWidget |
Adds a widget that is interactable, narrated, and rendered. |
Typically, #addRenderableWidget will be used most often.
// In some Screen subclass
@Override
protected void init() {
super.init();
// Add widgets and precomputed values
this.addRenderableWidget(new EditBox(/* ... */));
}
Ticking Screens
Screens also tick using the #tick method to perform some level of client side logic for rendering purposes. The most common example is the EditBox for the blinking cursor.
// In some Screen subclass
@Override
public void tick() {
super.tick();
// Add ticking logic for EditBox in editBox
this.editBox.tick();
}
Input Handling
Since screens are subtypes of GuiEventListeners, the input handlers can also be overridden, such as for handling logic on a specific key press.
Rendering the Screen
Finally, screens are rendered through the #render method provided by being a Renderable subtype. As mentioned, the #render method draws the everything the screen has to render every frame, such as the background, widgets, tooltips, etc. By default, the #render method only renders the widgets to the screen.
The two most common things rendered within a screen that is typically not handled by a subtype is the background and the tooltips.
The background can be rendered using #renderBackground, with one method taking in a v Offset for the options background whenever a screen is rendered when the level behind it cannot be.
Tooltips are rendered through GuiGraphics#renderTooltip or GuiGraphics#renderComponentTooltip which can take in the text components being rendered, an optional custom tooltip component, and the x / y relative coordinates on where the tooltip should be rendered on the screen.
// In some Screen subclass
// mouseX and mouseY indicate the scaled coordinates of where the cursor is in on the screen
@Override
public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTick) {
// Background is typically rendered first
this.renderBackground(graphics);
// Render things here before widgets (background textures)
// Then the widgets if this is a direct child of the Screen
super.render(graphics, mouseX, mouseY, partialTick);
// Render things after widgets (tooltips)
}
Closing the Screen
When a screen is closed, two methods handle the teardown: #onClose and #removed.
#onClose is called whenever the user makes an input to close the current screen. This method is typically used as a callback to destroy and save any internal processes in the screen itself. This includes sending packets to the server.
#removed is called just before the screen changes and is released to the garbage collector. This handles anything that hasn’t been reset back to its initial state before the screen was opened.
// In some Screen subclass
@Override
public void onClose() {
// Stop any handlers here
// Call last in case it interferes with the override
super.onClose();
}
@Override
public void removed() {
// Reset initial states here
// Call last in case it interferes with the override
super.removed()
;}
AbstractContainerScreen
If a screen is directly attached to a menu, then an AbstractContainerScreen should be subclassed instead. An AbstractContainerScreen acts as the renderer and input handler of a menu and contains logic for syncing and interacting with slots. As such, only two methods typically need to be overridden or implemented to have a working container screen. Once again, to make it easier to understand, the components of a container screen will be mentioned in the order they are typically encountered.
An AbstractContainerScreen typically requires three parameters: the container menu being opened (represented by the generic T), the player inventory (only for the display name), and the title of the screen itself. Within here, a number of positioning fields can be set:
| Field | Description |
|---|---|
imageWidth |
The width of the texture used for the background. This is typically inside a PNG of 256 x 256 and defaults to 176. |
imageHeight |
The width of the texture used for the background. This is typically inside a PNG of 256 x 256 and defaults to 166. |
titleLabelX |
The relative x coordinate of where the screen title will be rendered. |
titleLabelY |
The relative y coordinate of where the screen title will be rendered. |
inventoryLabelX |
The relative x coordinate of where the player inventory name will be rendered. |
inventoryLabelY |
The relative y coordinate of where the player inventory name will be rendered. |
Important
In a previous section, it mentioned that precomputed relative coordinates should be set in the #init method. This still remains true, as the values mentioned here are not precomputed coordinates but static values and relativized coordinates.
The image values are static and non changing as they represent the background texture size. To make things easier when rendering, two additional values (leftPos and topPos) are precomputed in the #init method which marks the top left corner of where the background will be rendered. The label coordinates are relative to these values.
The leftPos and topPos is also used as a convenient way to render the background as they already represent the position to pass into the #blit method.
// In some AbstractContainerScreen subclass
public MyContainerScreen(MyMenu menu, Inventory playerInventory, Component title) {
super(menu, playerInventory, title);
this.titleLabelX = 10;
this.inventoryLabelX = 10;
/*
* If the 'imageHeight' is changed, 'inventoryLabelY' must also be
* changed as the value depends on the 'imageHeight' value.
*/
}
Menu Access
As the menu is passed into the screen, any values that were within the menu and synced (either through slots, data slots, or a custom system) can now be accessed through the menu field.
Container Tick
Container screens tick within the #tick method when the player is alive and looking at the screen via #containerTick. This essentially takes the place of #tick within container screens, with its most common usage being to tick the recipe book.
// In some AbstractContainerScreen subclass
@Override
protected void containerTick() {
super.containerTick();
// Tick things here
}
Rendering the Container Screen
The container screen is rendered across three methods: #renderBg, which renders the background textures, #renderLabels, which renders any text on top of the background, and #render which encompass the previous two methods in addition to providing a grayed out background and tooltips.
Starting with #render, the most common override (and typically the only case) adds the background, calls the super to render the container screen, and finally renders the tooltips on top of it.
// In some AbstractContainerScreen subclass
@Override
public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTick) {
this.renderBackground(graphics);
super.render(graphics, mouseX, mouseY, partialTick);
/*
* This method is added by the container screen to render
* the tooltip of the hovered slot.
*/
this.renderTooltip(graphics, mouseX, mouseY);
}
Within the super, #renderBg is called to render the background of the screen. The most standard representation uses three method calls: two for setup and one to draw the background texture.
// In some AbstractContainerScreen subclass
// The location of the background texture (assets/<namespace>/<path>)
private static final ResourceLocation BACKGROUND_LOCATION = new ResourceLocation(MOD_ID, "textures/gui/container/my_container_screen.png");
@Override
protected void renderBg(GuiGraphics graphics, float partialTick, int mouseX, int mouseY) {
/*
* Renders the background texture to the screen. 'leftPos' and
* 'topPos' should already represent the top left corner of where
* the texture should be rendered as it was precomputed from the
* 'imageWidth' and 'imageHeight'. The two zeros represent the
* integer u/v coordinates inside the 256 x 256 PNG file.
*/
graphics.blit(BACKGROUND_LOCATION, this.leftPos, this.topPos, 0, 0, this.imageWidth, this.imageHeight);
}
Finally, #renderLabels is called to render any text above the background, but below the tooltips. This simply calls uses the font to draw the associated components.
// In some AbstractContainerScreen subclass
@Override
protected void renderLabels(GuiGraphics graphics, int mouseX, int mouseY) {
super.renderLabels(graphics, mouseX, mouseY);
// Assume we have some Component 'label'
// 'label' is drawn at 'labelX' and 'labelY'
graphics.drawString(this.font, this.label, this.labelX, this.labelY, 0x404040);
}
Note
When rendering the label, you do not need to specify the leftPos and topPos offset. Those have already been translated within the PoseStack so everything within this method is drawn relative to those coordinates.
Registering an AbstractContainerScreen
To use an AbstractContainerScreen with a menu, it needs to be registered. This can be done by calling MenuScreens#register within the FMLClientSetupEvent on the mod event bus.
// Event is listened to on the mod event bus
private void clientSetup(FMLClientSetupEvent event) {
event.enqueueWork(
// Assume RegistryObject<MenuType<MyMenu>> MY_MENU
// Assume MyContainerScreen<MyMenu> which takes in three parameters
() -> MenuScreens.register(MY_MENU.get(), MyContainerScreen::new)
);
}
Warning
MenuScreens#register is not thread-safe, so it needs to be called inside #enqueueWork provided by the parallel dispatch event.
Rendering: Model Extensions Intro
(Failed to fetch: )
Rendering: Root Transforms
Root Transforms
Adding the transform entry at the top level of a model JSON suggests to the loader that a transformation should be applied to all geometry right before the rotations in the blockstate file in the case of a block model, and before the display transforms in the case of an item model. The transformation is available through IGeometryBakingContext#getRootTransform() in IUnbakedGeometry#bake().
Custom model loaders may ignore this field entirely.
The root transforms can be specified in two formats:
- A JSON object containing a singular
matrixentry containing a raw transformation matrix in the form of a nested JSON array with the last row omitted (3*4 matrix, row major order). The matrix is the composition of the translation, left rotation, scale, right rotation and the transformation origin in that order. Example demonstrating the structure:
java
"transform": {
"matrix": [
[ 0, 0, 0, 0 ],
[ 0, 0, 0, 0 ],
[ 0, 0, 0, 0 ]
]
}
2. A JSON object containing any combination of the following optional entries:
- origin: origin point used for the rotations and scaling
- translation: relative translation
- rotation or left_rotation: rotation around the translated origin to be applied before scaling
- scale: scale relative to the translated origin
- right_rotation or post_rotation: rotation around the translated origin to be applied after scaling
Element-wise specification
If the transformation is specified as a combination of the entries mentioned in option 4, these entries will be applied in the order of translation, left_rotation, scale, right_rotation.
The transformation is moved to the specified origin as a last step.
{
"transform": {
"origin": "center",
"translation": [ 0, 0.5, 0 ],
"rotation": { "y": 45 }
},
// ...
}
The elements are expected to be defined as follows:
Origin
The origin can be specified either as an array of 3 floating point values representing a three-dimensional vector: [ x, y, z ] or as one of the three default values:
"corner"(0, 0, 0)"center"(.5, .5, .5)"opposing-corner"(1, 1, 1)
If the origin is not specified, it defaults to "opposing-corner".
Translation
The translation must be specified as an array of 3 floating point values representing a three-dimensional vector: [ x, y, z ] and defaults to (0, 0, 0) if not present.
Left and Right Rotation
The rotations can be specified in any one of the following four ways:
- Single JSON object with a single axis => rotation degree mapping:
{ "x": 90 } - Array of an arbitrary amount of JSON objects with the above format (applied in the order they are specified in):
[ { "x": 90 }, { "y": 45 }, { "x": -22.5 } ] - Array of 3 floating point values specifying the rotation in degrees around each axis:
[ 90, 180, 45 ] - Array of 4 floating point values specifying a quaternion directly:
[ 0.38268346, 0, 0, 0.9238795 ](example equals 45 degrees around the X axis)
If the respective rotation is not specified, it will default to no rotation.
Scale
The scale must be specified as an array of 3 floating point values representing a three-dimensional vector: [ x, y, z ] and defaults to (1, 1, 1) if not present.
Rendering: Render Types
Render Types
Adding the render_type entry at the top level of the JSON suggests to the loader what render type the model should use. If not specified, the loader gets to pick the render type(s) used, often falling back to the render types returned by ItemBlockRenderTypes#getRenderLayers().
Custom model loaders may ignore this field entirely.
Note
Since 1.19 this is preferred over the deprecated method of setting the applicable render type(s) via ItemBlockRenderTypes#setRenderLayer() for blocks.
Example of a model for a cutout block with the glass texture
{
"render_type": "minecraft:cutout",
"parent": "block/cube_all",
"textures": {
"all": "block/glass"
}
}
Vanilla Values
The following options with the respective chunk and entity render type are supplied by Forge (NamedRenderTypeManager#preRegisterVanillaRenderTypes()):
minecraft:solid- Chunk render type:
RenderType#solid() - Entity render type:
ForgeRenderTypes#ITEM_LAYERED_SOLID - Used for fully solid blocks (i.e. Stone)
minecraft:cutout- Chunk render type:
RenderType#cutout() - Entity render type:
ForgeRenderTypes#ITEM_LAYERED_CUTOUT - Used for blocks where any given pixel is either fully transparent or fully opaque (i.e. Glass Block)
minecraft:cutout_mipped- Chunk render type:
RenderType#cutoutMipped() - Entity render type:
ForgeRenderTypes#ITEM_LAYERED_CUTOUT - Chunk and entity render type differ due to mipmapping on the entity render type making items look weird
- Used for blocks where any given pixel is either fully transparent or fully opaque and the texture should be scaled down at larger distances (mipmapping) to avoid visual artifacts (i.e. Leaves)
minecraft:cutout_mipped_all- Chunk render type:
RenderType#cutoutMipped() - Entity render type:
ForgeRenderTypes#ITEM_LAYERED_CUTOUT_MIPPED - Used in similar cases as
minecraft:cutout_mippedwhen the item representation should also have mipmapping applied minecraft:translucent- Chunk render type:
RenderType#translucent() - Entity render type:
ForgeRenderTypes#ITEM_LAYERED_TRANSLUCENT - Used for blocks where any given pixel may be partially transparent (i.e. Stained Glass)
minecraft:tripwire- Chunk render type:
RenderType#tripwire() - Entity render type:
ForgeRenderTypes#ITEM_LAYERED_TRANSLUCENT - Chunk and entity render type differ due to the tripwire render type not being feasible as an entity render type
- Used for blocks with the special requirement of being rendered to the weather render target (i.e. Tripwire)
Custom Values
Custom named render types to be specified in a model can be registered in the RegisterNamedRenderTypesEvent. This event is fired on the mod event bus.
A custom named render type consists of two or three components:
- A chunk render type - any of the types in the list returned by
RenderType.chunkBufferLayers()can be used - A render type with the
DefaultVertexFormat.NEW_ENTITYvertex format (“entity render type”) - A render type with the
DefaultVertexFormat.NEW_ENTITYvertex format for use when the Fabulous! graphics mode is selected (optional)
The chunk render type is used when a block using this named render type is rendered as part of the chunk geometry.
The required entity render type is used when an item using this named render type is rendered in the Fast and Fancy graphics modes (inventory, ground, item frame, etc.).
The optional entity render type is used the same way as the required entity render type when the Fabulous! graphics mode is selected. This render type is needed in cases where the required entity render type does not work in the Fabulous! graphics mode (typically only applies to translucent render types).
public static void onRegisterNamedRenderTypes(RegisterNamedRenderTypesEvent event)
{
event.register("special_cutout", RenderType.cutout(), Sheets.cutoutBlockSheet());
event.register("special_translucent", RenderType.translucent(), Sheets.translucentCullBlockSheet(), Sheets.translucentItemSheet());
}
These can then be addressed in JSON as <your_mod_id>:special_cutout and <your_mod_id>:special_translucent.
Rendering: Part Visibility
Part Visibility
Adding the visibility entry at the top level of a model JSON allows control over the visibility of different parts of the model to decide whether they should be baked into the final BakedModel. The definition of a “part” is dependent on the model loader loading this model and custom model loaders are free to ignore this entry completely. Out of the model loaders provided by Forge only the composite model loader and the OBJ model loader make use of this functionality. The visibility entries are specified as "part name": boolean entries.
Example of a composite model with two parts, the second of which will not be baked into the final model, and two child models overriding this visibility to have only the first part and both parts visible respectively:
// mycompositemodel.json
{
"loader": "forge:composite",
"children": {
"part_one": {
"parent": "mymod:mypartmodel_one"
},
"part_two": {
"parent": "mymod:mypartmodel_two"
}
},
"visibility": {
"part_two": false
}
}
// mycompositechild_one.json
{
"parent": "mymod:mycompositemodel",
"visibility": {
"part_one": false,
"part_two": true
}
}
// mycompositechild_two.json
{
"parent": "mymod:mycompositemodel",
"visibility": {
"part_two": true
}
}
The visibility of a given part is determined by checking whether the model specifies a visibility for this part and, if not present, recursively checking the model’s parent until either an entry is found or there is no further parent to check, in which case it defaults to true.
This allows setups like the following where multiple models use different parts of a single composite model:
- A composite model specifies multiple components
- Multiple models specify this composite model as their parent
- These child models individually specify different visibilities for the parts
Rendering: Face Data
Face Data
In a vanilla “elements” model, additional data about an element’s faces can be specified at either the element level or the face level. Faces which do not specify their own face data will fall back to the element’s face data or a default if no face data is specified at the element level.
To use this extension for a generated item model, the model must be loaded through the forge:item_layers model loader due to the vanilla item model generator not being extended to read this additional data.
All values of the face data are optional.
Elements Model
In vanilla “elements” models, the face data applies to the face it is specified in or all faces of the element it is specified in which don’t have their own face data.
Note
If forge_data is specified on a face, it will not inherit any parameters from the element-level forge_data declaration.
The additional data can be specified in the two ways shown in this example:
{
"elements": [
{
"forge_data": {
"color": "0xFFFF0000",
"block_light": 15,
"sky_light": 15,
"ambient_occlusion": false
},
"faces": {
"north": {
"forge_data": {
"color": "0xFFFF0000",
"block_light": 15,
"sky_light": 15,
"ambient_occlusion": false
},
// ...
},
// ...
},
// ...
}
]
}
Generated Item Model
In item models generated using the forge:item_layers loader, face data is specified for each texture layer and applies to all of the geometry (front/back facing quads and edge quads).
The forge_data field must be located at the top level of the model JSON, with each key-value pair associating a face data object to a layer index.
In the following example, layer 1 will be tinted red and glow at full brightness:
{
"textures": {
"layer0": "minecraft:item/stick",
"layer1": "minecraft:item/glowstone_dust"
},
"forge_data": {
"1": {
"color": "0xFFFF0000",
"block_light": 15,
"sky_light": 15,
"ambient_occlusion": false
}
}
}
Parameters
Color
Specifying a color value with the color entry will apply that color as a tint to the quads. Defaults to 0xFFFFFFFF (white, fully opaque). The color must be in the ARGB format packed into a 32-bit integer and can be specified as either a hexadecimal string ("0xAARRGGBB") or as a decimal integer literal (JSON does not support hexadecimal integer literals).
Warning
The four color components are multiplied with the texture’s pixels. Omitting the alpha component is equivalent to making it 0, which will make the geometry fully transparent.
This can be used as a replacement for tinting with BlockColor and ItemColor if the color values are constant.
Block and Sky Light
Specifying a block and/or sky light value with the block_light and sky_light entry respectively will override the respective light value of the quads. Both values default to 0. The values must be in the range 0-15 (inclusive) and are treated as a minimum value for the respective light type when the face is rendered, meaning that a higher in-world value of the respective light type will override the specified value.
The specified light values are purely client-side and affect neither the server’s light level nor the brightness of surrounding blocks.
Ambient Occlusion
Specifying the ambient_occlusion flag will configure AO for the quads. Defaults to true. The behaviour of this flag is equivalent to the top-level ambientocclusion flag of the vanilla format.

Ambient occlusion enabled on the left and disabled on the right, demonstrated with the Smooth Lighting graphics setting
Note
If the top-level AO flag is set to false, specifying this flag as true on an element or face won’t be able to override the top-level flag.
{
"ambientocclusion": false,
"elements": [
{
"forge_data": {
"ambient_occlusion": true // Has no effect
}
}
]
}
Rendering: Model Loaders Intro
Custom Model Loaders
A “model” is simply a shape. It can be a simple cube, it can be several cubes, it can be a truncated icosidodecahedron, or anything in between. Most models you’ll see will be in the vanilla JSON format. Models in other formats are loaded into IUnbakedGeometrys by an IGeometryLoader at runtime. Forge provides default implementations for WaveFront OBJ files, buckets, composite models, models in different render layers, and a reimplementation of Vanilla’s builtin/generated item model. Most things do not care about what loaded the model or what format it’s in as they are all eventually represented by an BakedModel in code.
Warning
Specifying a custom model loader through the top-level loader entry in a model JSON will cause the elements entry to be ignored unless it is consumed by the custom loader. All other vanilla entries will still be loaded and available in the unbaked BlockModel representation and may be consumed outside of the custom loader.
WaveFront OBJ Models
Forge adds a loader for the .obj file format. To use these models, the JSON must reference the forge:obj loader. This loader accepts any model location that is in a registered namespace and whose path ends in .obj. The .mtl file should be placed in the same location with the same name as the .obj to be used automatically. The .mtl file will probably have to be manually edited to change the paths pointing to textures defined within the JSON. Additionally, the V axis for textures may be flipped depending on the external program that created the model (i.e. V = 0 may be the bottom edge, not the top). This may be rectified in the modelling program itself or done in the model JSON like so:
{
// Add the following line on the same level as a 'model' declaration
"loader": "forge:obj",
"flip_v": true,
"model": "examplemod:models/block/model.obj",
"textures": {
// Can refer to in .mtl using #texture0
"texture0": "minecraft:block/dirt",
"particle": "minecraft:block/dirt"
}
}
Rendering: Baked Model
BakedModel
BakedModel is the result of calling UnbakedModel#bake for the vanilla model loader or IUnbakedGeometry#bake for custom model loaders. Unlike UnbakedModel or IUnbakedGeometry, which purely represents a shape without any concept of items or blocks, BakedModel is not as abstract. It represents geometry that has been optimized and reduced to a form where it is (almost) ready to go to the GPU. It can also process the state of an item or block to change the model.
In a majority of cases, it is not really necessary to implement this interface manually. One can instead use one of the existing implementations.
getOverrides
Returns the ItemOverrides to use for this model. This is only used if this model is being rendered as an item.
useAmbientOcclusion
If the model is rendered as a block in the level, the block in question does not emit any light, and ambient occlusion is enabled. This causes the model to be rendered with ambient occlusion.
isGui3d
If the model is rendered as an item in an inventory, on the ground as an entity, on an item frame, etc., this makes the model look “flat.” In GUIs, this also disables the lighting.
isCustomRenderer
Important
Unless you know what you’re doing, just return false from this and continue on.
When rendering this as an item, returning true causes the model to not be rendered, instead falling back to BlockEntityWithoutLevelRenderer#renderByItem. For certain vanilla items such as chests and banners, this method is hardcoded to copy data from the item into a BlockEntity, before using a BlockEntityRenderer to render that BE in place of the item. For all other items, it will use the BlockEntityWithoutLevelRenderer instance provided by IClientItemExtensions#getCustomRenderer. Refer to BlockEntityWithoutLevelRenderer page for more information.
getParticleIcon
Whatever texture should be used for the particles. For blocks, this shows when an entity falls on it, when it breaks, etc. For items, this shows when it breaks or when it’s eaten.
Important
The vanilla method with no parameters has been deprecated in favor of #getParticleIcon(ModelData) since model data can have an effect on how a particular model might be rendered.
~~getTransforms~~
Deprecated in favor of implementing #applyTransform. The default implementation is fine if #applyTransform is implemented. See Transform.
applyTransform
See Transform.
getQuads
This is the main method of BakedModel. It returns a list of BakedQuads: objects which contain the low-level vertex data that will be used to render the model. If the model is being rendered as a block, then the BlockState passed in is non-null. If the model is being rendered as an item, the ItemOverrides returned from #getOverrides is responsible for handling the state of the item, and the BlockState parameter will be null.
Note
The origin point for the vertices in a BakedQuad is the bottom, northwest corner. Vertex coordinate values less than 0 or greater than 1 will position the vertex outside of the block. To avoid lighting issues, provide the vertices in counterclockwise order.
The Direction passed in is used for face culling. If the block against the given side of another block being rendered is opaque, then the faces associated with that side are not rendered. If that parameter is null, all faces not associated with a side are returned (that will never be culled).
The rand parameter is an instance of Random.
It also takes in a non null ModelData instance. This can be used to define extra data when rendering the specific model via ModelPropertys. For example, one such property is CompositeModel$Data, which is used to store any additional submodel data for a model using the forge:composite model loader.
Note that this method is called very often: once for every combination of non-culled face and supported block render layer (anywhere between 0 to 28 times) per block in a level. This method should be as fast as possible, and should probably cache heavily.
Rendering: Transform
Transform
When an BakedModel is being rendered as an item, it can apply special handling depending on which transform it is being rendered in. “Transform” means in what context the model is being rendered. The possible transforms are represented in code by the ItemDisplayContext enum. There are two systems for handling transform: the deprecated vanilla system, constituted by BakedModel#getTransforms, ItemTransforms, and ItemTransform, and the Forge system, embodied by the method IForgeBakedModel#applyTransform. The vanilla code is patched to favor using applyTransform over the vanilla system whenever possible.
ItemDisplayContext
NONE - Used for the display entity by default when no context is set and by Forge when a Block’s RenderShape is set to #ENTITYBLOCK_ANIMATED.
THIRD_PERSON_LEFT_HAND/THIRD_PERSON_RIGHT_HAND/FIRST_PERSON_LEFT_HAND/FIRST_PERSON_RIGHT_HAND - The first person values represent when the player is holding the item in their own hand. The third person values represent when another player is holding the item and the client is looking at them in the 3rd person. Hands are self-explanatory.
HEAD - Represents when any player is wearing the item in the helmet slot (e.g. pumpkins).
GUI - Represents when the item is being rendered in a Screen.
GROUND - Represents when the item is being rendered in the level as an ItemEntity.
FIXED - Used for item frames.
The Vanilla Way
The vanilla way of handling transform is through BakedModel#getTransforms. This method returns an ItemTransforms, which is a simple object that contains various ItemTransforms as public final fields. An ItemTransform represents a rotation, a translation, and a scale to be applied to the model. The ItemTransforms is a container for these, holding one for each of the ItemDisplayContexts except NONE. In the vanilla implementation, calling #getTransform for NONE results in the default transform, ItemTransform#NO_TRANSFORM.
The entire vanilla system for handling transforms is deprecated by Forge, and most implementations of BakedModel should simply return ItemTransforms#NO_TRANSFORMS (which is the default implementation) from BakedModel#getTransforms. Instead, they should implement #applyTransform.
The Forge Way
The Forge way of handling transforms is #applyTransform, a method patched into BakedModel. It supersedes the #getTransforms method.
BakedModel#applyTransform
Given a ItemDisplayContext, PoseStack, and a boolean to determine whether to apply the transform for the left hand, this method produces an BakedModel to be rendered. Because the returned BakedModel can be a totally new model, this method is more flexible than the vanilla method (e.g. a piece of paper that looks flat in hand but crumpled on the ground).
Resources: Client Assets Intro
Resource Packs
Resource Packs allow for the customization of client resources through the assets directory. This includes textures, models, sounds, localizations, and others. Your mod (as well as Forge itself) can also have resource packs. Any user can therefore modify all the textures, models, and other assets defined within this directory.
Creating a Resource Pack
Resource Packs are stored within your project’s resources. The assets directory contains the contents of the pack, while the pack itself is defined by the pack.mcmeta alongside the assets folder.
Your mod can have multiple asset domains, since you can add or modify already existing resource packs, like vanilla’s, Forge’s, or another mod’s.
You can then follow the steps found at the Minecraft Wiki to create any resource pack.
Additional reading: Resource Locations
Resources: Models Intro
Models
The model system is Minecraft’s way of giving blocks and items their shapes. Through the model system, blocks and items are mapped to their models, which define how they look. One of the main goals of the model system is to allow not only textures but the entire shape of a block/item to be changed by resource packs. Indeed, any mod that adds items or blocks also contains a mini-resource pack for their blocks and items.
Model Files
Models and textures are linked through ResourceLocations but are stored in the ModelManager using ModelResourceLocations. Models are referenced in different locations through the block or item’s registry name depending on whether they are referencing block states or item models. Blocks will have their ModelResourceLocation represent their registry name along with a stringified version of its current BlockState while items will use their registry name followed by inventory.
Note
JSON models only support cuboid elements; there is no way to express a triangular wedge or anything like it. To have more complicated models, another format must be used.
Textures
Textures, like models, are contained within resource packs and are referred to with ResourceLocations. In Minecraft, the UV coordinates (0,0) are taken to mean the top-left corner. UVs are always from 0 to 16. If a texture is larger or smaller, the coordinates are scaled to fit. A texture should also be square, and the side length of a texture should be a power of two, as doing otherwise breaks mipmapping (e.g. 1x1, 2x2, 8x8, 16x16, and 128x128 are good. 5x5 and 30x30 are not recommended because they are not powers of 2. 5x10 and 4x8 are completely broken as they are not square.). Textures should only ever be not a square if it is animated.
Resources: Texture Tinting
Coloring Textures
Many blocks and items in vanilla change their texture color depending on where they are or what properties they have, such as grass. Models support specifying “tint indices” on faces, which are integers that can then be handled by BlockColors and ItemColors. See the wiki for information on how tint indices are defined in vanilla models.
BlockColor/ItemColor
Both of these are single-method interfaces. BlockColor takes a BlockState, a (nullable) BlockAndTintGetter, and a (nullable) BlockPos. ItemColor takes an ItemStack. Both of them take an int parameter tintIndex, which is the tint index of the face being colored. Both of them return an int, a color multiplier. This int is treated as 4 unsigned bytes, alpha, red, green, and blue, in that order, from most significant byte to least. For each pixel in the tinted face, the value of each color channel is (int)((float) base * multiplier / 255.0), where base is the original value for the channel, and multiplier is the associated byte from the color multiplier. Note that blocks do not use the alpha channel. For example, the grass texture, untinted, looks white and gray. The BlockColor and ItemColor for grass return color multipliers with low red and blue components, but high alpha and green components, (at least in warm biomes) so when the multiplication is performed, the green is brought out and the red/blue diminished.
If an item inherits from the builtin/generated model, each layer (“layer0”, “layer1”, etc.) has a tint index corresponding to its layer index.
Creating Color Handlers
BlockColors need to be registered to the BlockColors instance of the game. BlockColors can be acquired through RegisterColorHandlersEvent$Block, and an BlockColor can be registered by #register. Note that this does not cause the BlockItem for the given block to be colored. BlockItems are items and need to be colored with an ItemColor.
@SubscribeEvent
public void registerBlockColors(RegisterColorHandlersEvent.Block event){
event.register(myBlockColor, coloredBlock1, coloredBlock2, ...);
}
ItemColors need to be registered to the ItemColors instance of the game. ItemColors can be acquired through RegisterColorHandlersEvent$Item, and an ItemColor can be registered by #register. This method is overloaded to also take Blocks, which simply registers the color handler for the item Block#asItem (i.e. the block’s BlockItem).
@SubscribeEvent
public void registerItemColors(RegisterColorHandlersEvent.Item event){
event.register(myItemColor, coloredItem1, coloredItem2, ...);
}
Resources: Server Data Intro
Datapacks
In 1.13, Mojang added datapacks to the base game. They allow for the modification of the files for logical servers through the data directory. This includes advancements, loot_tables, structures, recipes, tags, etc. Forge, and your mod, can also have datapacks. Any user can therefore modify all the recipes, loot tables, and other data defined within this directory.
Creating a Datapack
Datapacks are stored within the data directory within your project’s resources.
Your mod can have multiple data domains, since you can add or modify already existing datapacks, like vanilla’s, forge’s, or another mod’s.
You can then follow the steps found here to create any datapack.
Additional reading: Resource Locations
Resources: Ingredients
Ingredients
Ingredients are predicate handlers for item-based inputs which check whether a certain ItemStack meets the condition to be a valid input in a recipe. All vanilla recipes that take inputs use an Ingredient or a list of Ingredients, which is then merged into a single Ingredient.
Custom Ingredients
Custom ingredients can be specified by setting type to the name of the ingredient’s serializer, with the exception of compound ingredients. When no type is specified, type defaults to the vanilla ingredient minecraft:item. Custom ingredients can also easily be used in data generation.
Forge Types
Forge provides a few additional Ingredient types for programmers to implement.
CompoundIngredient
Though they are functionally identical, Compound ingredients replaces the way one would implement a list of ingredients would in a recipe. They work as a set OR where the passed in stack must be within at least one of the supplied ingredients. This change was made to allow custom ingredients to work correctly within lists. As such, no type needs to be specified.
// For some input
[
// At least one of these ingredients must match to succeed
{
// Ingredient
},
{
// Custom ingredient
"type": "examplemod:example_ingredient"
}
]
StrictNBTIngredient
StrictNBTIngredients compare the item, damage, and the share tags (as defined by IForgeItem#getShareTag) on an ItemStack for exact equivalency. This can be used by specifying the type as forge:nbt.
// For some input
{
"type": "forge:nbt",
"item": "examplemod:example_item",
"nbt": {
// Add nbt data (must match exactly what is on the stack)
}
}
PartialNBTIngredient
PartialNBTIngredients are a looser version of StrictNBTIngredient as they compare against a single or set of items and only keys specified within the share tag (as defined by IForgeItem#getShareTag). This can be used by specifying the type as forge:partial_nbt.
// For some input
{
"type": "forge:partial_nbt",
// Either 'item' or 'items' must be specified
// If both are specified, only 'item' will be read
"item": "examplemod:example_item",
"items": [
"examplemod:example_item",
"examplemod:example_item2"
// ...
],
"nbt": {
// Checks only for equivalency on 'key1' and 'key2'
// All other keys in the stack will not be checked
"key1": "data1",
"key2": {
// Data 2
}
}
}
IntersectionIngredient
IntersectionIngredients work as a set AND where the passed in stack must match all supplied ingredients. There must be at least two ingredients supplied to this. This can be used by specifying the type as forge:intersection.
// For some input
{
"type": "forge:intersection",
// All of these ingredients must return true to succeed
"children": [
{
// Ingredient 1
},
{
// Ingredient 2
}
// ...
]
}
DifferenceIngredient
DifferenceIngredients work as a set subtraction (SUB) where the passed in stack must match the first ingredient but must not match the second ingredient. This can be used by specifying the type as forge:difference.
// For some input
{
"type": "forge:difference",
"base": {
// Ingredient the stack is in
},
"subtracted": {
// Ingredient the stack is NOT in
}
}
Creating Custom Ingredients
Custom ingredients can be created by implementing IIngredientSerializer for the created Ingredient subclass.
Tip
Custom ingredients should subclass AbstractIngredient as it provides some useful abstractions for ease of implementation.
Ingredient Subclass
There are three important methods to implement for each ingredient subclass:
| Method | Description |
|---|---|
| getSerializer | Returns the serializer used to read and write the ingredient. |
| test | Returns true if the input is valid for this ingredient. |
| isSimple | Returns false if the ingredient matches on the stack’s tag. AbstractIngredient subclasses will need to define this behavior, while Ingredient subclasses return true by default. |
All other defined methods are left as an exercise to the reader to use as required for the ingredient subclass.
IIngredientSerializer
IIngredientSerializer subtypes must implement three methods:
| Method | Description |
|---|---|
| parse (JSON) | Converts a JsonObject to an Ingredient. |
| parse (Network) | Reads the network buffer to decode an Ingredient. |
| write | Writes an Ingredient to the network buffer. |
Additionally, Ingredient subclasses should implement Ingredient#toJson for use with data generation. AbstractIngredient subclasses make #toJson an abstract method requiring the method to be implemented.
Afterwards, a static instance should be declared to hold the initialized serializer and then registered using CraftingHelper#register either during the RegisterEvent for RecipeSerializers or during FMLCommonSetupEvent. The Ingredient subclass return the static instance of the serializer in Ingredient#getSerializer.
// In some serializer class
public static final ExampleIngredientSerializer INSTANCE = new ExampleIngredientSerializer();
// In some handler class
public void registerSerializers(RegisterEvent event) {
event.register(ForgeRegistries.Keys.RECIPE_SERIALIZERS,
helper -> CraftingHelper.register(registryName, INSTANCE)
);
}
// In some ingredient subclass
@Override
public IIngredientSerializer<? extends Ingredient> getSerializer() {
return INSTANCE;
}
Tip
If using FMLCommonSetupEvent to register an ingredient serializer, it must be enqueued to the synchronous work queue via FMLCommonSetupEvent#enqueueWork as CraftingHelper#register is not thread-safe.
Resources: Loot Tables
Loot Tables
Loot tables are logic files which dictate what should happen when various actions or scenarios occur. Although the vanilla system deals purely with item generation, the system can be expanded to perform any number of defined actions.
Data-Driven Tables
Most loot tables within vanilla are data driven via JSON. This means that a mod is not necessary to create a new loot table, only a Data pack. A full list on how to create and put these loot tables within the mod’s resources folder can be found on the Minecraft Wiki.
Using a Loot Table
A loot table is referenced by its ResourceLocation which points to data/<namespace>/loot_tables/<path>.json. The LootTable associated with the reference can be obtained using LootDataResolver#getLootTable, where LootDataResolver can be obtained via MinecraftServer#getLootData.
A loot table is always generated with given parameters. The LootParams contains the level the table is generated in, luck for better generation, the LootContextParams which define scenario context, and any dynamic information that should occur on activation. The LootParams can be created using the constructor of the LootParams$Builder builder, and built via LootParams$Builder#create by passing in the LootContextParamSet.
A loot table may also have some context. The LootContext takes in the built LootParams and can set some random seeded instance. The context is created via the builder LootContext$Builder and built using LootContext$Builder#create by passing in a nullable ResourceLocation representing the random instance to use.
A LootTable can be used to generate ItemStacks using one of the available methods which may take in a LootParams or a LootContext:
| Method | Description |
|---|---|
getRandomItemsRaw |
Consumes the items generated by the loot table. |
getRandomItems |
Returns the items generated by the loot table. |
fill |
Fills a container with the generated loot table. |
Note
Loot tables were built for generating items, so the methods expect some handling for the ItemStacks.
Additional Features
Forge provides some additional behavior to loot tables for greater control of the system.
LootTableLoadEvent
LootTableLoadEvent is an event fired on the Forge event bus which is fired whenever a loot table is loaded. If the event is canceled, then an empty loot table will be loaded instead.
Important
Do not modify a loot table’s drops through this event. Those modifications should be done using global loot modifiers.
Loot Pool Names
Loot pools can be named using the name key. Any non-named loot pool will be the hash code of the pool prefixed by custom#.
// For some loot pool
{
"name": "example_pool", // Pool will be named 'example_pool'
"rolls": {
// ...
},
"entries": {
// ...
}
}
Looting Modifiers
Loot tables are now affected by the LootingLevelEvent, on the Forge event bus, in addition to the looting enchantment.
Additional Context Parameters
Forge extends certain parameter sets to account for missing contexts which may be applicable. LootContextParamSets#CHEST now allows for a LootContextParams#KILLER_ENTITY as chest minecarts are entities which can be broken (or ‘killed’). LootContextParamSets#FISHING also allows for a LootContextParams#KILLER_ENTITY since the fishing hook is also an entity which is retracted (or ‘killed’) when the player retrieves it.
Multiple Items on Smelting
When using the SmeltItemFunction, a smelted recipe will now return the actual number of items from the result instead of a single smelted item (e.g. if a smelting recipe returns 3 items and there are 3 drops, then the result would be 9 smelted items instead of 3).
Loot Table Id Condition
Forge adds an additional LootItemCondition which allows certain items to generate for a specific table. This is typically used within global loot modifiers.
// In some loot pool or pool entry
{
"conditions": [
{
"condition": "forge:loot_table_id",
// Will apply when the loot table is for dirt
"loot_table_id": "minecraft:blocks/dirt"
}
]
}
Can Tool Perform Action Condition
Forge adds an additional LootItemCondition which checks whether the given LootContextParams#TOOL can perform the specified ToolAction.
// In some loot pool or pool entry
{
"conditions": [
{
"condition": "forge:can_tool_perform_action",
// Will apply when the tool can strip a log like an axe
"action": "axe_strip"
}
]
}
Resources: Global Loot Modifiers
Global Loot Modifiers
Global Loot Modifiers are a data-driven method of handling modification of harvested drops without the need to overwrite dozens to hundreds of vanilla loot tables or to handle effects that would require interactions with another mod’s loot tables without knowing what mods may be loaded. Global Loot Modifiers are also stacking, rather than last-load-wins, similar to tags.
Registering a Global Loot Modifier
You will need 4 things:
- Create a
global_loot_modifiers.json. - This will tell Forge about your modifiers and works similar to tags. - A serialized json representing your modifier. - This will contain all of the data about your modification and allows data packs to tweak your effect.
- A class that extends
IGlobalLootModifier. - The operational code that makes your modifier work. Most modders can extendLootModifieras it supplies base functionality. - Finally, a codec to encode and decode your operational class.
- This is registered as any other
IForgeRegistryEntry.
The global_loot_modifiers.json
The global_loot_modifiers.json represents all loot modifiers to be loaded into the game. This file MUST be placed within data/forge/loot_modifiers/global_loot_modifiers.json.
Important
global_loot_modifiers.json will only be read in the forge namespace. The file will be neglected if it is under the mod’s namespace.
entries is an ordered list of the modifiers that will be loaded. The ResourceLocations specified points to their associated entry within data/<namespace>/loot_modifiers/<path>.json. This is primarily relevant to data pack makers for resolving conflicts between modifiers from separate mods.
replace, when true, changes the behavior from appending loot modifiers to the global list to replacing the global list entries entirely. Modders will want to use false for compatibility with other mod implementations. Datapack makers may want to specify their overrides with true.
{
"replace": false, // Must be present
"entries": [
// Represents a loot modifier in 'data/examplemod/loot_modifiers/example_glm.json'
"examplemod:example_glm",
"examplemod:example_glm2"
// ...
]
}
The Serialized JSON
This file contains all of the potential variables related to your modifier, including the conditions that must be met prior to modifying any loot. Avoid hard-coded values wherever possible so that data pack makers can adjust balance if they wish to.
type represents the registry name of the codec used to read the associated JSON file. This must always be present.
conditions should represent the loot table conditions for this modifier to activate. Conditions should avoid being hardcoded to allow datapack creators as much flexibility to adjust the criteria. This must also be always present.
Important
Although conditions should represent what is needed for the modifier to activate, this is only the case if using the bundled Forge classes. If using LootModifier as a subclass, all conditions will be ANDed together and checked to see if the modifier should be applied.
Any additional properties read by the serializer and defined by the modifier can also be specified.
// Within data/examplemod/loot_modifiers/example_glm.json
{
"type": "examplemod:example_loot_modifier",
"conditions": [
// Normal loot table conditions
// ...
],
"prop1": "val1",
"prop2": 10,
"prop3": "minecraft:dirt"
}
IGlobalLootModifier
To supply the functionality a global loot modifier specifies, a IGlobalLootModifier implementation must be specified. These are instances generated each time a serializer decodes the information from JSON and supplies it into this object.
There are two methods that needs to be defined in order to create a new modifier: #apply and #codec. #apply takes in the current loot that will be generated along with the context information such as the currently level or additional defined parameters. It returns the list of drops to generate.
Note
The returned list of drops from any one modifier is fed into other modifiers in the order they are registered. As such, modified loot can be modified by another loot modifier.
#codec returns the registered codec used to encode and decode the modifier to/from JSON.
The LootModifier Subclass
LootModifier is an abstract implementation of IGlobalLootModifier to provide the base functionality which most modders can easily extend and implement. This expands upon the existing interface by defining the #apply method to check the conditions to determine whether or not to modify the generated loot.
There are two things of note within the subclass implementation: the constructor which must take in an array of LootItemConditions and the #doApply method.
The array of LootItemConditions define the list of conditions that must be true before the loot can be modified. The supplied conditions are ANDed together, meaning that all conditions must be true.
The #doApply method works the same as the #apply method except that it only executes once all conditions return true.
public class ExampleModifier extends LootModifier {
public ExampleModifier(LootItemCondition[] conditionsIn, String prop1, int prop2, Item prop3) {
super(conditionsIn);
// Store the rest of the parameters
}
@NotNull
@Override
protected ObjectArrayList<ItemStack> doApply(ObjectArrayList<ItemStack> generatedLoot, LootContext context) {
// Modify the loot and return the new drops
}
@Override
public Codec<? extends IGlobalLootModifier> codec() {
// Return the codec used to encode and decode this modifier
}
}
The Loot Modifier Codec
The connector between the JSON and the IGlobalLootModifier instance is a Codec<T>, where T represents the type of the IGlobalLootModifier to use.
For ease of convenience, a loot conditions codec has been provided for an easy addition to a record-like codec via LootModifier#codecStart. This is utilized for data generation of the associated loot modifier.
// For some DeferredRegister<Codec<? extends IGlobalLootModifier>> REGISTRAR
public static final RegistryObject<Codec<ExampleModifier>> = REGISTRAR.register("example_codec", () ->
RecordCodecBuilder.create(
inst -> LootModifier.codecStart(inst).and(
inst.group(
Codec.STRING.fieldOf("prop1").forGetter(m -> m.prop1),
Codec.INT.fieldOf("prop2").forGetter(m -> m.prop2),
ForgeRegistries.ITEMS.getCodec().fieldOf("prop3").forGetter(m -> m.prop3)
)
).apply(inst, ExampleModifier::new)
)
);
Examples can be found on the Forge Git repository, including silk touch and smelting effects.
Resources: Tags
Tags
Tags are generalized sets of objects in the game used for grouping related things together and providing fast membership checks.
Finding Tags
When looking for existing tags, there’s two main places to check:
Vanilla Tags
Vanilla tags are declared in the net.minecraft.tags package. For example, BlockTags contains all the Vanilla block tags, BiomeTags contains all the Vanilla biome tags, and so on.
Forge Tags
Forge bundles additional tags useful for mods, both Forge-specific and de-facto common tags that apply across all major mod loaders. You can find all of them in the net.minecraftforge.common.Tags class. The method names for each of the fields as well as code comment groups should make it clear which is a Forge-specific tag and which is a common tag.
Warning
The common c namespaced tags seen in Forge are common across all loaders, however other loaders may have additional loader-specific tags under the same c namespace. When making a multi-loader mod, it is recommended to check the tags for each loader to ensure compatibility if you are considering a c tag you saw on other loaders that is missing in Forge. Loader-specific c tags may be in Forge under the forge namespace until they become common across all loaders.
Full list of tags in Forge
You can find a full list of tags Forge adds on top of Vanilla Minecraft here.
Declaring Your Own Groupings
Tags are declared in your mod’s datapack. For example, a TagKey<Block> with a given identifier of modid:foo/tagname will reference a tag at /data/<modid>/tags/blocks/foo/tagname.json. Tags for Blocks, Items, EntityTypes, Fluids, and GameEvents use the plural forms for their folder location while all other registries use the singular version (EntityType uses the folder entity_types while Potion would use the folder potion).
Similarly, you may append to or override tags declared in other domains, such as Vanilla, by declaring your own JSONs.
For example, to add your own mod’s saplings to the Vanilla sapling tag, you would specify it in /data/minecraft/tags/blocks/saplings.json, and Vanilla will merge everything into one tag at reload, if the replace option is false.
If replace is true, then all entries before the json specifying replace will be removed.
Values listed that are not present will cause the tag to error unless the value is listed using an id string and required boolean set to false, as in the following example:
{
"replace": false,
"values": [
"minecraft:gold_ingot",
"mymod:my_ingot",
{
"id": "othermod:ingot_other",
"required": false
}
]
}
See the Vanilla wiki for a description of the base syntax.
There is also a Forge extension on the Vanilla syntax.
You may declare a remove array of the same format as the values array. Any values listed here will be removed from the tag. This acts as a finer grained version of the Vanilla replace option.
Using Tags In Code
Tags for all registries are automatically sent from the server to any remote clients on login and reload. Blocks, Items, EntityTypes, Fluids, and GameEvents are special cased as they have Holders allowing for available tags to be accessible through the object itself.
Note
Intrusive Holders may be removed in a future version of Minecraft. If they are, the below methods can be used instead to query the associated Holders.
ITagManager
Forge wrapped registries provide an additional helper for creating and managing tags through ITagManager which can be obtained via IForgeRegistry#tags. Tags can be created using using #createTagKey or #createOptionalTagKey. Tags or registry objects can also be checked for either or using #getTag or #getReverseTag respectively.
Custom Registries
Custom registries can create tags when constructing their DeferredRegister via #createTagKey or #createOptionalTagKey respectively. Their tags or registry objects can then checked for either using the IForgeRegistry obtained by calling DeferredRegister#makeRegistry.
Referencing Tags
There are four methods of creating a tag wrapper:
| Method | For |
|---|---|
*Tags#create |
BannerPattern, Biome, Block, CatVariant, DamageType, EntityType, FlatLevelGeneratorPreset, Fluid, GameEvent, Instrument, Item, PaintingVariant, PoiType, Structure, and WorldPreset where * represents one of these types. |
ITagManager#createTagKey |
Forge wrapped vanilla registries, registries can be obtained from ForgeRegistries. |
DeferredRegister#createTagKey |
Custom forge registries. |
TagKey#create |
Vanilla registries without forge wrappers, registries can be obtained from Registry. |
Registry objects can check their tags or registry objects either through their Holder or through ITag/IReverseTag for vanilla or forge registry objects respectively.
Vanilla registry objects can grab their associated holder using either Registry#getHolder or Registry#getHolderOrThrow and then compare if the registry object has a tag using Holder#is.
Forge registry objects can grab their tag definition using either ITagManager#getTag or ITagManager#getReverseTag and then compare if a registry object has a tag using ITag#contains or IReverseTag#containsTag respectively.
Tag-holding registry objects contain a method called #is in either their registry object or state-aware class to check whether the object belongs to a certain tag.
As an example:
public static final TagKey<Item> myItemTag = ItemTags.create(ResourceLocation.fromNamespaceAndPath("mymod", "myitemgroup"));
public static final TagKey<Potion> myPotionTag = ForgeRegistries.POTIONS.tags().createTagKey(ResourceLocation.fromNamespaceAndPath("mymod", "mypotiongroup"));
public static final TagKey<VillagerType> myVillagerTypeTag = TagKey.create(Registries.VILLAGER_TYPE, ResourceLocation.fromNamespaceAndPath("mymod", "myvillagertypegroup"));
// In some method:
ItemStack stack = /*...*/;
boolean isInItemGroup = stack.is(myItemTag);
Potion potion = /*...*/;
boolean isInPotionGroup = ForgeRegistries.POTIONS.tags().getTag(myPotionTag).contains(potion);
ResourceKey<VillagerType> villagerTypeKey = /*...*/;
boolean isInVillagerTypeGroup = BuiltInRegistries.VILLAGER_TYPE.getHolder(villagerTypeKey).map(holder -> holder.is(myVillagerTypeTag)).orElse(false);
Conventions
There are several conventions that will help facilitate compatibility in the ecosystem:
- If there is a Vanilla tag that fits your block or item, add it to that tag. See the list of Vanilla tags.
- If there is a Forge tag that fits your block or item, add it to that tag. The list of tags declared by Forge can be seen on GitHub.
- If there is a group of something you feel should be shared by the community, use the
forgenamespace instead of your mod id. - Tag naming conventions should follow Vanilla conventions. In particular, item and block groupings are plural instead of singular (e.g.
minecraft:logs,minecraft:saplings). - Item tags should be sorted into subdirectories according to their type (e.g.
forge:ingots/iron,forge:nuggets/brass, etc.).
Migration from OreDictionary
- For recipes, tags can be used directly in the vanilla recipe format (see below).
- For matching items in code, see the section above.
- If you are declaring a new type of item grouping, follow a couple naming conventions:
- Use
domain:type/material. When the name is a common one that all modders should adopt, use theforgedomain. - For example, brass ingots should be registered under the
forge:ingots/brasstag and cobalt nuggets under theforge:nuggets/cobalttag.
Using Tags in Recipes and Advancements
Tags are directly supported by Vanilla. See the respective Vanilla wiki pages for recipes and advancements for usage details.
Resources: Advancements
Advancements
Advancements are tasks that can be achieved by the player which may advance the progress of the game. Advancements can trigger based on any action the player may be directly involved in.
All advancement implementations within vanilla are data driven via JSON. This means that a mod is not necessary to create a new advancement, only a data pack. A full list on how to create and put these advancements within the mod’s resources can be found on the Minecraft Wiki. Additionally, advancements can be loaded conditionally and defaulted depending on what information is present (mod loaded, item exists, etc.).
Advancement Criteria
To unlock an advancement, the specified criteria must be met. Criteria are tracked through triggers which execute when a certain action is performed: killing an entity, changing an inventory, breading animals, etc. Any time an advancement is loaded into the game, the criteria defined are read and added as listeners to the trigger. Afterwards a trigger function is called (usually named #trigger) which checks all listeners as to whether the current state meets the conditions of the advancement criteria. The criteria listeners for the advancement are only removed once the advancement has been obtained by completing all requirements.
Requirements are defined as an array of string arrays representing the name of the criteria specified on the advancement. An advancement is completed once one string array of criteria has been met:
// In some advancement JSON
// List of defined criteria to meet
"criteria": {
"example_criterion1": { /*...*/ },
"example_criterion2": { /*...*/ },
"example_criterion3": { /*...*/ },
"example_criterion4": { /*...*/ }
},
// This advancement is only unlocked once
// - Criteria 1 AND 2 have been met
// OR
// - Criteria 3 and 4 have been met
"requirements": [
[
"example_criterion1",
"example_criterion2"
],
[
"example_criterion3",
"example_criterion4"
]
]
A list of criteria triggers defined by vanilla can be found in CriteriaTriggers. Additionally, the JSON formats are defined on the Minecraft Wiki.
Custom Criteria Triggers
Custom criteria triggers can be created by implementing SimpleCriterionTrigger for the created AbstractCriterionTriggerInstance subclass.
AbstractCriterionTriggerInstance Subclass
The AbstractCriterionTriggerInstance represents a single criteria defined in the criteria object. Trigger instances are responsible for holding the defined conditions, returning whether the inputs match the condition, and writing the instance to JSON for data generation.
Conditions are usually passed in through the constructor. The AbstractCriterionTriggerInstance super constructor requires the instance to define the registry name of the trigger and the conditions the player must meet as an ContextAwarePredicate. The registry name of the trigger should be supplied to the super directly while the conditions of the player should be a constructor parameter.
// Where ID is the registry name of the trigger
public ExampleTriggerInstance(ContextAwarePredicate player, ItemPredicate item) {
super(ID, player);
// Store the item condition that must be met
}
Note
Typically, trigger instances have a static constructor which allow these instances to be easily created for data generation. These static factory methods can also be statically imported instead of the class itself.
public static ExampleTriggerInstance instance(ContextAwarePredicate player, ItemPredicate item) {
return new ExampleTriggerInstance(player, item);
}
Additionally, the #serializeToJson method should be overridden. The method should add the conditions of the instance to the other JSON data.
@Override
public JsonObject serializeToJson(SerializationContext context) {
JsonObject obj = super.serializeToJson(context);
// Write conditions to json
return obj;
}
Finally, a method should be added which takes in the current data state and returns whether the user has met the necessary conditions. The conditions of the player are already checked through SimpleCriterionTrigger#trigger(ServerPlayer, Predicate). Most trigger instances call this method #matches.
// This method is unique for each instance and is as such not overridden
public boolean matches(ItemStack stack) {
// Since ItemPredicate matches a stack, a stack is the input
return this.item.matches(stack);
}
SimpleCriterionTrigger
The SimpleCriterionTrigger<T> subclass, where T is the type of the trigger instance, is responsible for specifying the registry name of the trigger, creating a trigger instance, and a method to check trigger instances and run attached listeners on success.
The registry name of the trigger is supplied to #getId. This should match the registry name supplied to the trigger instance.
A trigger instance is created via #createInstance. This method reads a criteria from JSON.
@Override
public ExampleTriggerInstance createInstance(JsonObject json, ContextAwarePredicate player, DeserializationContext context) {
// Read conditions from JSON: item
return new ExampleTriggerInstance(player, item);
}
Finally, a method is defined to check all trigger instances and run the listeners if their condition is met. This method takes in the ServerPlayer and whatever other data defined by the matching method in the AbstractCriterionTriggerInstance subclass. This method should internally call SimpleCriterionTrigger#trigger to properly handle checking all listeners. Most trigger instances call this method #trigger.
// This method is unique for each trigger and is as such not overridden
public void trigger(ServerPlayer player, ItemStack stack) {
this.trigger(player,
// The condition checker method within the AbstractCriterionTriggerInstance subclass
triggerInstance -> triggerInstance.matches(stack)
);
}
Afterwards, an instance should be registered using CriteriaTriggers#register during FMLCommonSetupEvent.
Important
CriteriaTriggers#register must be enqueued to the synchronous work queue via FMLCommonSetupEvent#enqueueWork as the method is not thread-safe.
Calling the Trigger
Whenever the action being checked is performed, the #trigger method defined by the SimpleCriterionTrigger subclass should be called.
// In some piece of code where the action is being performed
// Where EXAMPLE_CRITERIA_TRIGGER is the custom criteria trigger
public void performExampleAction(ServerPlayer player, ItemStack stack) {
// Run code to perform action
EXAMPLE_CRITERIA_TRIGGER.trigger(player, stack);
}
Advancement Rewards
When an advancement is completed, rewards may be given out. These can be a combination of experience points, loot tables, recipes for the recipe book, or a function executed as a creative player.
// In some advancement JSON
"rewards": {
"experience": 10,
"loot": [
"minecraft:example_loot_table",
"minecraft:example_loot_table2"
// ...
],
"recipes": [
"minecraft:example_recipe",
"minecraft:example_recipe2"
// ...
],
"function": "minecraft:example_function"
}
Resources: Conditionally-Loaded Data
Conditionally-Loaded Data
There are times when modders may want to include data-driven objects using information from another mod without having to explicitly make that mod a dependency. Other cases may be to swap out certain objects with other modded entries when they are present. This can be done through the conditional subsystem.
Implementations
Currently, conditional loading is implemented for recipes and advancements. For any conditional recipe or advancement, a list of conditions to datum pair is loaded. If the conditions specified for a datum in the list is true, then that datum is returned. Otherwise, the datum is discarded.
{
// The type needs to be specified for recipes as they can have custom serializers
// Advancements do not need this type
"type": "forge:conditional",
"recipes": [ // Or 'advancements' for Advancements
{
// The conditions to check
"conditions": [
// Conditions in the list are ANDed together
{
// Condition 1
},
{
// Condition 2
}
],
"recipe": { // Or 'advancement' for Advancements
// The recipe to use if all conditions succeed
}
},
{
// Next condition to check if the previous fails
},
]
}
Conditionally-loaded data additionally have wrappers for data generation through ConditionalRecipe$Builder and ConditionalAdvancement$Builder.
Conditions
Conditions are specified by setting type to the name of the condition as specified by IConditionSerializer#getID.
True and False
Boolean conditions consist of no data and return the expected value of the condition. They are represented by forge:true and forge:false.
// For some condition
{
// Will always return true (or false for 'forge:false')
"type": "forge:true"
}
Not, And, and Or
Boolean operator conditions consist of the condition(s) being operated upon and apply the following logic. They are represented by forge:not, forge:and, and forge:or.
// For some condition
{
// Inverts the result of the stored condition
"type": "forge:not",
"value": {
// A condition
}
}
// For some condition
{
// ANDs the stored conditions together (or ORs for 'forge:or')
"type": "forge:and",
"values": [
{
// First condition
},
{
// Second condition to be ANDed (or ORed for 'forge:or')
}
]
}
Mod Loaded
ModLoadedCondition returns true whenever the specified mod with the given id is loaded in the current application. This is represented by forge:mod_loaded.
// For some condition
{
"type": "forge:mod_loaded",
// Returns true if 'examplemod' is loaded
"modid": "examplemod"
}
Item Exists
ItemExistsCondition returns true whenever the given item has been registered in the current application. This is represented by forge:item_exists.
// For some condition
{
"type": "forge:item_exists",
// Returns true if 'examplemod:example_item' has been registered
"item": "examplemod:example_item"
}
Tag Empty
TagEmptyCondition returns true whenever the given item tag has no items within it. This is represented by forge:tag_empty.
// For some condition
{
"type": "forge:tag_empty",
// Returns true if 'examplemod:example_tag' is an item tag with no entries
"tag": "examplemod:example_tag"
}
Creating Custom Conditions
Custom conditions can be created by implementing ICondition and its associated IConditionSerializer.
ICondition
Any condition only need to implement two methods:
| Method | Description |
|---|---|
| getID | The registry name of the condition. Must be equivalent to IConditionSerializer#getID. Used only for data generation. |
| test | Returns true if the condition has been satisfied. |
Note
Every #test has access to some IContext representing the state of the game. Currently, only tags can be obtained from a registry.
IConditionSerializer
Serializers need to implement three methods:
| Method | Description |
|---|---|
| getID | The registry name of the condition. Must be equivalent to ICondition#getID. |
| read | Reads the condition data from JSON. |
| write | Writes the given condition data to JSON. |
Note
Condition serializers are not responsible for writing or reading the type of the serializer, similar to other serializer implementations in Minecraft.
Afterwards, a static instance should be declared to hold the initialized serializer and then registered using CraftingHelper#register either during the RegisterEvent for RecipeSerializers or during FMLCommonSetupEvent.
// In some serializer class
public static final ExampleConditionSerializer INSTANCE = new ExampleConditionSerializer();
// In some handler class
public void registerSerializers(RegisterEvent event) {
event.register(ForgeRegistries.Keys.RECIPE_SERIALIZERS,
helper -> CraftingHelper.register(INSTANCE)
);
}
Important
If using FMLCommonSetupEvent to register a condition serializer, it must be enqueued to the synchronous work queue via FMLCommonSetupEvent#enqueueWork as CraftingHelper#register is not thread-safe.
Resources: Tags List
Tags list
Forge bundles many tags useful for mods, both Forge-specific and de-facto common tags that apply across all major mod loaders. You can find all of them in the net.minecraftforge.common.Tags class. This page lists all those tags and their contents.
Note
This page does not include Vanilla tags. Refer to the net.minecraft.tags package for those.
This page is generated from the CommonTagsDumper and is correct as of Forge 52.0.20. Note that not all builds of Forge contain tag changes, so just because this page references an older build does not mean this page is outdated. However, you should treat the actual generated JSONs on the Forge GitHub repository found here as the ground truth. This page is provided for convenience and is not guaranteed to be up-to-date.
block
c:barrelsminecraft:barrelc:barrels/woodenminecraft:barrelc:bookshelvesminecraft:bookshelfc:budding_blocksminecraft:budding_amethystc:budsminecraft:large_amethyst_budminecraft:medium_amethyst_budminecraft:small_amethyst_budc:chainsminecraft:chainc:chestsminecraft:chestminecraft:ender_chestminecraft:trapped_chestc:chests/woodenminecraft:chestminecraft:trapped_chestc:clustersminecraft:amethyst_clusterc:cobblestonesminecraft:cobbled_deepslateminecraft:cobblestoneminecraft:infested_cobblestoneminecraft:mossy_cobblestonec:concretesminecraft:black_concreteminecraft:blue_concreteminecraft:brown_concreteminecraft:cyan_concreteminecraft:gray_concreteminecraft:green_concreteminecraft:light_blue_concreteminecraft:light_gray_concreteminecraft:lime_concreteminecraft:magenta_concreteminecraft:orange_concreteminecraft:pink_concreteminecraft:purple_concreteminecraft:red_concreteminecraft:white_concreteminecraft:yellow_concretec:dyedminecraft:black_bannerminecraft:black_bedminecraft:black_candleminecraft:black_carpetminecraft:black_concreteminecraft:black_concrete_powderminecraft:black_glazed_terracottaminecraft:black_shulker_boxminecraft:black_stained_glassminecraft:black_stained_glass_paneminecraft:black_terracottaminecraft:black_wall_bannerminecraft:black_woolminecraft:blue_bannerminecraft:blue_bedminecraft:blue_candleminecraft:blue_carpetminecraft:blue_concreteminecraft:blue_concrete_powderminecraft:blue_glazed_terracottaminecraft:blue_shulker_boxminecraft:blue_stained_glassminecraft:blue_stained_glass_paneminecraft:blue_terracottaminecraft:blue_wall_bannerminecraft:blue_woolminecraft:brown_bannerminecraft:brown_bedminecraft:brown_candleminecraft:brown_carpetminecraft:brown_concreteminecraft:brown_concrete_powderminecraft:brown_glazed_terracottaminecraft:brown_shulker_boxminecraft:brown_stained_glassminecraft:brown_stained_glass_paneminecraft:brown_terracottaminecraft:brown_wall_bannerminecraft:brown_woolminecraft:cyan_bannerminecraft:cyan_bedminecraft:cyan_candleminecraft:cyan_carpetminecraft:cyan_concreteminecraft:cyan_concrete_powderminecraft:cyan_glazed_terracottaminecraft:cyan_shulker_boxminecraft:cyan_stained_glassminecraft:cyan_stained_glass_paneminecraft:cyan_terracottaminecraft:cyan_wall_bannerminecraft:cyan_woolminecraft:gray_bannerminecraft:gray_bedminecraft:gray_candleminecraft:gray_carpetminecraft:gray_concreteminecraft:gray_concrete_powderminecraft:gray_glazed_terracottaminecraft:gray_shulker_boxminecraft:gray_stained_glassminecraft:gray_stained_glass_paneminecraft:gray_terracottaminecraft:gray_wall_bannerminecraft:gray_woolminecraft:green_bannerminecraft:green_bedminecraft:green_candleminecraft:green_carpetminecraft:green_concreteminecraft:green_concrete_powderminecraft:green_glazed_terracottaminecraft:green_shulker_boxminecraft:green_stained_glassminecraft:green_stained_glass_paneminecraft:green_terracottaminecraft:green_wall_bannerminecraft:green_woolminecraft:light_blue_bannerminecraft:light_blue_bedminecraft:light_blue_candleminecraft:light_blue_carpetminecraft:light_blue_concreteminecraft:light_blue_concrete_powderminecraft:light_blue_glazed_terracottaminecraft:light_blue_shulker_boxminecraft:light_blue_stained_glassminecraft:light_blue_stained_glass_paneminecraft:light_blue_terracottaminecraft:light_blue_wall_bannerminecraft:light_blue_woolminecraft:light_gray_bannerminecraft:light_gray_bedminecraft:light_gray_candleminecraft:light_gray_carpetminecraft:light_gray_concreteminecraft:light_gray_concrete_powderminecraft:light_gray_glazed_terracottaminecraft:light_gray_shulker_boxminecraft:light_gray_stained_glassminecraft:light_gray_stained_glass_paneminecraft:light_gray_terracottaminecraft:light_gray_wall_bannerminecraft:light_gray_woolminecraft:lime_bannerminecraft:lime_bedminecraft:lime_candleminecraft:lime_carpetminecraft:lime_concreteminecraft:lime_concrete_powderminecraft:lime_glazed_terracottaminecraft:lime_shulker_boxminecraft:lime_stained_glassminecraft:lime_stained_glass_paneminecraft:lime_terracottaminecraft:lime_wall_bannerminecraft:lime_woolminecraft:magenta_bannerminecraft:magenta_bedminecraft:magenta_candleminecraft:magenta_carpetminecraft:magenta_concreteminecraft:magenta_concrete_powderminecraft:magenta_glazed_terracottaminecraft:magenta_shulker_boxminecraft:magenta_stained_glassminecraft:magenta_stained_glass_paneminecraft:magenta_terracottaminecraft:magenta_wall_bannerminecraft:magenta_woolminecraft:orange_bannerminecraft:orange_bedminecraft:orange_candleminecraft:orange_carpetminecraft:orange_concreteminecraft:orange_concrete_powderminecraft:orange_glazed_terracottaminecraft:orange_shulker_boxminecraft:orange_stained_glassminecraft:orange_stained_glass_paneminecraft:orange_terracottaminecraft:orange_wall_bannerminecraft:orange_woolminecraft:pink_bannerminecraft:pink_bedminecraft:pink_candleminecraft:pink_carpetminecraft:pink_concreteminecraft:pink_concrete_powderminecraft:pink_glazed_terracottaminecraft:pink_shulker_boxminecraft:pink_stained_glassminecraft:pink_stained_glass_paneminecraft:pink_terracottaminecraft:pink_wall_bannerminecraft:pink_woolminecraft:purple_bannerminecraft:purple_bedminecraft:purple_candleminecraft:purple_carpetminecraft:purple_concreteminecraft:purple_concrete_powderminecraft:purple_glazed_terracottaminecraft:purple_shulker_boxminecraft:purple_stained_glassminecraft:purple_stained_glass_paneminecraft:purple_terracottaminecraft:purple_wall_bannerminecraft:purple_woolminecraft:red_bannerminecraft:red_bedminecraft:red_candleminecraft:red_carpetminecraft:red_concreteminecraft:red_concrete_powderminecraft:red_glazed_terracottaminecraft:red_shulker_boxminecraft:red_stained_glassminecraft:red_stained_glass_paneminecraft:red_terracottaminecraft:red_wall_bannerminecraft:red_woolminecraft:white_bannerminecraft:white_bedminecraft:white_candleminecraft:white_carpetminecraft:white_concreteminecraft:white_concrete_powderminecraft:white_glazed_terracottaminecraft:white_shulker_boxminecraft:white_stained_glassminecraft:white_stained_glass_paneminecraft:white_terracottaminecraft:white_wall_bannerminecraft:white_woolminecraft:yellow_bannerminecraft:yellow_bedminecraft:yellow_candleminecraft:yellow_carpetminecraft:yellow_concreteminecraft:yellow_concrete_powderminecraft:yellow_glazed_terracottaminecraft:yellow_shulker_boxminecraft:yellow_stained_glassminecraft:yellow_stained_glass_paneminecraft:yellow_terracottaminecraft:yellow_wall_bannerminecraft:yellow_woolc:dyed/blackminecraft:black_bannerminecraft:black_bedminecraft:black_candleminecraft:black_carpetminecraft:black_concreteminecraft:black_concrete_powderminecraft:black_glazed_terracottaminecraft:black_shulker_boxminecraft:black_stained_glassminecraft:black_stained_glass_paneminecraft:black_terracottaminecraft:black_wall_bannerminecraft:black_woolc:dyed/blueminecraft:blue_bannerminecraft:blue_bedminecraft:blue_candleminecraft:blue_carpetminecraft:blue_concreteminecraft:blue_concrete_powderminecraft:blue_glazed_terracottaminecraft:blue_shulker_boxminecraft:blue_stained_glassminecraft:blue_stained_glass_paneminecraft:blue_terracottaminecraft:blue_wall_bannerminecraft:blue_woolc:dyed/brownminecraft:brown_bannerminecraft:brown_bedminecraft:brown_candleminecraft:brown_carpetminecraft:brown_concreteminecraft:brown_concrete_powderminecraft:brown_glazed_terracottaminecraft:brown_shulker_boxminecraft:brown_stained_glassminecraft:brown_stained_glass_paneminecraft:brown_terracottaminecraft:brown_wall_bannerminecraft:brown_woolc:dyed/cyanminecraft:cyan_bannerminecraft:cyan_bedminecraft:cyan_candleminecraft:cyan_carpetminecraft:cyan_concreteminecraft:cyan_concrete_powderminecraft:cyan_glazed_terracottaminecraft:cyan_shulker_boxminecraft:cyan_stained_glassminecraft:cyan_stained_glass_paneminecraft:cyan_terracottaminecraft:cyan_wall_bannerminecraft:cyan_woolc:dyed/grayminecraft:gray_bannerminecraft:gray_bedminecraft:gray_candleminecraft:gray_carpetminecraft:gray_concreteminecraft:gray_concrete_powderminecraft:gray_glazed_terracottaminecraft:gray_shulker_boxminecraft:gray_stained_glassminecraft:gray_stained_glass_paneminecraft:gray_terracottaminecraft:gray_wall_bannerminecraft:gray_woolc:dyed/greenminecraft:green_bannerminecraft:green_bedminecraft:green_candleminecraft:green_carpetminecraft:green_concreteminecraft:green_concrete_powderminecraft:green_glazed_terracottaminecraft:green_shulker_boxminecraft:green_stained_glassminecraft:green_stained_glass_paneminecraft:green_terracottaminecraft:green_wall_bannerminecraft:green_woolc:dyed/light_blueminecraft:light_blue_bannerminecraft:light_blue_bedminecraft:light_blue_candleminecraft:light_blue_carpetminecraft:light_blue_concreteminecraft:light_blue_concrete_powderminecraft:light_blue_glazed_terracottaminecraft:light_blue_shulker_boxminecraft:light_blue_stained_glassminecraft:light_blue_stained_glass_paneminecraft:light_blue_terracottaminecraft:light_blue_wall_bannerminecraft:light_blue_woolc:dyed/light_grayminecraft:light_gray_bannerminecraft:light_gray_bedminecraft:light_gray_candleminecraft:light_gray_carpetminecraft:light_gray_concreteminecraft:light_gray_concrete_powderminecraft:light_gray_glazed_terracottaminecraft:light_gray_shulker_boxminecraft:light_gray_stained_glassminecraft:light_gray_stained_glass_paneminecraft:light_gray_terracottaminecraft:light_gray_wall_bannerminecraft:light_gray_woolc:dyed/limeminecraft:lime_bannerminecraft:lime_bedminecraft:lime_candleminecraft:lime_carpetminecraft:lime_concreteminecraft:lime_concrete_powderminecraft:lime_glazed_terracottaminecraft:lime_shulker_boxminecraft:lime_stained_glassminecraft:lime_stained_glass_paneminecraft:lime_terracottaminecraft:lime_wall_bannerminecraft:lime_woolc:dyed/magentaminecraft:magenta_bannerminecraft:magenta_bedminecraft:magenta_candleminecraft:magenta_carpetminecraft:magenta_concreteminecraft:magenta_concrete_powderminecraft:magenta_glazed_terracottaminecraft:magenta_shulker_boxminecraft:magenta_stained_glassminecraft:magenta_stained_glass_paneminecraft:magenta_terracottaminecraft:magenta_wall_bannerminecraft:magenta_woolc:dyed/orangeminecraft:orange_bannerminecraft:orange_bedminecraft:orange_candleminecraft:orange_carpetminecraft:orange_concreteminecraft:orange_concrete_powderminecraft:orange_glazed_terracottaminecraft:orange_shulker_boxminecraft:orange_stained_glassminecraft:orange_stained_glass_paneminecraft:orange_terracottaminecraft:orange_wall_bannerminecraft:orange_woolc:dyed/pinkminecraft:pink_bannerminecraft:pink_bedminecraft:pink_candleminecraft:pink_carpetminecraft:pink_concreteminecraft:pink_concrete_powderminecraft:pink_glazed_terracottaminecraft:pink_shulker_boxminecraft:pink_stained_glassminecraft:pink_stained_glass_paneminecraft:pink_terracottaminecraft:pink_wall_bannerminecraft:pink_woolc:dyed/purpleminecraft:purple_bannerminecraft:purple_bedminecraft:purple_candleminecraft:purple_carpetminecraft:purple_concreteminecraft:purple_concrete_powderminecraft:purple_glazed_terracottaminecraft:purple_shulker_boxminecraft:purple_stained_glassminecraft:purple_stained_glass_paneminecraft:purple_terracottaminecraft:purple_wall_bannerminecraft:purple_woolc:dyed/redminecraft:red_bannerminecraft:red_bedminecraft:red_candleminecraft:red_carpetminecraft:red_concreteminecraft:red_concrete_powderminecraft:red_glazed_terracottaminecraft:red_shulker_boxminecraft:red_stained_glassminecraft:red_stained_glass_paneminecraft:red_terracottaminecraft:red_wall_bannerminecraft:red_woolc:dyed/whiteminecraft:white_bannerminecraft:white_bedminecraft:white_candleminecraft:white_carpetminecraft:white_concreteminecraft:white_concrete_powderminecraft:white_glazed_terracottaminecraft:white_shulker_boxminecraft:white_stained_glassminecraft:white_stained_glass_paneminecraft:white_terracottaminecraft:white_wall_bannerminecraft:white_woolc:dyed/yellowminecraft:yellow_bannerminecraft:yellow_bedminecraft:yellow_candleminecraft:yellow_carpetminecraft:yellow_concreteminecraft:yellow_concrete_powderminecraft:yellow_glazed_terracottaminecraft:yellow_shulker_boxminecraft:yellow_stained_glassminecraft:yellow_stained_glass_paneminecraft:yellow_terracottaminecraft:yellow_wall_bannerminecraft:yellow_woolc:glass_blocksminecraft:black_stained_glassminecraft:blue_stained_glassminecraft:brown_stained_glassminecraft:cyan_stained_glassminecraft:glassminecraft:gray_stained_glassminecraft:green_stained_glassminecraft:light_blue_stained_glassminecraft:light_gray_stained_glassminecraft:lime_stained_glassminecraft:magenta_stained_glassminecraft:orange_stained_glassminecraft:pink_stained_glassminecraft:purple_stained_glassminecraft:red_stained_glassminecraft:tinted_glassminecraft:white_stained_glassminecraft:yellow_stained_glassc:glass_blocks/cheapminecraft:black_stained_glassminecraft:blue_stained_glassminecraft:brown_stained_glassminecraft:cyan_stained_glassminecraft:glassminecraft:gray_stained_glassminecraft:green_stained_glassminecraft:light_blue_stained_glassminecraft:light_gray_stained_glassminecraft:lime_stained_glassminecraft:magenta_stained_glassminecraft:orange_stained_glassminecraft:pink_stained_glassminecraft:purple_stained_glassminecraft:red_stained_glassminecraft:white_stained_glassminecraft:yellow_stained_glassc:glass_blocks/colorlessminecraft:glassc:glass_blocks/tintedminecraft:tinted_glassc:glass_panesminecraft:black_stained_glass_paneminecraft:blue_stained_glass_paneminecraft:brown_stained_glass_paneminecraft:cyan_stained_glass_paneminecraft:glass_paneminecraft:gray_stained_glass_paneminecraft:green_stained_glass_paneminecraft:light_blue_stained_glass_paneminecraft:light_gray_stained_glass_paneminecraft:lime_stained_glass_paneminecraft:magenta_stained_glass_paneminecraft:orange_stained_glass_paneminecraft:pink_stained_glass_paneminecraft:purple_stained_glass_paneminecraft:red_stained_glass_paneminecraft:white_stained_glass_paneminecraft:yellow_stained_glass_panec:glass_panes/colorlessminecraft:glass_panec:glazed_terracottasminecraft:black_glazed_terracottaminecraft:blue_glazed_terracottaminecraft:brown_glazed_terracottaminecraft:cyan_glazed_terracottaminecraft:gray_glazed_terracottaminecraft:green_glazed_terracottaminecraft:light_blue_glazed_terracottaminecraft:light_gray_glazed_terracottaminecraft:lime_glazed_terracottaminecraft:magenta_glazed_terracottaminecraft:orange_glazed_terracottaminecraft:pink_glazed_terracottaminecraft:purple_glazed_terracottaminecraft:red_glazed_terracottaminecraft:white_glazed_terracottaminecraft:yellow_glazed_terracottac:hidden_from_recipe_viewersc:obsidiansminecraft:crying_obsidianminecraft:obsidianc:obsidians/cryingminecraft:crying_obsidianc:obsidians/normalminecraft:obsidianc:oresminecraft:ancient_debrisminecraft:coal_oreminecraft:copper_oreminecraft:deepslate_coal_oreminecraft:deepslate_copper_oreminecraft:deepslate_diamond_oreminecraft:deepslate_emerald_oreminecraft:deepslate_gold_oreminecraft:deepslate_iron_oreminecraft:deepslate_lapis_oreminecraft:deepslate_redstone_oreminecraft:diamond_oreminecraft:emerald_oreminecraft:gold_oreminecraft:iron_oreminecraft:lapis_oreminecraft:nether_gold_oreminecraft:nether_quartz_oreminecraft:redstone_orec:ores/netherite_scrapminecraft:ancient_debrisc:ores/quartzminecraft:nether_quartz_orec:player_workstations/crafting_tablesminecraft:crafting_tablec:player_workstations/furnacesminecraft:furnacec:relocation_not_supportedc:ropesc:sandstone/blocksminecraft:chiseled_red_sandstoneminecraft:chiseled_sandstoneminecraft:cut_red_sandstoneminecraft:cut_sandstoneminecraft:red_sandstoneminecraft:sandstoneminecraft:smooth_red_sandstoneminecraft:smooth_sandstonec:sandstone/red_blocksminecraft:chiseled_red_sandstoneminecraft:cut_red_sandstoneminecraft:red_sandstoneminecraft:smooth_red_sandstonec:sandstone/red_slabsminecraft:cut_red_sandstone_slabminecraft:red_sandstone_slabminecraft:smooth_red_sandstone_slabc:sandstone/red_stairsminecraft:red_sandstone_stairsminecraft:smooth_red_sandstone_stairsc:sandstone/slabsminecraft:cut_red_sandstone_slabminecraft:cut_sandstone_slabminecraft:red_sandstone_slabminecraft:sandstone_slabminecraft:smooth_red_sandstone_slabminecraft:smooth_sandstone_slabc:sandstone/stairsminecraft:red_sandstone_stairsminecraft:sandstone_stairsminecraft:smooth_red_sandstone_stairsminecraft:smooth_sandstone_stairsc:sandstone/uncolored_blocksminecraft:chiseled_sandstoneminecraft:cut_sandstoneminecraft:sandstoneminecraft:smooth_sandstonec:sandstone/uncolored_slabsminecraft:cut_sandstone_slabminecraft:sandstone_slabminecraft:smooth_sandstone_slabc:sandstone/uncolored_stairsminecraft:sandstone_stairsminecraft:smooth_sandstone_stairsc:skullsminecraft:creeper_headminecraft:creeper_wall_headminecraft:dragon_headminecraft:dragon_wall_headminecraft:piglin_headminecraft:piglin_wall_headminecraft:player_headminecraft:player_wall_headminecraft:skeleton_skullminecraft:skeleton_wall_skullminecraft:wither_skeleton_skullminecraft:wither_skeleton_wall_skullminecraft:zombie_headminecraft:zombie_wall_headc:stonesminecraft:andesiteminecraft:deepslateminecraft:dioriteminecraft:graniteminecraft:stoneminecraft:tuffc:storage_blocksminecraft:bone_blockminecraft:coal_blockminecraft:copper_blockminecraft:diamond_blockminecraft:dried_kelp_blockminecraft:emerald_blockminecraft:gold_blockminecraft:hay_blockminecraft:iron_blockminecraft:lapis_blockminecraft:netherite_blockminecraft:raw_copper_blockminecraft:raw_gold_blockminecraft:raw_iron_blockminecraft:redstone_blockminecraft:slime_blockc:storage_blocks/bone_mealminecraft:bone_blockc:storage_blocks/coalminecraft:coal_blockc:storage_blocks/copperminecraft:copper_blockc:storage_blocks/diamondminecraft:diamond_blockc:storage_blocks/dried_kelpminecraft:dried_kelp_blockc:storage_blocks/emeraldminecraft:emerald_blockc:storage_blocks/goldminecraft:gold_blockc:storage_blocks/ironminecraft:iron_blockc:storage_blocks/lapisminecraft:lapis_blockc:storage_blocks/netheriteminecraft:netherite_blockc:storage_blocks/raw_copperminecraft:raw_copper_blockc:storage_blocks/raw_goldminecraft:raw_gold_blockc:storage_blocks/raw_ironminecraft:raw_iron_blockc:storage_blocks/redstoneminecraft:redstone_blockc:storage_blocks/slimeminecraft:slime_blockc:storage_blocks/wheatminecraft:hay_blockc:villager_job_sitesminecraft:barrelminecraft:blast_furnaceminecraft:brewing_standminecraft:cartography_tableminecraft:cauldronminecraft:composterminecraft:fletching_tableminecraft:grindstoneminecraft:lava_cauldronminecraft:lecternminecraft:loomminecraft:powder_snow_cauldronminecraft:smithing_tableminecraft:smokerminecraft:stonecutterminecraft:water_cauldronforge:chests/enderminecraft:ender_chestforge:chests/trappedminecraft:trapped_chestforge:cobblestone/deepslateminecraft:cobbled_deepslateforge:cobblestone/infestedminecraft:infested_cobblestoneforge:cobblestone/mossyminecraft:mossy_cobblestoneforge:cobblestone/normalminecraft:cobblestoneforge:end_stonesminecraft:end_stoneforge:enderman_place_on_blacklistforge:fence_gatesminecraft:acacia_fence_gateminecraft:bamboo_fence_gateminecraft:birch_fence_gateminecraft:cherry_fence_gateminecraft:crimson_fence_gateminecraft:dark_oak_fence_gateminecraft:jungle_fence_gateminecraft:mangrove_fence_gateminecraft:oak_fence_gateminecraft:spruce_fence_gateminecraft:warped_fence_gateforge:fence_gates/woodenminecraft:acacia_fence_gateminecraft:bamboo_fence_gateminecraft:birch_fence_gateminecraft:cherry_fence_gateminecraft:crimson_fence_gateminecraft:dark_oak_fence_gateminecraft:jungle_fence_gateminecraft:mangrove_fence_gateminecraft:oak_fence_gateminecraft:spruce_fence_gateminecraft:warped_fence_gateforge:fencesminecraft:acacia_fenceminecraft:bamboo_fenceminecraft:birch_fenceminecraft:cherry_fenceminecraft:crimson_fenceminecraft:dark_oak_fenceminecraft:jungle_fenceminecraft:mangrove_fenceminecraft:nether_brick_fenceminecraft:oak_fenceminecraft:spruce_fenceminecraft:warped_fenceforge:fences/nether_brickminecraft:nether_brick_fenceforge:fences/woodenminecraft:acacia_fenceminecraft:bamboo_fenceminecraft:birch_fenceminecraft:cherry_fenceminecraft:crimson_fenceminecraft:dark_oak_fenceminecraft:jungle_fenceminecraft:mangrove_fenceminecraft:oak_fenceminecraft:spruce_fenceminecraft:warped_fenceforge:gravelminecraft:gravelforge:netherrackminecraft:netherrackforge:ore_bearing_ground/deepslateminecraft:deepslateforge:ore_bearing_ground/netherrackminecraft:netherrackforge:ore_bearing_ground/stoneminecraft:stoneforge:ore_rates/denseminecraft:copper_oreminecraft:deepslate_copper_oreminecraft:deepslate_lapis_oreminecraft:deepslate_redstone_oreminecraft:lapis_oreminecraft:redstone_oreforge:ore_rates/singularminecraft:ancient_debrisminecraft:coal_oreminecraft:deepslate_coal_oreminecraft:deepslate_diamond_oreminecraft:deepslate_emerald_oreminecraft:deepslate_gold_oreminecraft:deepslate_iron_oreminecraft:diamond_oreminecraft:emerald_oreminecraft:gold_oreminecraft:iron_oreminecraft:nether_quartz_oreforge:ore_rates/sparseminecraft:nether_gold_oreforge:ores/coalminecraft:coal_oreminecraft:deepslate_coal_oreforge:ores/copperminecraft:copper_oreminecraft:deepslate_copper_oreforge:ores/diamondminecraft:deepslate_diamond_oreminecraft:diamond_oreforge:ores/emeraldminecraft:deepslate_emerald_oreminecraft:emerald_oreforge:ores/goldminecraft:deepslate_gold_oreminecraft:gold_oreminecraft:nether_gold_oreforge:ores/ironminecraft:deepslate_iron_oreminecraft:iron_oreforge:ores/lapisminecraft:deepslate_lapis_oreminecraft:lapis_oreforge:ores/redstoneminecraft:deepslate_redstone_oreminecraft:redstone_oreforge:ores_in_ground/deepslateminecraft:deepslate_coal_oreminecraft:deepslate_copper_oreminecraft:deepslate_diamond_oreminecraft:deepslate_emerald_oreminecraft:deepslate_gold_oreminecraft:deepslate_iron_oreminecraft:deepslate_lapis_oreminecraft:deepslate_redstone_oreforge:ores_in_ground/netherrackminecraft:nether_gold_oreminecraft:nether_quartz_oreforge:ores_in_ground/stoneminecraft:coal_oreminecraft:copper_oreminecraft:diamond_oreminecraft:emerald_oreminecraft:gold_oreminecraft:iron_oreminecraft:lapis_oreminecraft:redstone_oreforge:sandminecraft:red_sandminecraft:sandforge:sand/colorlessminecraft:sandforge:sand/redminecraft:red_sand
enchantment
c:entity_auxiliary_movement_enhancementsminecraft:feather_fallingminecraft:frost_walkerc:entity_defense_enhancementsminecraft:blast_protectionminecraft:feather_fallingminecraft:fire_protectionminecraft:projectile_protectionminecraft:protectionminecraft:respirationc:entity_speed_enhancementsminecraft:depth_striderminecraft:soul_speedminecraft:swift_sneakc:increase_block_dropsminecraft:fortunec:increase_entity_dropsminecraft:lootingc:weapon_damage_enhancementsminecraft:bane_of_arthropodsminecraft:impalingminecraft:powerminecraft:sharpnessminecraft:smite
entitytype
c:boatsminecraft:boatminecraft:chest_boatc:bossesminecraft:ender_dragonminecraft:witherc:capturing_not_supportedc:minecartsminecraft:chest_minecartminecraft:command_block_minecartminecraft:furnace_minecartminecraft:hopper_minecartminecraft:minecartminecraft:spawner_minecartminecraft:tnt_minecartc:teleporting_not_supported
fluid
c:hidden_from_recipe_viewersc:honeyc:lavaminecraft:flowing_lavaminecraft:lavac:milkc:waterminecraft:flowing_waterminecraft:waterforge:beetroot_soupforge:gaseousforge:mushroom_stewforge:potionforge:rabbit_stewforge:suspicious_stew
item
c:animal_foodsminecraft:alliumminecraft:appleminecraft:azure_bluetminecraft:bamboominecraft:beefminecraft:beetrootminecraft:beetroot_seedsminecraft:blue_orchidminecraft:cactusminecraft:carrotminecraft:cherry_leavesminecraft:chickenminecraft:chorus_flowerminecraft:codminecraft:cooked_beefminecraft:cooked_chickenminecraft:cooked_muttonminecraft:cooked_porkchopminecraft:cooked_rabbitminecraft:cornflowerminecraft:crimson_fungusminecraft:dandelionminecraft:enchanted_golden_appleminecraft:flowering_azaleaminecraft:flowering_azalea_leavesminecraft:glow_berriesminecraft:golden_appleminecraft:golden_carrotminecraft:hay_blockminecraft:lilacminecraft:lily_of_the_valleyminecraft:mangrove_propaguleminecraft:melon_seedsminecraft:muttonminecraft:orange_tulipminecraft:oxeye_daisyminecraft:peonyminecraft:pink_petalsminecraft:pink_tulipminecraft:pitcher_plantminecraft:pitcher_podminecraft:poppyminecraft:porkchopminecraft:potatominecraft:pumpkin_seedsminecraft:rabbitminecraft:red_tulipminecraft:rose_bushminecraft:rotten_fleshminecraft:salmonminecraft:seagrassminecraft:slime_ballminecraft:spider_eyeminecraft:spore_blossomminecraft:sugarminecraft:sunflowerminecraft:sweet_berriesminecraft:torchflowerminecraft:torchflower_seedsminecraft:tropical_fish_bucketminecraft:warped_fungusminecraft:wheatminecraft:wheat_seedsminecraft:white_tulipminecraft:wither_rosec:armorsminecraft:chainmail_bootsminecraft:chainmail_chestplateminecraft:chainmail_helmetminecraft:chainmail_leggingsminecraft:diamond_bootsminecraft:diamond_chestplateminecraft:diamond_helmetminecraft:diamond_leggingsminecraft:golden_bootsminecraft:golden_chestplateminecraft:golden_helmetminecraft:golden_leggingsminecraft:iron_bootsminecraft:iron_chestplateminecraft:iron_helmetminecraft:iron_leggingsminecraft:leather_bootsminecraft:leather_chestplateminecraft:leather_helmetminecraft:leather_leggingsminecraft:netherite_bootsminecraft:netherite_chestplateminecraft:netherite_helmetminecraft:netherite_leggingsminecraft:turtle_helmetc:barrelsminecraft:barrelc:barrels/woodenminecraft:barrelc:bookshelvesminecraft:bookshelfc:bricksminecraft:brickminecraft:nether_brickc:bricks/netherminecraft:nether_brickc:bricks/normalminecraft:brickc:bucketsminecraft:axolotl_bucketminecraft:bucketminecraft:cod_bucketminecraft:lava_bucketminecraft:milk_bucketminecraft:powder_snow_bucketminecraft:pufferfish_bucketminecraft:salmon_bucketminecraft:tadpole_bucketminecraft:tropical_fish_bucketminecraft:water_bucketc:buckets/emptyminecraft:bucketc:buckets/entity_waterminecraft:axolotl_bucketminecraft:cod_bucketminecraft:pufferfish_bucketminecraft:salmon_bucketminecraft:tadpole_bucketminecraft:tropical_fish_bucketc:buckets/lavaminecraft:lava_bucketc:buckets/milkminecraft:milk_bucketc:buckets/powder_snowminecraft:powder_snow_bucketc:buckets/waterminecraft:water_bucketc:budding_blocksminecraft:budding_amethystc:budsminecraft:large_amethyst_budminecraft:medium_amethyst_budminecraft:small_amethyst_budc:chainsminecraft:chainc:chestsminecraft:chestminecraft:ender_chestminecraft:trapped_chestc:chests/woodenminecraft:chestminecraft:trapped_chestc:clustersminecraft:amethyst_clusterc:cobblestonesminecraft:cobbled_deepslateminecraft:cobblestoneminecraft:infested_cobblestoneminecraft:mossy_cobblestonec:concrete_powdersminecraft:black_concrete_powderminecraft:blue_concrete_powderminecraft:brown_concrete_powderminecraft:cyan_concrete_powderminecraft:gray_concrete_powderminecraft:green_concrete_powderminecraft:light_blue_concrete_powderminecraft:light_gray_concrete_powderminecraft:lime_concrete_powderminecraft:magenta_concrete_powderminecraft:orange_concrete_powderminecraft:pink_concrete_powderminecraft:purple_concrete_powderminecraft:red_concrete_powderminecraft:white_concrete_powderminecraft:yellow_concrete_powderc:concretesminecraft:black_concreteminecraft:blue_concreteminecraft:brown_concreteminecraft:cyan_concreteminecraft:gray_concreteminecraft:green_concreteminecraft:light_blue_concreteminecraft:light_gray_concreteminecraft:lime_concreteminecraft:magenta_concreteminecraft:orange_concreteminecraft:pink_concreteminecraft:purple_concreteminecraft:red_concreteminecraft:white_concreteminecraft:yellow_concretec:cropsminecraft:beetrootminecraft:cactusminecraft:carrotminecraft:cocoa_beansminecraft:melonminecraft:nether_wartminecraft:potatominecraft:pumpkinminecraft:sugar_caneminecraft:wheatc:crops/beetrootminecraft:beetrootc:crops/cactusminecraft:cactusc:crops/carrotminecraft:carrotc:crops/cocoa_beanminecraft:cocoa_beansc:crops/melonminecraft:melonc:crops/nether_wartminecraft:nether_wartc:crops/potatominecraft:potatoc:crops/pumpkinminecraft:pumpkinc:crops/sugar_caneminecraft:sugar_canec:crops/wheatminecraft:wheatc:dustsminecraft:glowstone_dustminecraft:redstonec:dusts/glowstoneminecraft:glowstone_dustc:dusts/redstoneminecraft:redstonec:dyedminecraft:black_bannerminecraft:black_bedminecraft:black_candleminecraft:black_carpetminecraft:black_concreteminecraft:black_concrete_powderminecraft:black_glazed_terracottaminecraft:black_shulker_boxminecraft:black_stained_glassminecraft:black_stained_glass_paneminecraft:black_terracottaminecraft:black_woolminecraft:blue_bannerminecraft:blue_bedminecraft:blue_candleminecraft:blue_carpetminecraft:blue_concreteminecraft:blue_concrete_powderminecraft:blue_glazed_terracottaminecraft:blue_shulker_boxminecraft:blue_stained_glassminecraft:blue_stained_glass_paneminecraft:blue_terracottaminecraft:blue_woolminecraft:brown_bannerminecraft:brown_bedminecraft:brown_candleminecraft:brown_carpetminecraft:brown_concreteminecraft:brown_concrete_powderminecraft:brown_glazed_terracottaminecraft:brown_shulker_boxminecraft:brown_stained_glassminecraft:brown_stained_glass_paneminecraft:brown_terracottaminecraft:brown_woolminecraft:cyan_bannerminecraft:cyan_bedminecraft:cyan_candleminecraft:cyan_carpetminecraft:cyan_concreteminecraft:cyan_concrete_powderminecraft:cyan_glazed_terracottaminecraft:cyan_shulker_boxminecraft:cyan_stained_glassminecraft:cyan_stained_glass_paneminecraft:cyan_terracottaminecraft:cyan_woolminecraft:gray_bannerminecraft:gray_bedminecraft:gray_candleminecraft:gray_carpetminecraft:gray_concreteminecraft:gray_concrete_powderminecraft:gray_glazed_terracottaminecraft:gray_shulker_boxminecraft:gray_stained_glassminecraft:gray_stained_glass_paneminecraft:gray_terracottaminecraft:gray_woolminecraft:green_bannerminecraft:green_bedminecraft:green_candleminecraft:green_carpetminecraft:green_concreteminecraft:green_concrete_powderminecraft:green_glazed_terracottaminecraft:green_shulker_boxminecraft:green_stained_glassminecraft:green_stained_glass_paneminecraft:green_terracottaminecraft:green_woolminecraft:light_blue_bannerminecraft:light_blue_bedminecraft:light_blue_candleminecraft:light_blue_carpetminecraft:light_blue_concreteminecraft:light_blue_concrete_powderminecraft:light_blue_glazed_terracottaminecraft:light_blue_shulker_boxminecraft:light_blue_stained_glassminecraft:light_blue_stained_glass_paneminecraft:light_blue_terracottaminecraft:light_blue_woolminecraft:light_gray_bannerminecraft:light_gray_bedminecraft:light_gray_candleminecraft:light_gray_carpetminecraft:light_gray_concreteminecraft:light_gray_concrete_powderminecraft:light_gray_glazed_terracottaminecraft:light_gray_shulker_boxminecraft:light_gray_stained_glassminecraft:light_gray_stained_glass_paneminecraft:light_gray_terracottaminecraft:light_gray_woolminecraft:lime_bannerminecraft:lime_bedminecraft:lime_candleminecraft:lime_carpetminecraft:lime_concreteminecraft:lime_concrete_powderminecraft:lime_glazed_terracottaminecraft:lime_shulker_boxminecraft:lime_stained_glassminecraft:lime_stained_glass_paneminecraft:lime_terracottaminecraft:lime_woolminecraft:magenta_bannerminecraft:magenta_bedminecraft:magenta_candleminecraft:magenta_carpetminecraft:magenta_concreteminecraft:magenta_concrete_powderminecraft:magenta_glazed_terracottaminecraft:magenta_shulker_boxminecraft:magenta_stained_glassminecraft:magenta_stained_glass_paneminecraft:magenta_terracottaminecraft:magenta_woolminecraft:orange_bannerminecraft:orange_bedminecraft:orange_candleminecraft:orange_carpetminecraft:orange_concreteminecraft:orange_concrete_powderminecraft:orange_glazed_terracottaminecraft:orange_shulker_boxminecraft:orange_stained_glassminecraft:orange_stained_glass_paneminecraft:orange_terracottaminecraft:orange_woolminecraft:pink_bannerminecraft:pink_bedminecraft:pink_candleminecraft:pink_carpetminecraft:pink_concreteminecraft:pink_concrete_powderminecraft:pink_glazed_terracottaminecraft:pink_shulker_boxminecraft:pink_stained_glassminecraft:pink_stained_glass_paneminecraft:pink_terracottaminecraft:pink_woolminecraft:purple_bannerminecraft:purple_bedminecraft:purple_candleminecraft:purple_carpetminecraft:purple_concreteminecraft:purple_concrete_powderminecraft:purple_glazed_terracottaminecraft:purple_shulker_boxminecraft:purple_stained_glassminecraft:purple_stained_glass_paneminecraft:purple_terracottaminecraft:purple_woolminecraft:red_bannerminecraft:red_bedminecraft:red_candleminecraft:red_carpetminecraft:red_concreteminecraft:red_concrete_powderminecraft:red_glazed_terracottaminecraft:red_shulker_boxminecraft:red_stained_glassminecraft:red_stained_glass_paneminecraft:red_terracottaminecraft:red_woolminecraft:white_bannerminecraft:white_bedminecraft:white_candleminecraft:white_carpetminecraft:white_concreteminecraft:white_concrete_powderminecraft:white_glazed_terracottaminecraft:white_shulker_boxminecraft:white_stained_glassminecraft:white_stained_glass_paneminecraft:white_terracottaminecraft:white_woolminecraft:yellow_bannerminecraft:yellow_bedminecraft:yellow_candleminecraft:yellow_carpetminecraft:yellow_concreteminecraft:yellow_concrete_powderminecraft:yellow_glazed_terracottaminecraft:yellow_shulker_boxminecraft:yellow_stained_glassminecraft:yellow_stained_glass_paneminecraft:yellow_terracottaminecraft:yellow_woolc:dyed/blackminecraft:black_bannerminecraft:black_bedminecraft:black_candleminecraft:black_carpetminecraft:black_concreteminecraft:black_concrete_powderminecraft:black_glazed_terracottaminecraft:black_shulker_boxminecraft:black_stained_glassminecraft:black_stained_glass_paneminecraft:black_terracottaminecraft:black_woolc:dyed/blueminecraft:blue_bannerminecraft:blue_bedminecraft:blue_candleminecraft:blue_carpetminecraft:blue_concreteminecraft:blue_concrete_powderminecraft:blue_glazed_terracottaminecraft:blue_shulker_boxminecraft:blue_stained_glassminecraft:blue_stained_glass_paneminecraft:blue_terracottaminecraft:blue_woolc:dyed/brownminecraft:brown_bannerminecraft:brown_bedminecraft:brown_candleminecraft:brown_carpetminecraft:brown_concreteminecraft:brown_concrete_powderminecraft:brown_glazed_terracottaminecraft:brown_shulker_boxminecraft:brown_stained_glassminecraft:brown_stained_glass_paneminecraft:brown_terracottaminecraft:brown_woolc:dyed/cyanminecraft:cyan_bannerminecraft:cyan_bedminecraft:cyan_candleminecraft:cyan_carpetminecraft:cyan_concreteminecraft:cyan_concrete_powderminecraft:cyan_glazed_terracottaminecraft:cyan_shulker_boxminecraft:cyan_stained_glassminecraft:cyan_stained_glass_paneminecraft:cyan_terracottaminecraft:cyan_woolc:dyed/grayminecraft:gray_bannerminecraft:gray_bedminecraft:gray_candleminecraft:gray_carpetminecraft:gray_concreteminecraft:gray_concrete_powderminecraft:gray_glazed_terracottaminecraft:gray_shulker_boxminecraft:gray_stained_glassminecraft:gray_stained_glass_paneminecraft:gray_terracottaminecraft:gray_woolc:dyed/greenminecraft:green_bannerminecraft:green_bedminecraft:green_candleminecraft:green_carpetminecraft:green_concreteminecraft:green_concrete_powderminecraft:green_glazed_terracottaminecraft:green_shulker_boxminecraft:green_stained_glassminecraft:green_stained_glass_paneminecraft:green_terracottaminecraft:green_woolc:dyed/light_blueminecraft:light_blue_bannerminecraft:light_blue_bedminecraft:light_blue_candleminecraft:light_blue_carpetminecraft:light_blue_concreteminecraft:light_blue_concrete_powderminecraft:light_blue_glazed_terracottaminecraft:light_blue_shulker_boxminecraft:light_blue_stained_glassminecraft:light_blue_stained_glass_paneminecraft:light_blue_terracottaminecraft:light_blue_woolc:dyed/light_grayminecraft:light_gray_bannerminecraft:light_gray_bedminecraft:light_gray_candleminecraft:light_gray_carpetminecraft:light_gray_concreteminecraft:light_gray_concrete_powderminecraft:light_gray_glazed_terracottaminecraft:light_gray_shulker_boxminecraft:light_gray_stained_glassminecraft:light_gray_stained_glass_paneminecraft:light_gray_terracottaminecraft:light_gray_woolc:dyed/limeminecraft:lime_bannerminecraft:lime_bedminecraft:lime_candleminecraft:lime_carpetminecraft:lime_concreteminecraft:lime_concrete_powderminecraft:lime_glazed_terracottaminecraft:lime_shulker_boxminecraft:lime_stained_glassminecraft:lime_stained_glass_paneminecraft:lime_terracottaminecraft:lime_woolc:dyed/magentaminecraft:magenta_bannerminecraft:magenta_bedminecraft:magenta_candleminecraft:magenta_carpetminecraft:magenta_concreteminecraft:magenta_concrete_powderminecraft:magenta_glazed_terracottaminecraft:magenta_shulker_boxminecraft:magenta_stained_glassminecraft:magenta_stained_glass_paneminecraft:magenta_terracottaminecraft:magenta_woolc:dyed/orangeminecraft:orange_bannerminecraft:orange_bedminecraft:orange_candleminecraft:orange_carpetminecraft:orange_concreteminecraft:orange_concrete_powderminecraft:orange_glazed_terracottaminecraft:orange_shulker_boxminecraft:orange_stained_glassminecraft:orange_stained_glass_paneminecraft:orange_terracottaminecraft:orange_woolc:dyed/pinkminecraft:pink_bannerminecraft:pink_bedminecraft:pink_candleminecraft:pink_carpetminecraft:pink_concreteminecraft:pink_concrete_powderminecraft:pink_glazed_terracottaminecraft:pink_shulker_boxminecraft:pink_stained_glassminecraft:pink_stained_glass_paneminecraft:pink_terracottaminecraft:pink_woolc:dyed/purpleminecraft:purple_bannerminecraft:purple_bedminecraft:purple_candleminecraft:purple_carpetminecraft:purple_concreteminecraft:purple_concrete_powderminecraft:purple_glazed_terracottaminecraft:purple_shulker_boxminecraft:purple_stained_glassminecraft:purple_stained_glass_paneminecraft:purple_terracottaminecraft:purple_woolc:dyed/redminecraft:red_bannerminecraft:red_bedminecraft:red_candleminecraft:red_carpetminecraft:red_concreteminecraft:red_concrete_powderminecraft:red_glazed_terracottaminecraft:red_shulker_boxminecraft:red_stained_glassminecraft:red_stained_glass_paneminecraft:red_terracottaminecraft:red_woolc:dyed/whiteminecraft:white_bannerminecraft:white_bedminecraft:white_candleminecraft:white_carpetminecraft:white_concreteminecraft:white_concrete_powderminecraft:white_glazed_terracottaminecraft:white_shulker_boxminecraft:white_stained_glassminecraft:white_stained_glass_paneminecraft:white_terracottaminecraft:white_woolc:dyed/yellowminecraft:yellow_bannerminecraft:yellow_bedminecraft:yellow_candleminecraft:yellow_carpetminecraft:yellow_concreteminecraft:yellow_concrete_powderminecraft:yellow_glazed_terracottaminecraft:yellow_shulker_boxminecraft:yellow_stained_glassminecraft:yellow_stained_glass_paneminecraft:yellow_terracottaminecraft:yellow_woolc:dyesminecraft:black_dyeminecraft:blue_dyeminecraft:brown_dyeminecraft:cyan_dyeminecraft:gray_dyeminecraft:green_dyeminecraft:light_blue_dyeminecraft:light_gray_dyeminecraft:lime_dyeminecraft:magenta_dyeminecraft:orange_dyeminecraft:pink_dyeminecraft:purple_dyeminecraft:red_dyeminecraft:white_dyeminecraft:yellow_dyec:dyes/blackminecraft:black_dyec:dyes/blueminecraft:blue_dyec:dyes/brownminecraft:brown_dyec:dyes/cyanminecraft:cyan_dyec:dyes/grayminecraft:gray_dyec:dyes/greenminecraft:green_dyec:dyes/light_blueminecraft:light_blue_dyec:dyes/light_grayminecraft:light_gray_dyec:dyes/limeminecraft:lime_dyec:dyes/magentaminecraft:magenta_dyec:dyes/orangeminecraft:orange_dyec:dyes/pinkminecraft:pink_dyec:dyes/purpleminecraft:purple_dyec:dyes/redminecraft:red_dyec:dyes/whiteminecraft:white_dyec:dyes/yellowminecraft:yellow_dyec:enchantablesminecraft:bowminecraft:brushminecraft:carrot_on_a_stickminecraft:carved_pumpkinminecraft:chainmail_bootsminecraft:chainmail_chestplateminecraft:chainmail_helmetminecraft:chainmail_leggingsminecraft:compassminecraft:creeper_headminecraft:crossbowminecraft:diamond_axeminecraft:diamond_bootsminecraft:diamond_chestplateminecraft:diamond_helmetminecraft:diamond_hoeminecraft:diamond_leggingsminecraft:diamond_pickaxeminecraft:diamond_shovelminecraft:diamond_swordminecraft:dragon_headminecraft:elytraminecraft:fishing_rodminecraft:flint_and_steelminecraft:golden_axeminecraft:golden_bootsminecraft:golden_chestplateminecraft:golden_helmetminecraft:golden_hoeminecraft:golden_leggingsminecraft:golden_pickaxeminecraft:golden_shovelminecraft:golden_swordminecraft:iron_axeminecraft:iron_bootsminecraft:iron_chestplateminecraft:iron_helmetminecraft:iron_hoeminecraft:iron_leggingsminecraft:iron_pickaxeminecraft:iron_shovelminecraft:iron_swordminecraft:leather_bootsminecraft:leather_chestplateminecraft:leather_helmetminecraft:leather_leggingsminecraft:maceminecraft:netherite_axeminecraft:netherite_bootsminecraft:netherite_chestplateminecraft:netherite_helmetminecraft:netherite_hoeminecraft:netherite_leggingsminecraft:netherite_pickaxeminecraft:netherite_shovelminecraft:netherite_swordminecraft:piglin_headminecraft:player_headminecraft:shearsminecraft:shieldminecraft:skeleton_skullminecraft:stone_axeminecraft:stone_hoeminecraft:stone_pickaxeminecraft:stone_shovelminecraft:stone_swordminecraft:tridentminecraft:turtle_helmetminecraft:warped_fungus_on_a_stickminecraft:wither_skeleton_skullminecraft:wooden_axeminecraft:wooden_hoeminecraft:wooden_pickaxeminecraft:wooden_shovelminecraft:wooden_swordminecraft:zombie_headc:ender_pearlsminecraft:ender_pearlc:fertilizersminecraft:bone_mealc:foodsminecraft:appleminecraft:baked_potatominecraft:beefminecraft:beetrootminecraft:beetroot_soupminecraft:breadminecraft:cakeminecraft:carrotminecraft:chickenminecraft:chorus_fruitminecraft:codminecraft:cooked_beefminecraft:cooked_chickenminecraft:cooked_codminecraft:cooked_muttonminecraft:cooked_porkchopminecraft:cooked_rabbitminecraft:cooked_salmonminecraft:cookieminecraft:dried_kelpminecraft:enchanted_golden_appleminecraft:glow_berriesminecraft:golden_appleminecraft:golden_carrotminecraft:honey_bottleminecraft:melon_sliceminecraft:mushroom_stewminecraft:muttonminecraft:ominous_bottleminecraft:poisonous_potatominecraft:porkchopminecraft:potatominecraft:pufferfishminecraft:pumpkin_pieminecraft:rabbitminecraft:rabbit_stewminecraft:rotten_fleshminecraft:salmonminecraft:spider_eyeminecraft:suspicious_stewminecraft:sweet_berriesminecraft:tropical_fishc:foods/berryminecraft:glow_berriesminecraft:sweet_berriesc:foods/breadminecraft:breadc:foods/candyc:foods/cooked_fishminecraft:cooked_codminecraft:cooked_salmonc:foods/cooked_meatminecraft:cooked_beefminecraft:cooked_chickenminecraft:cooked_muttonminecraft:cooked_porkchopminecraft:cooked_rabbitc:foods/cookieminecraft:cookiec:foods/edible_when_placedminecraft:cakec:foods/food_poisoningminecraft:chickenminecraft:poisonous_potatominecraft:pufferfishminecraft:rotten_fleshminecraft:spider_eyec:foods/fruitminecraft:appleminecraft:chorus_fruitminecraft:enchanted_golden_appleminecraft:golden_appleminecraft:melon_slicec:foods/goldenminecraft:enchanted_golden_appleminecraft:golden_appleminecraft:golden_carrotc:foods/raw_fishminecraft:codminecraft:pufferfishminecraft:salmonminecraft:tropical_fishc:foods/raw_meatminecraft:beefminecraft:chickenminecraft:muttonminecraft:porkchopminecraft:rabbitc:foods/soupminecraft:beetroot_soupminecraft:mushroom_stewminecraft:rabbit_stewminecraft:suspicious_stewc:foods/vegetableminecraft:beetrootminecraft:carrotminecraft:golden_carrotminecraft:potatoc:gemsminecraft:amethyst_shardminecraft:diamondminecraft:emeraldminecraft:lapis_lazuliminecraft:prismarine_crystalsminecraft:quartzc:gems/amethystminecraft:amethyst_shardc:gems/diamondminecraft:diamondc:gems/emeraldminecraft:emeraldc:gems/lapisminecraft:lapis_lazulic:gems/prismarineminecraft:prismarine_crystalsc:gems/quartzminecraft:quartzc:glass_blocksminecraft:black_stained_glassminecraft:blue_stained_glassminecraft:brown_stained_glassminecraft:cyan_stained_glassminecraft:glassminecraft:gray_stained_glassminecraft:green_stained_glassminecraft:light_blue_stained_glassminecraft:light_gray_stained_glassminecraft:lime_stained_glassminecraft:magenta_stained_glassminecraft:orange_stained_glassminecraft:pink_stained_glassminecraft:purple_stained_glassminecraft:red_stained_glassminecraft:tinted_glassminecraft:white_stained_glassminecraft:yellow_stained_glassc:glass_blocks/cheapminecraft:black_stained_glassminecraft:blue_stained_glassminecraft:brown_stained_glassminecraft:cyan_stained_glassminecraft:glassminecraft:gray_stained_glassminecraft:green_stained_glassminecraft:light_blue_stained_glassminecraft:light_gray_stained_glassminecraft:lime_stained_glassminecraft:magenta_stained_glassminecraft:orange_stained_glassminecraft:pink_stained_glassminecraft:purple_stained_glassminecraft:red_stained_glassminecraft:white_stained_glassminecraft:yellow_stained_glassc:glass_blocks/colorlessminecraft:glassc:glass_blocks/tintedminecraft:tinted_glassc:glass_panesminecraft:black_stained_glass_paneminecraft:blue_stained_glass_paneminecraft:brown_stained_glass_paneminecraft:cyan_stained_glass_paneminecraft:glass_paneminecraft:gray_stained_glass_paneminecraft:green_stained_glass_paneminecraft:light_blue_stained_glass_paneminecraft:light_gray_stained_glass_paneminecraft:lime_stained_glass_paneminecraft:magenta_stained_glass_paneminecraft:orange_stained_glass_paneminecraft:pink_stained_glass_paneminecraft:purple_stained_glass_paneminecraft:red_stained_glass_paneminecraft:white_stained_glass_paneminecraft:yellow_stained_glass_panec:glass_panes/colorlessminecraft:glass_panec:glazed_terracottasminecraft:black_glazed_terracottaminecraft:blue_glazed_terracottaminecraft:brown_glazed_terracottaminecraft:cyan_glazed_terracottaminecraft:gray_glazed_terracottaminecraft:green_glazed_terracottaminecraft:light_blue_glazed_terracottaminecraft:light_gray_glazed_terracottaminecraft:lime_glazed_terracottaminecraft:magenta_glazed_terracottaminecraft:orange_glazed_terracottaminecraft:pink_glazed_terracottaminecraft:purple_glazed_terracottaminecraft:red_glazed_terracottaminecraft:white_glazed_terracottaminecraft:yellow_glazed_terracottac:hidden_from_recipe_viewersc:ingotsminecraft:copper_ingotminecraft:gold_ingotminecraft:iron_ingotminecraft:netherite_ingotc:ingots/copperminecraft:copper_ingotc:ingots/goldminecraft:gold_ingotc:ingots/ironminecraft:iron_ingotc:ingots/netheriteminecraft:netherite_ingotc:leathersminecraft:leatherc:music_discsminecraft:music_disc_11minecraft:music_disc_13minecraft:music_disc_5minecraft:music_disc_blocksminecraft:music_disc_catminecraft:music_disc_chirpminecraft:music_disc_creatorminecraft:music_disc_creator_music_boxminecraft:music_disc_farminecraft:music_disc_mallminecraft:music_disc_mellohiminecraft:music_disc_othersideminecraft:music_disc_pigstepminecraft:music_disc_precipiceminecraft:music_disc_relicminecraft:music_disc_stalminecraft:music_disc_stradminecraft:music_disc_waitminecraft:music_disc_wardc:nuggetsminecraft:gold_nuggetminecraft:iron_nuggetc:nuggets/goldminecraft:gold_nuggetc:nuggets/ironminecraft:iron_nuggetc:obsidiansminecraft:crying_obsidianminecraft:obsidianc:obsidians/cryingminecraft:crying_obsidianc:obsidians/normalminecraft:obsidianc:oresminecraft:ancient_debrisminecraft:coal_oreminecraft:copper_oreminecraft:deepslate_coal_oreminecraft:deepslate_copper_oreminecraft:deepslate_diamond_oreminecraft:deepslate_emerald_oreminecraft:deepslate_gold_oreminecraft:deepslate_iron_oreminecraft:deepslate_lapis_oreminecraft:deepslate_redstone_oreminecraft:diamond_oreminecraft:emerald_oreminecraft:gold_oreminecraft:iron_oreminecraft:lapis_oreminecraft:nether_gold_oreminecraft:nether_quartz_oreminecraft:redstone_orec:ores/netherite_scrapminecraft:ancient_debrisc:ores/quartzminecraft:nether_quartz_orec:player_workstations/crafting_tablesminecraft:crafting_tablec:player_workstations/furnacesminecraft:furnacec:raw_materialsminecraft:raw_copperminecraft:raw_goldminecraft:raw_ironc:raw_materials/copperminecraft:raw_copperc:raw_materials/goldminecraft:raw_goldc:raw_materials/ironminecraft:raw_ironc:rodsminecraft:blaze_rodminecraft:breeze_rodminecraft:stickc:rods/blazeminecraft:blaze_rodc:rods/breezeminecraft:breeze_rodc:rods/woodenminecraft:stickc:ropesc:sandstone/blocksminecraft:chiseled_red_sandstoneminecraft:chiseled_sandstoneminecraft:cut_red_sandstoneminecraft:cut_sandstoneminecraft:red_sandstoneminecraft:sandstoneminecraft:smooth_red_sandstoneminecraft:smooth_sandstonec:sandstone/red_blocksminecraft:chiseled_red_sandstoneminecraft:cut_red_sandstoneminecraft:red_sandstoneminecraft:smooth_red_sandstonec:sandstone/red_slabsminecraft:cut_red_sandstone_slabminecraft:red_sandstone_slabminecraft:smooth_red_sandstone_slabc:sandstone/red_stairsminecraft:red_sandstone_stairsminecraft:smooth_red_sandstone_stairsc:sandstone/slabsminecraft:cut_red_sandstone_slabminecraft:cut_sandstone_slabminecraft:red_sandstone_slabminecraft:sandstone_slabminecraft:smooth_red_sandstone_slabminecraft:smooth_sandstone_slabc:sandstone/stairsminecraft:red_sandstone_stairsminecraft:sandstone_stairsminecraft:smooth_red_sandstone_stairsminecraft:smooth_sandstone_stairsc:sandstone/uncolored_blocksminecraft:chiseled_sandstoneminecraft:cut_sandstoneminecraft:sandstoneminecraft:smooth_sandstonec:sandstone/uncolored_slabsminecraft:cut_sandstone_slabminecraft:sandstone_slabminecraft:smooth_sandstone_slabc:sandstone/uncolored_stairsminecraft:sandstone_stairsminecraft:smooth_sandstone_stairsc:shulker_boxesminecraft:black_shulker_boxminecraft:blue_shulker_boxminecraft:brown_shulker_boxminecraft:cyan_shulker_boxminecraft:gray_shulker_boxminecraft:green_shulker_boxminecraft:light_blue_shulker_boxminecraft:light_gray_shulker_boxminecraft:lime_shulker_boxminecraft:magenta_shulker_boxminecraft:orange_shulker_boxminecraft:pink_shulker_boxminecraft:purple_shulker_boxminecraft:red_shulker_boxminecraft:shulker_boxminecraft:white_shulker_boxminecraft:yellow_shulker_boxc:slime_ballsminecraft:slime_ballc:stonesminecraft:andesiteminecraft:deepslateminecraft:dioriteminecraft:graniteminecraft:stoneminecraft:tuffc:storage_blocksminecraft:bone_blockminecraft:coal_blockminecraft:copper_blockminecraft:diamond_blockminecraft:dried_kelp_blockminecraft:emerald_blockminecraft:gold_blockminecraft:hay_blockminecraft:iron_blockminecraft:lapis_blockminecraft:netherite_blockminecraft:raw_copper_blockminecraft:raw_gold_blockminecraft:raw_iron_blockminecraft:redstone_blockminecraft:slime_blockc:storage_blocks/bone_mealminecraft:bone_blockc:storage_blocks/coalminecraft:coal_blockc:storage_blocks/copperminecraft:copper_blockc:storage_blocks/diamondminecraft:diamond_blockc:storage_blocks/dried_kelpminecraft:dried_kelp_blockc:storage_blocks/emeraldminecraft:emerald_blockc:storage_blocks/goldminecraft:gold_blockc:storage_blocks/ironminecraft:iron_blockc:storage_blocks/lapisminecraft:lapis_blockc:storage_blocks/netheriteminecraft:netherite_blockc:storage_blocks/raw_copperminecraft:raw_copper_blockc:storage_blocks/raw_goldminecraft:raw_gold_blockc:storage_blocks/raw_ironminecraft:raw_iron_blockc:storage_blocks/redstoneminecraft:redstone_blockc:storage_blocks/slimeminecraft:slime_blockc:storage_blocks/wheatminecraft:hay_blockc:stringsminecraft:stringc:toolsminecraft:bowminecraft:brushminecraft:crossbowminecraft:diamond_axeminecraft:diamond_hoeminecraft:diamond_pickaxeminecraft:diamond_shovelminecraft:diamond_swordminecraft:fishing_rodminecraft:flint_and_steelminecraft:golden_axeminecraft:golden_hoeminecraft:golden_pickaxeminecraft:golden_shovelminecraft:golden_swordminecraft:iron_axeminecraft:iron_hoeminecraft:iron_pickaxeminecraft:iron_shovelminecraft:iron_swordminecraft:maceminecraft:netherite_axeminecraft:netherite_hoeminecraft:netherite_pickaxeminecraft:netherite_shovelminecraft:netherite_swordminecraft:shearsminecraft:shieldminecraft:stone_axeminecraft:stone_hoeminecraft:stone_pickaxeminecraft:stone_shovelminecraft:stone_swordminecraft:tridentminecraft:wooden_axeminecraft:wooden_hoeminecraft:wooden_pickaxeminecraft:wooden_shovelminecraft:wooden_swordc:tools/bowminecraft:bowc:tools/brushminecraft:brushc:tools/crossbowminecraft:crossbowc:tools/fishing_rodminecraft:fishing_rodc:tools/igniterminecraft:flint_and_steelc:tools/maceminecraft:macec:tools/melee_weaponminecraft:diamond_axeminecraft:diamond_swordminecraft:golden_axeminecraft:golden_swordminecraft:iron_axeminecraft:iron_swordminecraft:maceminecraft:netherite_axeminecraft:netherite_swordminecraft:stone_axeminecraft:stone_swordminecraft:tridentminecraft:wooden_axeminecraft:wooden_swordc:tools/mining_toolminecraft:diamond_pickaxeminecraft:golden_pickaxeminecraft:iron_pickaxeminecraft:netherite_pickaxeminecraft:stone_pickaxeminecraft:wooden_pickaxec:tools/ranged_weaponminecraft:bowminecraft:crossbowminecraft:tridentc:tools/shearminecraft:shearsc:tools/shieldminecraft:shieldc:tools/spearminecraft:tridentc:villager_job_sitesminecraft:barrelminecraft:blast_furnaceminecraft:brewing_standminecraft:cartography_tableminecraft:cauldronminecraft:composterminecraft:fletching_tableminecraft:grindstoneminecraft:lecternminecraft:loomminecraft:smithing_tableminecraft:smokerminecraft:stonecutterforge:bonesminecraft:boneforge:chests/enderminecraft:ender_chestforge:chests/trappedminecraft:trapped_chestforge:cobblestone/deepslateminecraft:cobbled_deepslateforge:cobblestone/infestedminecraft:infested_cobblestoneforge:cobblestone/mossyminecraft:mossy_cobblestoneforge:cobblestone/normalminecraft:cobblestoneforge:eggsminecraft:eggforge:enchanting_fuelsminecraft:lapis_lazuliforge:end_stonesminecraft:end_stoneforge:feathersminecraft:featherforge:fence_gatesminecraft:acacia_fence_gateminecraft:bamboo_fence_gateminecraft:birch_fence_gateminecraft:cherry_fence_gateminecraft:crimson_fence_gateminecraft:dark_oak_fence_gateminecraft:jungle_fence_gateminecraft:mangrove_fence_gateminecraft:oak_fence_gateminecraft:spruce_fence_gateminecraft:warped_fence_gateforge:fence_gates/woodenminecraft:acacia_fence_gateminecraft:bamboo_fence_gateminecraft:birch_fence_gateminecraft:cherry_fence_gateminecraft:crimson_fence_gateminecraft:dark_oak_fence_gateminecraft:jungle_fence_gateminecraft:mangrove_fence_gateminecraft:oak_fence_gateminecraft:spruce_fence_gateminecraft:warped_fence_gateforge:fencesminecraft:acacia_fenceminecraft:bamboo_fenceminecraft:birch_fenceminecraft:cherry_fenceminecraft:crimson_fenceminecraft:dark_oak_fenceminecraft:jungle_fenceminecraft:mangrove_fenceminecraft:nether_brick_fenceminecraft:oak_fenceminecraft:spruce_fenceminecraft:warped_fenceforge:fences/nether_brickminecraft:nether_brick_fenceforge:fences/woodenminecraft:acacia_fenceminecraft:bamboo_fenceminecraft:birch_fenceminecraft:cherry_fenceminecraft:crimson_fenceminecraft:dark_oak_fenceminecraft:jungle_fenceminecraft:mangrove_fenceminecraft:oak_fenceminecraft:spruce_fenceminecraft:warped_fenceforge:foods/pieminecraft:pumpkin_pieforge:gravelminecraft:gravelforge:gunpowderminecraft:gunpowderforge:mushroomsminecraft:brown_mushroomminecraft:red_mushroomforge:nether_starsminecraft:nether_starforge:netherrackminecraft:netherrackforge:ore_bearing_ground/deepslateminecraft:deepslateforge:ore_bearing_ground/netherrackminecraft:netherrackforge:ore_bearing_ground/stoneminecraft:stoneforge:ore_rates/denseminecraft:copper_oreminecraft:deepslate_copper_oreminecraft:deepslate_lapis_oreminecraft:deepslate_redstone_oreminecraft:lapis_oreminecraft:redstone_oreforge:ore_rates/singularminecraft:ancient_debrisminecraft:coal_oreminecraft:deepslate_coal_oreminecraft:deepslate_diamond_oreminecraft:deepslate_emerald_oreminecraft:deepslate_gold_oreminecraft:deepslate_iron_oreminecraft:diamond_oreminecraft:emerald_oreminecraft:gold_oreminecraft:iron_oreminecraft:nether_quartz_oreforge:ore_rates/sparseminecraft:nether_gold_oreforge:ores/coalminecraft:coal_oreminecraft:deepslate_coal_oreforge:ores/copperminecraft:copper_oreminecraft:deepslate_copper_oreforge:ores/diamondminecraft:deepslate_diamond_oreminecraft:diamond_oreforge:ores/emeraldminecraft:deepslate_emerald_oreminecraft:emerald_oreforge:ores/goldminecraft:deepslate_gold_oreminecraft:gold_oreminecraft:nether_gold_oreforge:ores/ironminecraft:deepslate_iron_oreminecraft:iron_oreforge:ores/lapisminecraft:deepslate_lapis_oreminecraft:lapis_oreforge:ores/redstoneminecraft:deepslate_redstone_oreminecraft:redstone_oreforge:ores_in_ground/deepslateminecraft:deepslate_coal_oreminecraft:deepslate_copper_oreminecraft:deepslate_diamond_oreminecraft:deepslate_emerald_oreminecraft:deepslate_gold_oreminecraft:deepslate_iron_oreminecraft:deepslate_lapis_oreminecraft:deepslate_redstone_oreforge:ores_in_ground/netherrackminecraft:nether_gold_oreminecraft:nether_quartz_oreforge:ores_in_ground/stoneminecraft:coal_oreminecraft:copper_oreminecraft:diamond_oreminecraft:emerald_oreminecraft:gold_oreminecraft:iron_oreminecraft:lapis_oreminecraft:redstone_oreforge:sandminecraft:red_sandminecraft:sandforge:sand/colorlessminecraft:sandforge:sand/redminecraft:red_sandforge:seedsminecraft:beetroot_seedsminecraft:melon_seedsminecraft:pumpkin_seedsminecraft:wheat_seedsforge:seeds/beetrootminecraft:beetroot_seedsforge:seeds/melonminecraft:melon_seedsforge:seeds/pumpkinminecraft:pumpkin_seedsforge:seeds/wheatminecraft:wheat_seeds
worldgen/biome
c:hidden_from_locator_selectionc:is_aquaticminecraft:cold_oceanminecraft:deep_cold_oceanminecraft:deep_frozen_oceanminecraft:deep_lukewarm_oceanminecraft:deep_oceanminecraft:frozen_oceanminecraft:frozen_riverminecraft:lukewarm_oceanminecraft:oceanminecraft:riverminecraft:warm_oceanc:is_aquatic_icyminecraft:deep_frozen_oceanminecraft:frozen_oceanminecraft:frozen_riverc:is_badlandsminecraft:badlandsminecraft:eroded_badlandsminecraft:wooded_badlandsc:is_beachminecraft:beachminecraft:snowy_beachc:is_birch_forestminecraft:birch_forestminecraft:old_growth_birch_forestc:is_caveminecraft:deep_darkminecraft:dripstone_cavesminecraft:lush_cavesc:is_coldminecraft:cold_oceanminecraft:deep_cold_oceanminecraft:deep_frozen_oceanminecraft:end_barrensminecraft:end_highlandsminecraft:end_midlandsminecraft:frozen_oceanminecraft:frozen_peaksminecraft:frozen_riverminecraft:groveminecraft:ice_spikesminecraft:jagged_peaksminecraft:old_growth_pine_taigaminecraft:old_growth_spruce_taigaminecraft:small_end_islandsminecraft:snowy_beachminecraft:snowy_plainsminecraft:snowy_slopesminecraft:snowy_taigaminecraft:stony_shoreminecraft:taigaminecraft:the_endminecraft:windswept_forestminecraft:windswept_gravelly_hillsminecraft:windswept_hillsc:is_cold/endminecraft:end_barrensminecraft:end_highlandsminecraft:end_midlandsminecraft:small_end_islandsminecraft:the_endc:is_cold/overworldminecraft:cold_oceanminecraft:deep_cold_oceanminecraft:deep_frozen_oceanminecraft:frozen_oceanminecraft:frozen_peaksminecraft:frozen_riverminecraft:groveminecraft:ice_spikesminecraft:jagged_peaksminecraft:old_growth_pine_taigaminecraft:old_growth_spruce_taigaminecraft:snowy_beachminecraft:snowy_plainsminecraft:snowy_slopesminecraft:snowy_taigaminecraft:stony_shoreminecraft:taigaminecraft:windswept_forestminecraft:windswept_gravelly_hillsminecraft:windswept_hillsc:is_deadc:is_deep_oceanminecraft:deep_cold_oceanminecraft:deep_frozen_oceanminecraft:deep_lukewarm_oceanminecraft:deep_oceanc:is_dense_vegetationminecraft:bamboo_jungleminecraft:dark_forestminecraft:jungleminecraft:mangrove_swampminecraft:old_growth_birch_forestminecraft:old_growth_spruce_taigac:is_dense_vegetation/overworldminecraft:bamboo_jungleminecraft:dark_forestminecraft:jungleminecraft:mangrove_swampminecraft:old_growth_birch_forestminecraft:old_growth_spruce_taigac:is_desertminecraft:desertc:is_dryminecraft:badlandsminecraft:basalt_deltasminecraft:crimson_forestminecraft:desertminecraft:end_barrensminecraft:end_highlandsminecraft:end_midlandsminecraft:eroded_badlandsminecraft:nether_wastesminecraft:savannaminecraft:savanna_plateauminecraft:small_end_islandsminecraft:soul_sand_valleyminecraft:the_endminecraft:warped_forestminecraft:windswept_savannaminecraft:wooded_badlandsc:is_dry/endminecraft:end_barrensminecraft:end_highlandsminecraft:end_midlandsminecraft:small_end_islandsminecraft:the_endc:is_dry/netherminecraft:basalt_deltasminecraft:crimson_forestminecraft:nether_wastesminecraft:soul_sand_valleyminecraft:warped_forestc:is_dry/overworldminecraft:badlandsminecraft:desertminecraft:eroded_badlandsminecraft:savannaminecraft:savanna_plateauminecraft:windswept_savannaminecraft:wooded_badlandsc:is_endminecraft:end_barrensminecraft:end_highlandsminecraft:end_midlandsminecraft:small_end_islandsminecraft:the_endc:is_floralminecraft:cherry_groveminecraft:flower_forestminecraft:meadowminecraft:sunflower_plainsc:is_flower_forestminecraft:flower_forestc:is_forestminecraft:birch_forestminecraft:dark_forestminecraft:flower_forestminecraft:forestminecraft:groveminecraft:old_growth_birch_forestc:is_hillminecraft:windswept_forestminecraft:windswept_gravelly_hillsminecraft:windswept_hillsc:is_hotminecraft:badlandsminecraft:bamboo_jungleminecraft:basalt_deltasminecraft:crimson_forestminecraft:desertminecraft:eroded_badlandsminecraft:jungleminecraft:mangrove_swampminecraft:nether_wastesminecraft:savannaminecraft:savanna_plateauminecraft:soul_sand_valleyminecraft:sparse_jungleminecraft:stony_peaksminecraft:swampminecraft:warm_oceanminecraft:warped_forestminecraft:windswept_savannaminecraft:wooded_badlandsc:is_hot/netherminecraft:basalt_deltasminecraft:crimson_forestminecraft:nether_wastesminecraft:soul_sand_valleyminecraft:warped_forestc:is_hot/overworldminecraft:badlandsminecraft:bamboo_jungleminecraft:desertminecraft:eroded_badlandsminecraft:jungleminecraft:mangrove_swampminecraft:savannaminecraft:savanna_plateauminecraft:sparse_jungleminecraft:stony_peaksminecraft:swampminecraft:warm_oceanminecraft:windswept_savannaminecraft:wooded_badlandsc:is_icyminecraft:frozen_peaksminecraft:ice_spikesc:is_jungleminecraft:bamboo_jungleminecraft:jungleminecraft:sparse_junglec:is_mountainminecraft:cherry_groveminecraft:frozen_peaksminecraft:groveminecraft:jagged_peaksminecraft:meadowminecraft:snowy_slopesminecraft:stony_peaksc:is_mountain/peakminecraft:frozen_peaksminecraft:jagged_peaksminecraft:stony_peaksc:is_mountain/slopeminecraft:cherry_groveminecraft:groveminecraft:meadowminecraft:snowy_slopesc:is_mushroomminecraft:mushroom_fieldsc:is_netherminecraft:basalt_deltasminecraft:crimson_forestminecraft:nether_wastesminecraft:soul_sand_valleyminecraft:warped_forestc:is_nether_forestminecraft:crimson_forestminecraft:warped_forestc:is_oceanminecraft:cold_oceanminecraft:deep_cold_oceanminecraft:deep_frozen_oceanminecraft:deep_lukewarm_oceanminecraft:deep_oceanminecraft:frozen_oceanminecraft:lukewarm_oceanminecraft:oceanminecraft:warm_oceanc:is_old_growthminecraft:old_growth_birch_forestminecraft:old_growth_pine_taigaminecraft:old_growth_spruce_taigac:is_outer_end_islandminecraft:end_barrensminecraft:end_highlandsminecraft:end_midlandsc:is_overworldminecraft:badlandsminecraft:bamboo_jungleminecraft:beachminecraft:birch_forestminecraft:cherry_groveminecraft:cold_oceanminecraft:dark_forestminecraft:deep_cold_oceanminecraft:deep_darkminecraft:deep_frozen_oceanminecraft:deep_lukewarm_oceanminecraft:deep_oceanminecraft:desertminecraft:dripstone_cavesminecraft:eroded_badlandsminecraft:flower_forestminecraft:forestminecraft:frozen_oceanminecraft:frozen_peaksminecraft:frozen_riverminecraft:groveminecraft:ice_spikesminecraft:jagged_peaksminecraft:jungleminecraft:lukewarm_oceanminecraft:lush_cavesminecraft:mangrove_swampminecraft:meadowminecraft:mushroom_fieldsminecraft:oceanminecraft:old_growth_birch_forestminecraft:old_growth_pine_taigaminecraft:old_growth_spruce_taigaminecraft:plainsminecraft:riverminecraft:savannaminecraft:savanna_plateauminecraft:snowy_beachminecraft:snowy_plainsminecraft:snowy_slopesminecraft:snowy_taigaminecraft:sparse_jungleminecraft:stony_peaksminecraft:stony_shoreminecraft:sunflower_plainsminecraft:swampminecraft:taigaminecraft:warm_oceanminecraft:windswept_forestminecraft:windswept_gravelly_hillsminecraft:windswept_hillsminecraft:windswept_savannaminecraft:wooded_badlandsc:is_plainsminecraft:plainsminecraft:sunflower_plainsc:is_riverminecraft:frozen_riverminecraft:riverc:is_savannaminecraft:savannaminecraft:savanna_plateauminecraft:windswept_savannac:is_shallow_oceanminecraft:cold_oceanminecraft:frozen_oceanminecraft:lukewarm_oceanminecraft:oceanminecraft:warm_oceanc:is_snowyminecraft:frozen_peaksminecraft:groveminecraft:ice_spikesminecraft:jagged_peaksminecraft:snowy_beachminecraft:snowy_plainsminecraft:snowy_slopesminecraft:snowy_taigac:is_snowy_plainsminecraft:snowy_plainsc:is_sparse_vegetationminecraft:frozen_peaksminecraft:jagged_peaksminecraft:savannaminecraft:savanna_plateauminecraft:snowy_slopesminecraft:sparse_jungleminecraft:windswept_forestminecraft:windswept_gravelly_hillsminecraft:windswept_hillsminecraft:windswept_savannaminecraft:wooded_badlandsc:is_sparse_vegetation/overworldminecraft:frozen_peaksminecraft:jagged_peaksminecraft:savannaminecraft:savanna_plateauminecraft:snowy_slopesminecraft:sparse_jungleminecraft:windswept_forestminecraft:windswept_gravelly_hillsminecraft:windswept_hillsminecraft:windswept_savannaminecraft:wooded_badlandsc:is_stony_shoresminecraft:stony_shorec:is_swampminecraft:mangrove_swampminecraft:swampc:is_taigaminecraft:old_growth_pine_taigaminecraft:old_growth_spruce_taigaminecraft:snowy_taigaminecraft:taigac:is_tree/coniferousminecraft:groveminecraft:old_growth_pine_taigaminecraft:old_growth_spruce_taigaminecraft:snowy_taigaminecraft:taigac:is_tree/deciduousminecraft:birch_forestminecraft:dark_forestminecraft:flower_forestminecraft:forestminecraft:old_growth_birch_forestminecraft:windswept_forestc:is_tree/jungleminecraft:bamboo_jungleminecraft:jungleminecraft:sparse_junglec:is_tree/savannaminecraft:savannaminecraft:savanna_plateauminecraft:windswept_savannac:is_undergroundminecraft:deep_darkminecraft:dripstone_cavesminecraft:lush_cavesc:is_voidminecraft:the_voidc:is_wastelandc:is_wetminecraft:bamboo_jungleminecraft:beachminecraft:dripstone_cavesminecraft:jungleminecraft:lush_cavesminecraft:mangrove_swampminecraft:sparse_jungleminecraft:swampc:is_wet/overworldminecraft:bamboo_jungleminecraft:beachminecraft:dripstone_cavesminecraft:jungleminecraft:lush_cavesminecraft:mangrove_swampminecraft:sparse_jungleminecraft:swampc:is_windsweptminecraft:windswept_forestminecraft:windswept_gravelly_hillsminecraft:windswept_hillsminecraft:windswept_savannac:no_default_monstersminecraft:deep_darkminecraft:mushroom_fieldsforge:is_cold/netherforge:is_dense/endforge:is_dense/netherforge:is_hot/endforge:is_lushminecraft:lush_cavesforge:is_magicalforge:is_modifiedforge:is_plateauminecraft:meadowminecraft:savanna_plateauminecraft:wooded_badlandsforge:is_rareminecraft:bamboo_jungleminecraft:deep_darkminecraft:eroded_badlandsminecraft:flower_forestminecraft:ice_spikesminecraft:mushroom_fieldsminecraft:old_growth_birch_forestminecraft:old_growth_spruce_taigaminecraft:savanna_plateauminecraft:sparse_jungleminecraft:sunflower_plainsminecraft:windswept_gravelly_hillsminecraft:windswept_savannaforge:is_sandyminecraft:badlandsminecraft:beachminecraft:desertminecraft:wooded_badlandsforge:is_sparse/endforge:is_sparse/netherforge:is_spookyminecraft:dark_forestminecraft:deep_darkforge:is_wet/endforge:is_wet/nether
worldgen/structure
c:hidden_from_displayersc:hidden_from_locator_selection
Data Generation: Introduction
Data Generators
Data generators are a way to programmatically generate the assets and data of mods. It allows the definition of the contents of these files in the code and their automatic generation, without worrying about the specifics.
The data generator system is loaded by the main class net.minecraft.data.Main. Different command-line arguments can be passed to customize which mods’ data are gathered, what existing files are considered, etc. The class responsible for data generation is net.minecraft.data.DataGenerator.
The default configurations in the MDK build.gradle adds the runData task for running the data generators.
Existing Files
All references to textures or other data files not generated for data generation must reference existing files on the system. This is to ensure that all referenced textures are in the correct places, so typos can be found and corrected.
ExistingFileHelper is the class responsible for validating the existence of those data files. An instance can be retrieved from GatherDataEvent#getExistingFileHelper.
The --existing <folderpath> argument allows the specified folder and its subfolders to be used when validating the existence of files. Additionally, the --existing-mod <modid> argument allows the resources of a loaded mod to be used for validation. By default, only the vanilla datapack and resources are available to the ExistingFileHelper.
Generator Modes
The data generator can be configured to run 4 different data generations, which are configured from the command-line parameters, and can be checked from GatherDataEvent#include*** methods.
- Client Assets
- Generates client-only files in
assets: block/item models, blockstate JSONs, language files, etc. --client,#includeClient- Server Data
- Generates server-only files in
data: recipes, advancements, tags, etc. --server,#includeServer- Development Tools
- Runs some development tools: converting SNBT to NBT and vice-versa, etc.
--dev,#includeDev- Reports
- Dumps all registered blocks, items, commands, etc.
--reports,#includeReports
All of the generators can be included using --all.
Data Providers
Data providers are the classes that actually define what data will be generated and provided. All data providers implement DataProvider. Minecraft has abstract implementations for most assets and data, so modders need only to extend and override the specified method.
The GatherDataEvent is fired on the mod event bus when the data generator is being created, and the DataGenerator can be obtained from the event. Create and register data providers using DataGenerator#addProvider.
Client Assets
net.minecraftforge.common.data.LanguageProvider- for language strings; implement#addTranslationsnet.minecraftforge.common.data.SoundDefinitionsProvider- forsounds.json; implement#registerSoundsnet.minecraftforge.client.model.generators.ModelProvider<?>- for models; implement#registerModelsItemModelProvider- for item modelsBlockModelProvider- for block modelsnet.minecraftforge.client.model.generators.BlockStateProvider- for blockstate JSONs and their block and item models; implement#registerStatesAndModels
Server Data
These classes are under the net.minecraftforge.common.data package:
GlobalLootModifierProvider- for global loot modifiers; implement#startDatapackBuiltinEntriesProviderfor datapack registry objects; pass inRegistrySetBuilderto the constructor
These classes are under the net.minecraft.data package:
loot.LootTableProvider- for loot tables; pass inLootTableProvider$SubProviderEntrys to the constructorrecipes.RecipeProvider- for recipes and their unlocking advancements; implement#buildRecipestags.TagsProvider- for tags; implement#addTagsadvancements.AdvancementProvider- for advancements; pass inAdvancementSubProviders to the constructor
Data Generation: Model Providers
Model Generation
Models can be generated for models or block states by default. Each provides a method of generating the necessary JSONs (ModelBuilder#toJson for models and IGeneratedBlockState#toJson for block states). After implementation, the associated providers must be added to the DataGenerator.
// On the MOD event bus
@SubscribeEvent
public void gatherData(GatherDataEvent event) {
DataGenerator gen = event.getGenerator();
ExistingFileHelper efh = event.getExistingFileHelper();
gen.addProvider(
// Tell generator to run only when client assets are generating
event.includeClient(),
output -> new MyItemModelProvider(output, MOD_ID, efh)
);
gen.addProvider(
event.includeClient(),
output -> new MyBlockStateProvider(output, MOD_ID, efh)
);
}
Model Files
A ModelFile acts as the base for all models referenced or generated by a provider. Each model file stores the location relative to the models subdirectory and can assert whether the file exists.
Existing Model Files
ExistingModelFile is a subclass of ModelFile which checks via ExistingFileHelper#exists whether the model already exists within the models subdirectory. All non-generated models are usually referenced through ExistingModelFiles.
Unchecked Model Files
UncheckedModelFile is a subclass of ModelFile which assumes the specified model exists in some location.
Note
There should be no cases where an UncheckedModelFile is used to reference a model. If there is, then the associated resources are not properly being tracked by ExistingFileHelper.
Model Builders
A ModelBuilder represents a to-be-generated ModelFile. It contains all the data about a model: its parent, faces, textures, transformations, lighting, and loader.
Tip
While a complex model can be generated, it is recommended that those models be constructed using a modeling software beforehand. Then, the data provider can generate the children models with specific textures applied through the defined references in the parent complex model.
The parent (via ModelBuilder#parent) of the builder can be any ModelFile: generated or existing. Generated files are added to ModelProviders as soon as the builder is created. The builder itself can be passed in as a parent, or the ResourceLocation can supplied alternatively.
Warning
If the parent is not generated before the child model when passing in a ResourceLocation, then an exception will be thrown.
Each element (via ModelBuilder#element) within a model is defined as cube using two three-dimensional points (ElementBuilder#from and #to respectively) where each axis is limited to the values [-16,32] (between -16 and 32 inclusive). Each face (ElementBuilder#face) of the cube can specify when the face is culled (FaceBuilder#cullface), tint index (FaceBuilder#tintindex), texture reference from the textures keys (FaceBuilder#texture), UV coordinate on the texture (FaceBuilder#uvs), and rotation in 90 degree intervals (FaceBuilder#rotation).
Note
It recommended for block models which have elements that exceed a bound of [0,16] on any axis to separate into multiple blocks, such as for a multiblock structure, to avoid lighting and culling issues.
Each cube can additionally be rotated (ElementBuilder#rotation) around a specified point (RotationBuilder#origin) for a given axis (RotationBuilder#axis) in 22.5 degree intervals (RotationBuilder#angle). The cube can scale all faces in relation to the entire model as well (RotationBuilder#rescale). The cube can also determine whether its shadows should be rendered (ElementBuilder#shade).
Each model defines a list of texture keys (ModelBuilder#texture) which points to either a location or a reference. Each key can then be referenced in any element by prefixing using a # (a texture key of example can be referenced in an element using #example). A location specifies where a texture is in assets/<namespace>/textures/<path>.png. A reference is used by any models parenting the current model as keys to define textures for later.
The model can additionally be transformed (ModelBuilder#transforms) for any defined perspective (in the left hand in first person, in the gui, on the ground, etc.). For any perspective (TransformsBuilder#transform), the rotation (TransformVecBuilder#rotation), translation (TransformVecBuilder#translation), and scale (TransformVecBuilder#scale) can be set.
Finally, the model can set whether to use ambient occlusion in a level (ModelBuilder#ao) and from what location to light and shade the model from ModelBuilder#guiLight.
BlockModelBuilder
A BlockModelBuilder represents a block model to-be-generated. In addition to the ModelBuilder, a transform to the entire model (BlockModelBuilder#rootTransform) can be generated. The root can be translated (RootTransformBuilder#transform), rotated (RootTransformBuilder#rotation, RootTransformBuilder#postRotation), and scaled (RootTransformBuilder#scale) either individually or all in one transformation (RootTransformBuilder#transform) around some origin (RootTransformBuilder#origin).
ItemModelBuilder
An ItemModelBuilder represents an item model to-be-generated. In addition to the ModelBuilder, overrides (OverrideBuilder#override) can be generated. Each override applied to a model can apply conditions which represent for a given property that must be above the specified value (OverrideBuilder#predicate). If the conditions are met, then the specified model (OverrideBuilder#model) will be rendered instead of this model.
Model Providers
The ModelProvider subclasses are responsible for generating the constructed ModelBuilders. The provider takes in the generator, mod id, subdirectory in the models folder to generate within, a ModelBuilder factory, and the existing file helper. Each provider subclass must implement #registerModels.
The provider contains basic methods which either create the ModelBuilder or provides convenience for getting texture or model references:
| Method | Description |
|---|---|
getBuilder |
Creates a new ModelBuilder within the provider’s subdirectory for the given mod id. |
withExistingParent |
Creates a new ModelBuilder for the given parent. Should be used when the parent is not generated by the builder. |
mcLoc |
Creates a ResourceLocation for the path in the minecraft namespace. |
modLoc |
Creates a ResourceLocation for the path in the given mod id’s namespace. |
Additionally, there are several helpers for easily generating common models using vanilla templates. Most are for block models with only a few being universal.
Note
Although the models are within a specific subdirectory, that does not mean that the model cannot be referenced by a model in another subdirectory. Usually, it is indicative of that model being used for that type of object.
BlockModelProvider
The BlockModelProvider is used for generating block models via BlockModelBuilder in the block folder. Block models should typically parent minecraft:block/block or one of its children models for use with item models.
Note
Block models and its item model counterpart are typically not generated through a direct subclass of BlockModelProvider and ItemModelProvider but through BlockStateProvider.
ItemModelProvider
The ItemModelProvider is used for generating block models via ItemModelBuilder in the item folder. Most item models parent item/generated and use layer0 to specify their texture, which can be done using #singleTexture.
Note
item/generated can support five texture layers stacked on top of each other: layer0, layer1, layer2, layer3, and layer4.
// In some ItemModelProvider#registerModels
// Will generate 'assets/<modid>/models/item/example_item.json'
// Parent will be 'minecraft:item/generated'
// For the texture key 'layer0'
// It will be at 'assets/<modid>/textures/item/example_item.png'
this.basicItem(EXAMPLE_ITEM.get());
Note
Item models for blocks should typically parent an existing block model instead of generating a separate model for an item.
Block State Provider
A BlockStateProvider is responsible for generating block state JSONs in blockstates, block models in models/block, and item models in models/item for said blocks. The provider takes in the data generator, mod id, and existing file helper. Each BlockStateProvider subclass must implement #registerStatesAndModels.
The provider contains basic methods for generating block state JSONs and block models. Item models must be generated separately as a block state JSON may define multiple models to use in different contexts. There are a number of common methods, however, that that the modder should be aware of when dealing with more complex tasks:
| Method | Description |
|---|---|
models |
Gets the BlockModelProvider used to generate the item block models. |
itemModels |
Gets the ItemModelProvider used to generate the item block models. |
modLoc |
Creates a ResourceLocation for the path in the given mod id’s namespace. |
mcLoc |
Creates a ResourceLocation for the path in the minecraft namespace. |
blockTexture |
References a texture within textures/block which has the same name as the block. |
simpleBlockItem |
Creates an item model for a block given the associated model file. |
simpleBlockWithItem |
Creates a single block state for a block model and an item model using the block model as its parent. |
A block state JSON is made up of variants or conditions. Each variant or condition references a ConfiguredModelList: a list of ConfiguredModels. Each configured model contains the model file (via ConfiguredModel$Builder#modelFile), the X and Y rotation in 90 degree intervals (via #rotationX and rotationY respectively), whether the texture can rotate when the model is rotated by the block state JSON (via #uvLock), and the weight of the model appearing compared to other models in the list (via #weight).
The builder (ConfiguredModel#builder) can also create an array of ConfiguredModels by creating the next model using #nextModel and repeating the settings until #build is called.
VariantBlockStateBuilder
Variants can be generated using BlockStateProvider#getVariantBuilder. Each variant specifies a list of properties (PartialBlockstate) which when matches a BlockState in a level, will display a model chosen from the corresponding model list. An exception is thrown if there is a BlockState which is not covered by any variant defined. Only one variant can be true for any BlockState.
A PartialBlockstate is typically defined using one of three methods:
| Method | Description |
|---|---|
partialState |
Creates a PartialBlockstate to be defined. |
forAllStates |
Defines a function where a given BlockState can be represented by an array of ConfiguredModels. |
forAllStatesExcept |
Defines a function similar to #forAllStates; however, it also specifies which properties do not affect the models rendered. |
For a PartialBlockstate, the properties defined can be specified (#with). The configured models can be set (#setModels), appended to the existing models (#addModels), or built (#modelForState and then ConfiguredModel$Builder#addModel once finished instead of #ConfiguredModel$Builder#build).
// In some BlockStateProvider#registerStatesAndModels
// EXAMPLE_BLOCK_1: Has Property BlockStateProperties#AXIS
this.getVariantBuilder(EXAMPLE_BLOCK_1) // Get variant builder
.partialState() // Construct partial state
.with(AXIS, Axis.Y) // When BlockState AXIS = Y
.modelForState() // Set models when AXIS = Y
.modelFile(yModelFile1) // Can show 'yModelFile1'
.nextModel() // Adds another model when AXIS = Y
.modelFile(yModelFile2) // Can show 'yModelFile2'
.weight(2) // Will show 'yModelFile2' 2/3 of the time
.addModel() // Finalizes models when AXIS = Y
.with(AXIS, Axis.Z) // When BlockState AXIS = Z
.modelForState() // Set models when AXIS = Z
.modelFile(hModelFile) // Can show 'hModelFile'
.addModel() // Finalizes models when AXIS = Z
.with(AXIS, Axis.X) // When BlockState AXIS = X
.modelForState() // Set models when AXIS = X
.modelFile(hModelFile) // Can show 'hModelFile'
.rotationY(90) // Rotates 'hModelFile' 90 degrees on the Y axis
.addModel(); // Finalizes models when AXIS = X
// EXAMPLE_BLOCK_2: Has Property BlockStateProperties#HORIZONTAL_FACING
this.getVariantBuilder(EXAMPLE_BLOCK_2) // Get variant builder
.forAllStates(state -> // For all possible states
ConfiguredModel.builder() // Creates configured model builder
.modelFile(modelFile) // Can show 'modelFile'
.rotationY((int) state.getValue(HORIZONTAL_FACING).toYRot()) // Rotates 'modelFile' on the Y axis depending on the property
.build() // Creates the array of configured models
);
// EXAMPLE_BLOCK_3: Has Properties BlockStateProperties#HORIZONTAL_FACING, BlockStateProperties#WATERLOGGED
this.getVariantBuilder(EXAMPLE_BLOCK_3) // Get variant builder
.forAllStatesExcept(state -> // For all HORIZONTAL_FACING states
ConfiguredModel.builder() // Creates configured model builder
.modelFile(modelFile) // Can show 'modelFile'
.rotationY((int) state.getValue(HORIZONTAL_FACING).toYRot()) // Rotates 'modelFile' on the Y axis depending on the property
.build(), // Creates the array of configured models
WATERLOGGED); // Ignores WATERLOGGED property
MultiPartBlockStateBuilder
Multiparts can be generated using BlockStateProvider#getMultipartBuilder. Each part (MultiPartBlockStateBuilder#part) specifies a group of conditions of properties which when matches a BlockState in a level, will display a model from the model list. All condition groups that match the BlockState will display their chosen model overlaid on each other.
For any part (obtained via ConfiguredModel$Builder#addModel), a condition can be added (via #condition) when a property is one of the specified values. Conditions must all succeed or, when #useOr is set, at least one must succeed. Conditions can be grouped (via #nestedGroup) as long as the current grouping only contains other groups and no single conditions. Groups of conditions can be left using #endNestedGroup and a given part can be finished via #end.
// In some BlockStateProvider#registerStatesAndModels
// Redstone Wire
this.getMultipartBuilder(REDSTONE) // Get multipart builder
.part() // Create part
.modelFile(redstoneDot) // Can show 'redstoneDot'
.addModel() // 'redstoneDot' is displayed when...
.useOr() // At least one of these conditions are true
.nestedGroup() // true when all grouped conditions are true
.condition(WEST_REDSTONE, NONE) // true when WEST_REDSTONE is NONE
.condition(EAST_REDSTONE, NONE) // true when EAST_REDSTONE is NONE
.condition(SOUTH_REDSTONE, NONE) // true when SOUTH_REDSTONE is NONE
.condition(NORTH_REDSTONE, NONE) // true when NORTH_REDSTONE is NONE
.endNestedGroup() // End group
.nestedGroup() // true when all grouped conditions are true
.condition(EAST_REDSTONE, SIDE, UP) // true when EAST_REDSTONE is SIDE or UP
.condition(NORTH_REDSTONE, SIDE, UP) // true when NORTH_REDSTONE is SIDE or UP
.endNestedGroup() // End group
.nestedGroup() // true when all grouped conditions are true
.condition(EAST_REDSTONE, SIDE, UP) // true when EAST_REDSTONE is SIDE or UP
.condition(SOUTH_REDSTONE, SIDE, UP) // true when SOUTH_REDSTONE is SIDE or UP
.endNestedGroup() // End group
.nestedGroup() // true when all grouped conditions are true
.condition(WEST_REDSTONE, SIDE, UP) // true when WEST_REDSTONE is SIDE or UP
.condition(SOUTH_REDSTONE, SIDE, UP) // true when SOUTH_REDSTONE is SIDE or UP
.endNestedGroup() // End group
.nestedGroup() // true when all grouped conditions are true
.condition(WEST_REDSTONE, SIDE, UP) // true when WEST_REDSTONE is SIDE or UP
.condition(NORTH_REDSTONE, SIDE, UP) // true when NORTH_REDSTONE is SIDE or UP
.endNestedGroup() // End group
.end() // Finish part
.part() // Create part
.modelFile(redstoneSide0) // Can show 'redstoneSide0'
.addModel() // 'redstoneSide0' is displayed when...
.condition(NORTH_REDSTONE, SIDE, UP) // NORTH_REDSTONE is SIDE or UP
.end() // Finish part
.part() // Create part
.modelFile(redstoneSideAlt0) // Can show 'redstoneSideAlt0'
.addModel() // 'redstoneSideAlt0' is displayed when...
.condition(SOUTH_REDSTONE, SIDE, UP) // SOUTH_REDSTONE is SIDE or UP
.end() // Finish part
.part() // Create part
.modelFile(redstoneSideAlt1) // Can show 'redstoneSideAlt1'
.rotationY(270) // Rotates 'redstoneSideAlt1' 270 degrees on the Y axis
.addModel() // 'redstoneSideAlt1' is displayed when...
.condition(EAST_REDSTONE, SIDE, UP) // EAST_REDSTONE is SIDE or UP
.end() // Finish part
.part() // Create part
.modelFile(redstoneSide1) // Can show 'redstoneSide1'
.rotationY(270) // Rotates 'redstoneSide1' 270 degrees on the Y axis
.addModel() // 'redstoneSide1' is displayed when...
.condition(WEST_REDSTONE, SIDE, UP) // WEST_REDSTONE is SIDE or UP
.end() // Finish part
.part() // Create part
.modelFile(redstoneUp) // Can show 'redstoneUp'
.addModel() // 'redstoneUp' is displayed when...
.condition(NORTH_REDSTONE, UP) // NORTH_REDSTONE is UP
.end() // Finish part
.part() // Create part
.modelFile(redstoneUp) // Can show 'redstoneUp'
.rotationY(90) // Rotates 'redstoneUp' 90 degrees on the Y axis
.addModel() // 'redstoneUp' is displayed when...
.condition(EAST_REDSTONE, UP) // EAST_REDSTONE is UP
.end() // Finish part
.part() // Create part
.modelFile(redstoneUp) // Can show 'redstoneUp'
.rotationY(180) // Rotates 'redstoneUp' 180 degrees on the Y axis
.addModel() // 'redstoneUp' is displayed when...
.condition(SOUTH_REDSTONE, UP) // SOUTH_REDSTONE is UP
.end() // Finish part
.part() // Create part
.modelFile(redstoneUp) // Can show 'redstoneUp'
.rotationY(270) // Rotates 'redstoneUp' 270 degrees on the Y axis
.addModel() // 'redstoneUp' is displayed when...
.condition(WEST_REDSTONE, UP) // WEST_REDSTONE is UP
.end(); // Finish part
Model Loader Builders
Custom model loaders can also be generated for a given ModelBuilder. Custom model loaders subclass CustomLoaderBuilder and can be applied to a ModelBuilder via #customLoader. The factory method passed in creates a new loader builder to which configurations can be made. After all the changes have been finished, the custom loader can return back to the ModelBuilder via CustomLoaderBuilder#end.
| Model Builder | Factory Method | Description |
|---|---|---|
DynamicFluidContainerModelBuilder |
#begin |
Generates a bucket model for the specified fluid. |
CompositeModelBuilder |
#begin |
Generates a model composed of models. |
ItemLayersModelBuilder |
#begin |
Generates a Forge implementation of an item/generated model. |
SeparateTransformsModelBuilder |
#begin |
Generates a model which changes based on the specified transform. |
ObjModelBuilder |
#begin |
Generates an OBJ model. |
// For some BlockModelBuilder builder
builder.customLoader(ObjModelBuilder::begin) // Custom loader 'forge:obj'
.modelLocation(modLoc("models/block/model.obj")) // Set the OBJ model location
.flipV(true) // Flips the V coordinate in the supplied .mtl texture
.end() // Finish custom loader configuration
.texture("particle", mcLoc("block/dirt")) // Set particle texture to dirt
.texture("texture0", mcLoc("block/dirt")); // Set 'texture0' texture to dirt
Custom Model Loader Builders
Custom loader builders can be created by extending CustomLoaderBuilder. The constructor can still have a protected visibility with the ResourceLocation hardcoded to the loader id registered via ModelEvent$RegisterGeometryLoaders#register. The builder can then be initialized via a static factory method or the constructor if made public.
public class ExampleLoaderBuilder<T extends ModelBuilder<T>> extends CustomLoaderBuilder<T> {
public static <T extends ModelBuilder<T>> ExampleLoaderBuilder<T> begin(T parent, ExistingFileHelper existingFileHelper) {
return new ExampleLoaderBuilder<>(parent, existingFileHelper);
}
protected ExampleLoaderBuilder(T parent, ExistingFileHelper existingFileHelper) {
super(ResourceLocation.fromNamespaceAndPath(MOD_ID, "example_loader"), parent, existingFileHelper);
}
}
Afterwards, any configurations specified by the loader should be added as chainable methods.
// In ExampleLoaderBuilder
public ExampleLoaderBuilder<T> exampleInt(int example) {
// Set int
return this;
}
public ExampleLoaderBuilder<T> exampleString(String example) {
// Set string
return this;
}
If any additional configuration is specified, #toJson should be overridden to write the additional properties.
// In ExampleLoaderBuilder
@Override
public JsonObject toJson(JsonObject json) {
json = super.toJson(json); // Handle base loader properties
// Encode custom loader properties
return json;
}
Custom Model Providers
Custom model providers require a ModelBuilder subclass, which defines the base of the model to generate, and a ModelProvider subclass, which generates the models.
The ModelBuilder subclass contains any special properties to which can be applied specifically to those types of models (item models can have overrides). If any additional properties are added, #toJson needs to be overridden to write the additional information.
public class ExampleModelBuilder extends ModelBuilder<ExampleModelBuilder> {
// ...
}
The ModelProvider subclass requires no special logic. The constructor should hardcode the subdirectory within the models folder and the ModelBuilder to represent the to-be-generated models.
public class ExampleModelProvider extends ModelProvider<ExampleModelBuilder> {
public ExampleModelProvider(PackOutput output, String modid, ExistingFileHelper existingFileHelper) {
// Models will be generated to 'assets/<modid>/models/example' if no 'modid' is specified in '#getBuilder'
super(output, modid, "example", ExampleModelBuilder::new, existingFileHelper);
}
}
Custom Model Consumers
Custom model consumers like BlockStateProvider can be created by manually generating the models themselves. The ModelProvider subclass used to generate the models should be specified and made available.
public class ExampleModelConsumerProvider implements IDataProvider {
public ExampleModelConsumerProvider(PackOutput output, String modid, ExistingFileHelper existingFileHelper) {
this.example = new ExampleModelProvider(output, modid, existingFileHelper);
}
}
Once the data provider is running, the models within the ModelProvider subclass can be generated using ModelProvider#generateAll.
// In ExampleModelConsumerProvider
@Override
public CompletableFuture<?> run(CachedOutput cache) {
// Populate the model provider
CompletableFuture<?> exampleFutures = this.example.generateAll(cache); // Generate the models
// Run logic and create CompletableFuture(s) for writing files
// ...
// Assume we have a new CompletableFuture providerFuture
return CompletableFuture.allOf(exampleFutures, providerFuture);
}
Data Generation: Language Providers
Language Generation
Language files can be generated for a mod by subclassing LanguageProvider and implementing #addTranslations. Each LanguageProvider subclass created represents a separate locale (en_us represents American English, es_es represents Spanish, etc.). 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 client assets are generating
event.includeClient(),
// Localizations for American English
output -> new MyLanguageProvider(output, MOD_ID, "en_us")
);
}
LanguageProvider
Each language provider is simple a map of strings where each translation key is mapped to a localized name. A translation key mapping can be added using #add. Additionally, there are methods which use the translation key of a Block, Item, ItemStack, Enchantment, MobEffect, and EntityType.
// In LanguageProvider#addTranslations
this.addBlock(EXAMPLE_BLOCK, "Example Block");
this.add("object.examplemod.example_object", "Example Object");
Tip
Localized names which contain alphanumeric values not in American English can be supplied as is. The provider automatically translates the characters into their unicode equivalents to be read by the game.
// Encdoded as 'Example with a d\u00EDacritic'
this.addItem("example.diacritic", "Example with a díacritic");
Data Generation: Sound Providers
Sound Definition Generation
The sounds.json file can be generated for a mod by subclassing SoundDefinitionsProvider and implementing #registerSounds. 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 client assets are generating
event.includeClient(),
output -> new MySoundDefinitionsProvider(output, MOD_ID, event.getExistingFileHelper())
);
}
Adding a Sound
A sound definition can be generated by specifying the sound name and definition via #add. The sound name can either be provided from a SoundEvent, ResourceLocation, or string.
Warning
The sound name supplied will always assume the namespace is the mod id supplied to the constructor of the provider. There is no validation performed on the namespace of the sound name!
SoundDefinition
The SoundDefinition can be created using #definition. The definition contains the data to define a sound instance.
A definition specifies a few methods:
| Method | Description |
|---|---|
with |
Adds a sound(s) which may be played when the definition is selected. |
subtitle |
Sets the translation key of the definition. |
replace |
When true, removes the sounds already defined by other sounds.json for this definition instead of appending to it. |
SoundDefinition$Sound
A sound supplied to the SoundDefinition can be specified using SoundDefinitionsProvider#sound. These methods take in the reference of the sound and a SoundType if specified.
The SoundType can be one of two values:
| Sound Type | Definition |
|---|---|
SOUND |
Specifies a reference to the sound located at assets/<namespace>/sounds/<path>.ogg. |
EVENT |
Specifies a reference to the name of another sound defined by the sounds.json. |
Each Sound created from SoundDefinitionsProvider#sound can specify additional configurations on how to load and play the sound provided:
| Method | Description |
|---|---|
volume |
Sets the volume scale of the sound, must be greater than 0. |
pitch |
Sets the pitch scale of the sound, must be greater than 0. |
weight |
Sets the likelihood of the sound getting played when the sound is selected. |
stream |
When true, reads the sound from file instead of loading the sound into memory. Recommended for long sounds: background music, music discs, etc. |
attenuationDistance |
Sets the number of blocks the sound can be heard from. |
preload |
When true, immediately loads the sound into memory as soon as the resource pack is loaded. |
// In some SoundDefinitionsProvider#registerSounds
this.add(EXAMPLE_SOUND_EVENT, definition()
.subtitle("sound.examplemod.example_sound") // Set translation key
.with(
sound(ResourceLocation.fromNamespaceAndPath(MODID, "example_sound_1")) // Set first sound
.weight(4) // Has a 4 / 5 = 80% chance of playing
.volume(0.5), // Scales all volumes called on this sound by half
sound(ResourceLocation.fromNamespaceAndPath(MODID, "example_sound_2")) // Set second sound
.stream() // Streams the sound
)
);
this.add(EXAMPLE_SOUND_EVENT_2, definition()
.subtitle("sound.examplemod.example_sound") // Set translation key
.with(
sound(EXAMPLE_SOUND_EVENT.getLocation(), SoundType.EVENT) // Adds sounds from 'EXAMPLE_SOUND_EVENT'
.pitch(0.5) // Scales all pitches called on this sound by half
)
);
Data Generation: Loot Table Providers
Loot Table Generation
Loot tables can be generated for a mod by constructing a new LootTableProvider and providing LootTableProvider$SubProviderEntrys. 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(),
output -> new MyLootTableProvider(
output,
// Specify registry names of tables that are required to generate, or can leave empty
Collections.emptySet(),
// Sub providers which generate the loot
List.of(subProvider1, subProvider2, /*...*/)
)
);
}
LootTableSubProvider
Each LootTableProvider$SubProviderEntry takes in a supplied LootTableSubProvider, which generates the loot table, for a given LootContextParamSet. The LootTableSubProvider contains a method which takes in the writer (BiConsumer<ResourceLocation, LootTable.Builder>) to generate a table.
public class ExampleSubProvider implements LootTableSubProvider {
// Used to create a factory method for the wrapping Supplier
public ExampleSubProvider() {}
// The method used to generate the loot tables
@Override
public void generate(BiConsumer<ResourceLocation, LootTable.Builder> writer) {
// Generate loot tables here by calling writer#accept
}
}
The table can then be added to LootTableProvider#getTables for any available LootContextParamSet:
// In the list passed into the LootTableProvider constructor
new LootTableProvider.SubProviderEntry(
ExampleSubProvider::new,
// Loot table generator for the 'empty' param set
LootContextParamSets.EMPTY
)
BlockLootSubProvider and EntityLootSubProvider Subclasses
For LootContextParamSets#BLOCK and #ENTITY, there are special types (BlockLootSubProvider and EntityLootSubProvider respectively) which provide additional helper methods for creating and validating that there are loot tables.
The BlockLootSubProvider’s constructor takes in a list of items, which are explosion resistant to determine whether the loot table can be generated if a block is exploded, and a FeatureFlagSet, which determines whether the block is enabled so that a loot table is generated for it.
// In some BlockLootSubProvider subclass
public MyBlockLootSubProvider() {
super(Collections.emptySet(), FeatureFlags.REGISTRY.allFlags());
}
The EntityLootSubProvider’s constructor takes in a FeatureFlagSet, which determines whether the entity type is enabled so that a loot table is generated for it.
// In some EntityLootSubProvider subclass
public MyEntityLootSubProvider() {
super(FeatureFlags.REGISTRY.allFlags());
}
To use them, all registered objects must be supplied to either BlockLootSubProvider#getKnownBlocks and EntityLootSubProvider#getKnownEntityTypes respectively. These methods are to make sure all objects within the iterable has a loot table.
Tip
If DeferredRegister is being used to register a mod’s objects, then the #getKnown* methods can be supplied the entries via DeferredRegister#getEntries:
// In some BlockLootSubProvider subclass for some DeferredRegister BLOCK_REGISTRAR
@Override
protected Iterable<Block> getKnownBlocks() {
return BLOCK_REGISTRAR.getEntries() // Get all registered entries
.stream() // Stream the wrapped objects
.flatMap(RegistryObject::stream) // Get the object if available
::iterator; // Create the iterable
}
The loot tables themselves can be added by implementing the #generate method.
// In some BlockLootSubProvider subclass
@Override
public void generate() {
// Add loot tables here
}
Loot Table Builders
To generate loot tables, they are accepted by the LootTableSubProvider as a LootTable$Builder. Afterwards, the specified LootContextParamSet is set in the LootTableProvider$SubProviderEntry and then built via #build. Before being built, the builder can specify entries, conditions, and modifiers which affect how the loot table functions.
Note
The functionality of loot tables is so expansive that it will not be covered by this documentation in its entirety. Instead, a brief description of each component will be mentioned. The specific subtypes of each component can be found using an IDE. Their implementations will be left as an exercise to the reader.
LootTable
Loot tables are the base object and can be transformed into the required LootTable$Builder using LootTable#lootTable. The loot table can be built with a list of pools (via #withPool) applied in the order they are specified along with functions (via #apply) to modify the resulting items of those pools.
LootPool
Loot pools represents a group to perform operations and can generate a LootPool$Builder using LootPool#lootPool. Each loot pool can specify the entries (via #add) which define the operations in the pool, the conditions (via #when) which define if the operations in the pool should be performed, and functions (via #apply) to modify the resulting items of the entries. Each pool can be executed as many times as specified (via #setRolls). Additionally, bonus executions can be specified (via #setBonusRolls) which is modified by the luck of the executor.
LootPoolEntryContainer
Loot entries define the operations to occur when selected, typically generating items. Each entry has an associated, registered LootPoolEntryType. They also have their own associated builders which subtype LootPoolEntryContainer$Builder. Multiple entries can execute at the same time (via #append) or sequentially until one fails (via #then). Additionally, entries can default to another entry on failure (via #otherwise).
LootItemCondition
Loot conditions define requirements which need to be met for some operation to execute. Each condition has an associated, registered LootItemConditionType. They also have their own associated builders which subtype LootItemCondition$Builder. By default, all loot conditions specified must return true for an operation to execute. Loot conditions can also be specified such that only one must return true instead (via #or). Additionally, the resulting output of a condition can be inverted (via #invert).
LootItemFunction
Loot functions modify the result of an execution before passing it to the output. Each function has an associated, registered LootItemFunctionType. They also have their own associated builders which subtype LootItemFunction$Builder.
NbtProvider
NBT providers are a special type of functions defined by CopyNbtFunction. They define where to pull tag information from. Each provider has an associated, registered LootNbtProviderType.
NumberProvider
Number providers determine how many times a loot pool executes. Each provider has an associated, registered LootNumberProviderType.
ScoreboardNameProvider
Scoreboard providers are a special type of number providers defined by ScoreboardValue. They define the name of the scoreboard to pull the number of rolls to execute from. Each provider has an associated, registered LootScoreProviderType.
Data Generation: Tag Providers
Tag Generation
Tags can be generated for a mod by subclassing TagsProvider and implementing #addTags. 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(),
// Extends net.minecraftforge.common.data.BlockTagsProvider
output -> new MyBlockTagsProvider(
output,
event.getLookupProvider(),
MOD_ID,
event.getExistingFileHelper()
)
);
}
TagsProvider
The tags provider has two methods used for generating tags: creating a tag with objects and other tags via #tag, or using tags from other object types to generate the tag data via #getOrCreateRawBuilder.
Note
Typically, a provider will not call #getOrCreateRawBuilder directly unless a registry contains a representation of objects from a different registry (blocks have item representations to obtain the blocks in the inventory).
When #tag is called, a TagAppender is created which acts as a chainable consumer of elements to add to the tag:
| Method | Description |
|---|---|
add |
Adds an object to a tag through its resource key. |
addOptional |
Adds an object to a tag through its name. If the object is not present, then the object will be skipped when loading. |
addTag |
Adds a tag to a tag through its tag key. All elements within the inner tag are now a part of the outer tag. |
addOptionalTag |
Adds a tag to a tag through its name. If the tag is not present, then the tag will be skipped when loading. |
replace |
When true, all previously loaded entries added to this tag from other datapacks will be discarded. If a datapack is loaded after this one, then it will still append the entries to the tag. |
remove |
Removes an object or tag from a tag through its name or key. |
// In some TagProvider#addTags
this.tag(EXAMPLE_TAG)
.add(EXAMPLE_OBJECT) // Adds an object to the tag
.addOptional(ResourceLocation.fromNamespaceAndPath("othermod", "other_object")) // Adds an object from another mod to the tag
this.tag(EXAMPLE_TAG_2)
.addTag(EXAMPLE_TAG) // Adds a tag to the tag
.remove(EXAMPLE_OBJECT) // Removes an object from this tag
Important
If the mod’s tags softly depends on another mod’s tags (the other mod may or may not be present at runtime), the other mods’ tags should be referenced using the optional methods.
Existing Providers
Minecraft contains a few tag providers for certain registries that can be subclassed instead. Additionally, some providers contain additional helper methods to more easily create tags.
| Registry Object Type | Tag Provider |
|---|---|
Block |
BlockTagsProvider* |
Item |
ItemTagsProvider |
EntityType |
EntityTypeTagsProvider |
Fluid |
FluidTagsProvider |
GameEvent |
GameEventTagsProvider |
Biome |
BiomeTagsProvider |
FlatLevelGeneratorPreset |
FlatLevelGeneratorPresetTagsProvider |
WorldPreset |
WorldPresetTagsProvider |
Structure |
StructureTagsProvider |
PoiType |
PoiTypeTagsProvider |
BannerPattern |
BannerPatternTagsProvider |
CatVariant |
CatVariantTagsProvider |
PaintingVariant |
PaintingVariantTagsProvider |
Instrument |
InstrumentTagsProvider |
DamageType |
DamageTypeTagsProvider |
* BlockTagsProvider is a Forge added TagsProvider.
ItemTagsProvider#copy
Blocks have item representations to obtain them in the inventory. As such, many of the block tags can also be an item tag. To easily generate item tags to have the same entries as block tags, the #copy method can be used which takes in the block tag to copy from and the item tag to copy to.
//In ItemTagsProvider#addTags
this.copy(EXAMPLE_BLOCK_TAG, EXAMPLE_ITEM_TAG);
Custom Tag Providers
A custom tag provider can be created via a TagsProvider subclass which takes in the registry key to generate tags for.
public RecipeTypeTagsProvider(PackOutput output, CompletableFuture<HolderLookup.Provider> registries, ExistingFileHelper fileHelper) {
super(output, Registries.RECIPE_TYPE, registries, MOD_ID, fileHelper);
}
Intrinsic Holder Tags Providers
One special type of TagProviders are IntrinsicHolderTagsProviders. When creating a tag using this provider via #tag, the object itself can be used to add itself to the tag via #add. To do so, a function is provided within the constructor to turn an object into its ResourceKey.
// Subtype of `IntrinsicHolderTagsProvider`
public AttributeTagsProvider(PackOutput output, CompletableFuture<HolderLookup.Provider> registries, ExistingFileHelper fileHelper) {
super(
output,
ForgeRegistries.Keys.ATTRIBUTES,
registries,
attribute -> ForgeRegistries.ATTRIBUTES.getResourceKey(attribute).get(),
MOD_ID,
fileHelper
);
}
Data Generation: Advancement Providers
Advancement Generation
Advancements can be generated for a mod by constructing a new AdvancementProvider and providing AdvancementSubProviders. Advancements can either be created and supplied manually or, for convenience, created using Advancement$Builder. The provider must be added to the DataGenerator.
Note
Forge provides an extension for the AdvancementProvider called ForgeAdvancementProvider which integrates better for generating advancements. So, this documentation will use ForgeAdvancementProvider along with the sub provider interface ForgeAdvancementProvider$AdvancementGenerator.
// 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(),
output -> new ForgeAdvancementProvider(
output,
event.getLookupProvider(),
event.getExistingFileHelper(),
// Sub providers which generate the advancements
List.of(subProvider1, subProvider2, /*...*/)
)
);
}
ForgeAdvancementProvider$AdvancementGenerator
A ForgeAdvancementProvider$AdvancementGenerator is responsible for generating advancements, containing a method which takes in a registry lookup, the writer (Consumer<Advancement>), and the existing file helper..
// In some subclass of ForgeAdvancementProvider$AdvancementGenerator or as a lambda reference
@Override
public void generate(HolderLookup.Provider registries, Consumer<Advancement> writer, ExistingFileHelper existingFileHelper) {
// Build advancements here
}
Advancement$Builder
Advancement$Builder is a convenience implementation for creating Advancements to generate. It allows the definition of the parent advancement, the display information, the rewards when the advancement has been completed, and the requirements to unlock the advancement. Only the requirements need to be specified to create an Advancement.
Although not required, there are a number of methods that are important to know of:
| Method | Description |
|---|---|
parent |
Sets the advancement which this advancement is directly linked to. Can either specify the name of the advancement or the advancement itself if its generated by the modder. |
display |
Sets the information to display to the chat, toast, and advancement screen. |
rewards |
Sets the rewards obtained when this advancement is completed. |
addCriterion |
Adds a condition to the advancement. |
requirements |
Specifies if the conditions must all return true or at least one must return true. An additional overload can be used to mix-and-match those operations. |
Once an Advancement$Builder is ready to be built, the #save method should be called which takes in the writer, the registry name of the advancement, and the file helper used to check whether the supplied parent exists.
// In some ForgeAdvancementProvider$AdvancementGenerator#generate(registries, writer, existingFileHelper)
Advancement example = Advancement.Builder.advancement()
.addCriterion("example_criterion", triggerInstance) // How the advancement is unlocked
.save(writer, name, existingFileHelper); // Add data to builder
Data Generation: Global Loot Modifier Providers
Global Loot Modifier Generation
Global Loot Modifiers (GLMs) can be generated for a mod by subclassing GlobalLootModifierProvider and implementing #start. Each GLM can be added generated by calling #add and specifying the name of the modifier and the modifier instance to be serialized. 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(),
output -> new MyGlobalLootModifierProvider(output, MOD_ID)
);
}
// In some GlobalLootModifierProvider#start
this.add("example_modifier", new ExampleModifier(
new LootItemCondition[] {
WeatherCheck.weather().setRaining(true).build() // Executes when raining
},
"val1",
10,
Items.DIRT
));
Data Generation: Datapack Registry Object Providers
Datapack Registry Object Generation
Datapack registry objects can be generated for a mod by constructing a new DatapackBuiltinEntriesProvider and providing a RegistrySetBuilder with the new objects to register. The provider must be added to the DataGenerator.
Note
DatapackBuiltinEntriesProvider is a Forge extension on top of RegistriesDatapackGenerator which properly handles referencing existing datapack registry objects without exploding the entry. So, this documentation will use DatapackBuiltinEntriesProvider.
// 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(),
output -> new DatapackBuiltinEntriesProvider(
output,
event.getLookupProvider(),
// The builder containing the datapack registry objects to generate
new RegistrySetBuilder().add(/* ... */),
// Set of mod ids to generate the datapack registry objects of
Set.of(MOD_ID)
)
);
}
RegistrySetBuilder
A RegistrySetBuilder is responsible for building all datapack registry objects to be used within the game. The builder can add a new entry for a registry, which can then register objects to that registry.
First, a new instance of a RegistrySetBuilder can be initialized by calling the constructor. Then, the #add method (which takes in the ResourceKey of the registry, a RegistryBootstrap consumer containing the BootstapContext to register the objects, and an optional Lifecycle argument to indicate the registry’s current lifecycle status) can be called to handle a specific registry for registration.
new RegistrySetBuilder()
// Create configured features
.add(Registries.CONFIGURED_FEATURE, bootstrap -> {
// Register configured features here
})
// Create placed features
.add(Registries.PLACED_FEATURE, bootstrap -> {
// Register placed features here
});
Note
Datapack registries created through Forge can also generate their objects using this builder by also passing in the associated ResourceKey.
Registering with BootstapContext
The #register method in the BootstapContext provided by the builder can be used to register objects. It takes in the ResourceKey representing the registry name of the object, the object to register, and an optional Lifecycle argument to indicate the registry object’s current lifecycle status.
public static final ResourceKey<ConfiguredFeature<?, ?>> EXAMPLE_CONFIGURED_FEATURE = ResourceKey.create(
Registries.CONFIGURED_FEATURE,
ResourceLocation.fromNamespaceAndPath(MOD_ID, "example_configured_feature")
);
// In some constant location or argument
new RegistrySetBuilder()
// Create configured features
.add(Registries.CONFIGURED_FEATURE, bootstrap -> {
// Register configured features here
bootstrap.register(
// The resource key for the configured feature
EXAMPLE_CONFIGURED_FEATURE,
new ConfiguredFeature<>(
Feature.ORE, // Create an ore feature
new OreConfiguration(
List.of(), // Does nothing
8 // in veins of at most 8
)
)
);
})
// Create placed features
.add(Registries.PLACED_FEATURE, bootstrap -> {
// Register placed features here
});
Datapack Registry Object Lookup
Sometimes datapack registry objects may want to use other datapack registry objects or tags containing datapack registry objects. In those cases, you can look up another datapack registry using BootstapContext#lookup to get a HolderGetter. From there, you can get a Holder$Reference to the datapack registry object or a HolderSet$Named for the tag via #getOrThrow by passing in the associated key.
public static final ResourceKey<ConfiguredFeature<?, ?>> EXAMPLE_CONFIGURED_FEATURE = ResourceKey.create(
Registries.CONFIGURED_FEATURE,
ResourceLocation.fromNamespaceAndPath(MOD_ID, "example_configured_feature")
);
public static final ResourceKey<PlacedFeature> EXAMPLE_PLACED_FEATURE = ResourceKey.create(
Registries.PLACED_FEATURE,
ResourceLocation.fromNamespaceAndPath(MOD_ID, "example_placed_feature")
);
// In some constant location or argument
new RegistrySetBuilder()
// Create configured features
.add(Registries.CONFIGURED_FEATURE, bootstrap -> {
// Register configured features here
bootstrap.register(
// The resource key for the configured feature
EXAMPLE_CONFIGURED_FEATURE,
new ConfiguredFeature(/* ... */)
);
})
// Create placed features
.add(Registries.PLACED_FEATURE, bootstrap -> {
// Register placed features here
// Get configured feature registry
HolderGetter<ConfiguredFeature<?, ?>> configured = bootstrap.lookup(Registries.CONFIGURED_FEATURE);
bootstrap.register(
// The resource key for the placed feature
EXAMPLE_PLACED_FEATURE,
new PlacedFeature(
configured.getOrThrow(EXAMPLE_CONFIGURED_FEATURE), // Get the configured feature
List.of() // and do nothing to the placement location
)
)
});
Misc: Key Mappings
Key Mappings
A key mapping, or key binding, defines a particular action that should be tied to an input: mouse click, key press, etc. Each action defined by a key mapping can be checked whenever the client can take an input. Furthermore, each key mapping can be assigned to any input through the Controls option menu.
Registering a KeyMapping
A KeyMapping can be registered by listening to the RegisterKeyMappingsEvent on the mod event bus only on the physical client and calling #register.
// In some physical client only class
// Key mapping is lazily initialized so it doesn't exist until it is registered
public static final Lazy<KeyMapping> EXAMPLE_MAPPING = Lazy.of(() -> /*...*/);
// Event is on the mod event bus only on the physical client
@SubscribeEvent
public void registerBindings(RegisterKeyMappingsEvent event) {
event.register(EXAMPLE_MAPPING.get());
}
Creating a KeyMapping
A KeyMapping can be created using it’s constructor. The KeyMapping takes in a translation key defining the name of the mapping, the default input of the mapping, and the translation key defining the category the mapping will be put within in the Controls option menu.
Tip
A KeyMapping can be added to a custom category by providing a category translation key not provided by vanilla. Custom category translation keys should contain the mod id (e.g. key.categories.examplemod.examplecategory).
Default Inputs
Each key mapping has a default input associated with it. This is provided through InputConstants$Key. Each input consists of an InputConstants$Type, which defines what device is providing the input, and an integer, which defines the associated identifier of the input on the device.
Vanilla provides three types of inputs: KEYSYM, which defines a keyboard through the provided GLFW key tokens, SCANCODE, which defines a keyboard through the platform-specific scancode, and MOUSE, which defines a mouse.
Note
It is highly recommended to use KEYSYM over SCANCODE for keyboards as GLFW key tokens are not tied to any particular system. You can read more on the GLFW docs.
The integer is dependent on the type provided. All input codes are defined in GLFW: KEYSYM tokens are prefixed with GLFW_KEY_* while MOUSE codes are prefixed with GLFW_MOUSE_*.
new KeyMapping(
"key.examplemod.example1", // Will be localized using this translation key
InputConstants.Type.KEYSYM, // Default mapping is on the keyboard
GLFW.GLFW_KEY_P, // Default key is P
"key.categories.misc" // Mapping will be in the misc category
)
Note
If the key mapping should not be mapped to a default, the input should be set to InputConstants#UNKNOWN. The vanilla constructor will require you to extract the input code via InputConstants$Key#getValue while the Forge constructor can be supplied the raw input field.
IKeyConflictContext
Not all mappings are used in every context. Some mappings are only used in a GUI, while others are only used purely in game. To avoid mappings of the same key used in different contexts conflicting with each other, an IKeyConflictContext can be assigned.
Each conflict context contains two methods: #isActive, which defines if the mapping can be used in the current game state, and #conflicts, which defines whether the mapping conflicts with a key in the same or different conflict context.
Currently, Forge defines three basic contexts through KeyConflictContext: UNIVERSAL, which is the default meaning the key can be used in every context, GUI, which means the mapping can only be used when a Screen is open, and IN_GAME, which means the mapping can only be used if a Screen is not open. New conflict contexts can be created by implementing IKeyConflictContext.
new KeyMapping(
"key.examplemod.example2",
KeyConflictContext.GUI, // Mapping can only be used when a screen is open
InputConstants.Type.MOUSE, // Default mapping is on the mouse
GLFW.GLFW_MOUSE_BUTTON_LEFT, // Default mouse input is the left mouse button
"key.categories.examplemod.examplecategory" // Mapping will be in the new example category
)
KeyModifier
Modders may not want mappings to have the same behavior if a modifier key is held at the same (e.g. G vs CTRL + G). To remedy this, Forge adds an additional parameter to the constructor to take in a KeyModifier which can apply control (KeyModifier#CONTROL), shift (KeyModifier#SHIFT), or alt (KeyModifier#ALT) to any input. KeyModifier#NONE is the default and will apply no modifier.
A modifier can be added in the controls option menu by holding down the modifier key and the associated input.
new KeyMapping(
"key.examplemod.example3",
KeyConflictContext.UNIVERSAL,
KeyModifier.SHIFT, // Default mapping requires shift to be held down
InputConstants.Type.KEYSYM, // Default mapping is on the keyboard
GLFW.GLFW_KEY_G, // Default key is G
"key.categories.misc"
)
Checking a KeyMapping
A KeyMapping can be checked to see whether it has been clicked. Depending on when, the mapping can be used in a conditional to apply the associated logic.
Within the Game
Within the game, a mapping should be checked by listening to ClientTickEvent on the Forge event bus and checking KeyMapping#consumeClick within a while loop. #consumeClick will return true only the number of times the input was performed and not already previously handled, so it won’t infinitely stall the game.
// Event is on the Forge event bus only on the physical client
public void onClientTick(ClientTickEvent event) {
if (event.phase == TickEvent.Phase.END) { // Only call code once as the tick event is called twice every tick
while (EXAMPLE_MAPPING.get().consumeClick()) {
// Execute logic to perform on click here
}
}
}
Warning
Do not use the InputEvents as an alternative to ClientTickEvent. There are separate events for keyboard and mouse inputs only, so they wouldn’t handle any additional inputs.
Inside a GUI
Within a GUI, a mapping can be checked within one of the GuiEventListener methods using IForgeKeyMapping#isActiveAndMatches. The most common methods which can be checked are #keyPressed and #mouseClicked.
#keyPressed takes in the GLFW key token, the platform-specific scan code, and a bitfield of the held down modifiers. A key can be checked against a mapping by creating the input using InputConstants#getKey. The modifiers are already checked within the mapping methods itself.
// In some Screen subclass
@Override
public boolean keyPressed(int key, int scancode, int mods) {
if (EXAMPLE_MAPPING.get().isActiveAndMatches(InputConstants.getKey(key, scancode))) {
// Execute logic to perform on key press here
return true;
}
return super.keyPressed(x, y, button);
}
Note
If you do not own the screen which you are trying to check a key for, you can listen to the Pre or Post events of ScreenEvent$KeyPressed on the Forge event bus instead.
#mouseClicked takes in the mouse’s x position, y position, and the button clicked. A mouse button can be checked against a mapping by creating the input using InputConstants$Type#getOrCreate with the MOUSE input.
// In some Screen subclass
@Override
public boolean mouseClicked(double x, double y, int button) {
if (EXAMPLE_MAPPING.get().isActiveAndMatches(InputConstants.TYPE.MOUSE.getOrCreate(button))) {
// Execute logic to perform on mouse click here
return true;
}
return super.mouseClicked(x, y, button);
}
Note
If you do not own the screen which you are trying to check a mouse for, you can listen to the Pre or Post events of ScreenEvent$MouseButtonPressed on the Forge event bus instead.
Misc: Game Tests
Game Tests
Game Tests are a way to run in-game unit tests. The system was designed to be scalable and in parallel to run large numbers of different tests efficiently. Testing object interactions and behaviors are simply a few of the many applications of this framework.
Creating a Game Test
A standard Game Test follows three basic steps:
- A structure, or template, is loaded holding the scene on which the interaction or behavior is tested.
- A method conducts the logic to perform on the scene.
- The method logic executes. If a successful state is reached, then the test succeeds. Otherwise, the test fails and the result is stored within a lectern adjacent to the scene.
As such, to create a Game Test, there must be an existing template holding the initial start state of the scene and a method which provides the logic of execution.
The Test Method
A Game Test method is a Consumer<GameTestHelper> reference, meaning it takes in a GameTestHelper and returns nothing. For a Game Test method to be recognized, it must have a @GameTest annotation:
public class ExampleGameTests {
@GameTest
public static void exampleTest(GameTestHelper helper) {
// Do stuff
}
}
The @GameTest annotation also contains members which configure how the game test should run.
// In some class
@GameTest(
setupTicks = 20L, // The test spends 20 ticks to set up for execution
required = false // The failure is logged but does not affect the execution of the batch
)
public static void exampleConfiguredTest(GameTestHelper helper) {
// Do stuff
}
Relative Positioning
All GameTestHelper methods translate relative coordinates within the structure template scene to its absolute coordinates using the structure block’s current location. To allow for easy conversion between relative and absolute positioning, GameTestHelper#absolutePos and GameTestHelper#relativePos can be used respectively.
The relative position of a structure template can be obtained in-game by loading the structure via the test command, placing the player at the wanted location, and finally running the /test pos command. This will grab the coordinates of the player relative to the closest structure within 200 blocks of the player. The command will export the relative position as a copyable text component in the chat to be used as a final local variable.
Tip
The local variable generated by /test pos can specify its reference name by appending it to the end of the command:
/test pos <var> # Exports 'final BlockPos <var> = new BlockPos(...);'
Successful Completion
A Game Test method is responsible for one thing: marking the test was successful on a valid completion. If no success state was achieved before the timeout is reached (as defined by GameTest#timeoutTicks), then the test automatically fails.
There are many abstracted methods within GameTestHelper which can be used to define a successful state; however, four are extremely important to be aware of.
| Method | Description |
|---|---|
#succeed |
The test is marked as successful. |
#succeedIf |
The supplied Runnable is tested immediately and succeeds if no GameTestAssertException is thrown. If the test does not succeed on the immediate tick, then it is marked as a failure. |
#succeedWhen |
The supplied Runnable is tested every tick until timeout and succeeds if the check on one of the ticks does not throw a GameTestAssertException. |
#succeedOnTickWhen |
The supplied Runnable is tested on the specified tick and will succeed if no GameTestAssertException is thrown. If the Runnable succeeds on any other tick, then it is marked as a failure. |
Important
Game Tests are executed every tick until the test is marked as a success. As such, methods which schedule success on a given tick must be careful to always fail on any previous tick.
Scheduling Actions
Not all actions will occur when a test begins. Actions can be scheduled to occur at specific times or intervals:
| Method | Description |
|---|---|
#runAtTickTime |
The action is ran on the specified tick. |
#runAfterDelay |
The action is ran x ticks after the current tick. |
#onEachTick |
The action is ran every tick. |
Assertions
At any time during a Game Test, an assertion can be made to check if a given condition is true. There are numerous assertion methods within GameTestHelper; however, it simplifies to throwing a GameTestAssertException whenever the appropriate state is not met.
Generated Test Methods
If Game Test methods need to be generated dynamically, a test method generator can be created. These methods take in no parameters and return a collection of TestFunctions. For a test method generator to be recognized, it must have a @GameTestGenerator annotation:
public class ExampleGameTests {
@GameTestGenerator
public static Collection<TestFunction> exampleTests() {
// Return a collection of TestFunctions
}
}
TestFunction
A TestFunction is the boxed information held by the @GameTest annotation and the method running the test.
Tip
Any methods annotated using @GameTest are translated into a TestFunction using GameTestRegistry#turnMethodIntoTestFunction. That method can be used as a reference for creating TestFunctions without the use of the annotation.
Batching
Game Tests can be executed in batches instead of registration order. A test can be added to a batch by having the same supplied GameTest#batch string.
On its own, batching does not provide anything useful. However, batching can be used to perform setup and teardown states on the current level the tests are running in. This is done by annotating a method with either @BeforeBatch for setup or @AfterBatch for takedown. The #batch methods must match the string supplied to the game test.
Batch methods are Consumer<ServerLevel> references, meaning they take in a ServerLevel and return nothing:
public class ExampleGameTests {
@BeforeBatch(batch = "firstBatch")
public static void beforeTest(ServerLevel level) {
// Perform setup
}
@GameTest(batch = "firstBatch")
public static void exampleTest2(GameTestHelper helper) {
// Do stuff
}
}
Registering a Game Test
A Game Test must be registered to be ran in-game. There are two methods of doing so: via the @GameTestHolder annotation or RegisterGameTestsEvent. Both registration methods still require the test methods to be annotated with either @GameTest, @GameTestGenerator, @BeforeBatch, or @AfterBatch.
GameTestHolder
The @GameTestHolder annotation registers any test methods within the type (class, interface, enum, or record). @GameTestHolder contains a single method which has multiple uses. In this instance, the supplied #value must be the mod id of the mod; otherwise, the test will not run under default configurations.
@GameTestHolder(MODID)
public class ExampleGameTests {
// ...
}
RegisterGameTestsEvent
RegisterGameTestsEvent can also register either classes or methods using #register. The event listener must be added to the mod event bus. Test methods registered this way must supply their mod id to GameTest#templateNamespace on every method annotated with @GameTest.
// In some class
public void registerTests(RegisterGameTestsEvent event) {
event.register(ExampleGameTests.class);
}
// In ExampleGameTests
@GameTest(templateNamespace = MODID)
public static void exampleTest3(GameTestHelper helper) {
// Perform setup
}
Note
The value supplied to GameTestHolder#value and GameTest#templateNamespace can be different from the current mod id. The configuration within the buildscript would need to be changed.
Structure Templates
Game Tests are performed within scenes loaded by structures, or templates. All templates define the dimensions of the scene and the initial data (blocks and entities) that will be loaded. The template must be stored as an .nbt file within data/<namespace>/structures.
Tip
A structure template can be created and saved using a structure block.
The location of the template is specified by a few factors:
- If the namespace of the template is specified.
- If the class should be prepended to the name of the template.
- If the name of the template is specified.
The namespace of the template is determined by GameTest#templateNamespace, then GameTestHolder#value if not specified, then minecraft if neither is specified.
The simple class name is not prepended to the name of the template if the @PrefixGameTestTemplate is applied to a class or method with the test annotations and set to false. Otherwise, the simple class name is made lowercase and prepended and followed by a dot before the template name.
The name of the template is determined by GameTest#template. If not specified, then the lowercase name of the method is used instead.
// Modid for all structures will be MODID
@GameTestHolder(MODID)
public class ExampleGameTests {
// Class name is prepended, template name is not specified
// Template Location at 'modid:examplegametests.exampletest'
@GameTest
public static void exampleTest(GameTestHelper helper) { /*...*/ }
// Class name is not prepended, template name is not specified
// Template Location at 'modid:exampletest2'
@PrefixGameTestTemplate(false)
@GameTest
public static void exampleTest2(GameTestHelper helper) { /*...*/ }
// Class name is prepended, template name is specified
// Template Location at 'modid:examplegametests.test_template'
@GameTest(template = "test_template")
public static void exampleTest3(GameTestHelper helper) { /*...*/ }
// Class name is not prepended, template name is specified
// Template Location at 'modid:test_template2'
@PrefixGameTestTemplate(false)
@GameTest(template = "test_template2")
public static void exampleTest4(GameTestHelper helper) { /*...*/ }
}
Running Game Tests
Game Tests can be run using the /test command. The test command is highly configurable; however, only a few are of importance to running tests:
| Subcommand | Description |
|---|---|
run |
Runs the specified test: run <test_name>. |
runall |
Runs all available tests. |
runthis |
Runs the nearest test to the player within 15 blocks. |
runthese |
Runs tests within 200 blocks of the player. |
runfailed |
Runs all tests that failed in the previous run. |
Note
Subcommands follow the test command: /test <subcommand>.
Buildscript Configurations
Game Tests provide additional configuration settings within a buildscript (the build.gradle file) to run and integrate into different settings.
Enabling Other Namespaces
If the buildscript was setup as recommended, then only Game Tests under the current mod id would be enabled. To enable other namespaces to load Game Tests from, a run configuration must set the property forge.enabledGameTestNamespaces to a string specifying each namespace separated by a comma. If the property is empty or not set, then all namespaces will be loaded.
// Inside a run configuration
property 'forge.enabledGameTestNamespaces', 'modid1,modid2,modid3'
Warning
There must be no spaces in-between namespaces; otherwise, the namespace will not be loaded correctly.
Game Test Server Run Configuration
The Game Test Server is a special configuration which runs a build server. The build server returns an exit code of the number of required, failed Game Tests. All failed tests, whether required or optional, are logged. This server can be run using gradlew runGameTestServer.
Enabling Game Tests in Other Run Configurations
By default, only the client, server, and gameTestServer run configurations have Game Tests enabled. If another run configuration should run Game Tests, then the forge.enableGameTest property must be set to true.
// Inside a run configuration
property 'forge.enableGameTest', 'true'
Misc: Forge Update Checker
Forge Update Checker
Forge provides a very lightweight, opt-in, update-checking framework. If any mods have an available update, it will show a flashing icon on the ‘Mods’ button of the main menu and mod list along with the respective changelogs. It does not download updates automatically.
Getting Started
The first thing you want to do is specify the updateJSONURL parameter in your mods.toml file. The value of this parameter should be a valid URL pointing to an update JSON file. This file can be hosted on your own web server, GitHub, or wherever you want as long as it can be reliably reached by all users of your mod.
Update JSON format
The JSON itself has a relatively simple format as follows:
{
"homepage": "<homepage/download page for your mod>",
"<mcversion>": {
"<modversion>": "<changelog for this version>",
// List all versions of your mod for the given Minecraft version, along with their changelogs
// ...
},
"promos": {
"<mcversion>-latest": "<modversion>",
// Declare the latest "bleeding-edge" version of your mod for the given Minecraft version
"<mcversion>-recommended": "<modversion>",
// Declare the latest "stable" version of your mod for the given Minecraft version
// ...
}
}
This is fairly self-explanatory, but some notes:
- The link under
homepageis the link the user will be shown when the mod is outdated. - Forge uses an internal algorithm to determine whether one version string of your mod is “newer” than another. Most versioning schemes should be compatible, but see the
ComparableVersionclass if you are concerned about whether your scheme is supported. Adherence to Maven versioning is highly recommended. - The changelog string can be separated into lines using
\n. Some prefer to include a abbreviated changelog, then link to an external site that provides a full listing of changes. - Manually inputting data can be chore. You can configure your
build.gradleto automatically update this file when building a release as Groovy has native JSON parsing support. Doing this is left as an exercise to the reader. - Some examples can be found here for nocubes, Forge and Corail Tombstone.
Retrieving Update Check Results
You can retrieve the results of the Forge Update Checker using VersionChecker#getResult(IModInfo). You can obtain your IModInfo via ModContainer#getModInfo. You can get your ModContainer using ModLoadingContext.get().getActiveContainer() inside your constructor, ModList.get().getModContainerById(<your modId>), or ModList.get().getModContainerByObject(<your mod instance>). You can obtain any other mod’s ModContainer using ModList.get().getModContainerById(<modId>). The returned object has a method #status which indicates the status of the version check.
| Status | Description |
|---|---|
FAILED |
The version checker could not connect to the URL provided. |
UP_TO_DATE |
The current version is equal to the recommended version. |
AHEAD |
The current version is newer than the recommended version if there is not latest version. |
OUTDATED |
There is a new recommended or latest version. |
BETA_OUTDATED |
There is a new latest version. |
BETA |
The current version is equal to or newer than the latest version. |
PENDING |
The result requested has not finished yet, so you should try again in a little bit. |
The returned object will also have the target version and any changelog lines as specified in update.json.
Misc: Debug Profiler
Debug Profiler
Minecraft provides a Debug Profiler that provides system data, current game settings, JVM data, level data, and sided tick information to find time consuming code. Considering things like TickEvents and ticking BlockEntities, this can be very useful for modders and server owners that want to find a lag source.
Using the Debug Profiler
The Debug Profiler is very simple to use. It requires the debug keybind F3 + L to start the profiler. After 10 seconds, it will automatically stop; however, it can be stopped earlier by pressing the keybind again.
Note
Naturally, you can only profile code paths that are actually being reached. Entities and BlockEntities that you want to profile must exist in the level to show up in the results.
After you have stopped the debugger, it will create a new zip within the debug/profiling subdirectory in your run directory.
The file name will be formatted with the date and time as yyyy-mm-dd_hh_mi_ss-WorldName-VersionNumber.zip
Reading a Profiling result
Within each sided folder (client and server), you will find a profiling.txt file containing the result data. At the top, it first tells you how long in milliseconds it was running and how many ticks ran in that time.
Below that, you will find information similar to the snippet below:
[00] levels - 96.70%/96.70%
[01] | Level Name - 99.76%/96.47%
[02] | | tick - 99.31%/95.81%
[03] | | | entities - 47.72%/45.72%
[04] | | | | regular - 98.32%/44.95%
[04] | | | | blockEntities - 0.90%/0.41%
[05] | | | | | unspecified - 64.26%/0.26%
[05] | | | | | minecraft:furnace - 33.35%/0.14%
[05] | | | | | minecraft:chest - 2.39%/0.01%
Here is a small explanation of what each part means
| [02] | tick | 99.31% | 95.81% |
|---|---|---|---|
| The Depth of the section | The Name of the Section | The percentage of time it took in relation to it’s parent. For Layer 0, it is the percentage of the time a tick takes. For Layer 1, it is the percentage of the time its parent takes. | The second percentage tells you how much time it took from the entire tick. |
Profiling your own code
The Debug Profiler has basic support for Entity and BlockEntity. If you would like to profile something else, you may need to manually create your sections like so:
ProfilerFiller#push(yourSectionName : String);
//The code you want to profile
ProfilerFiller#pop();
You can obtain the ProfilerFiller instance from a Level, MinecraftServer, or Minecraft instance.
Now you just need to search the results file for your section name.
Advanced: Access Transformers
Access Transformers
Access Transformers (ATs for short) allow for widening the visibility and modifying the final flags of classes, methods, and fields. They allow modders to access and modify otherwise inaccessible members in classes outside their control.
The specification document can be viewed on the Minecraft Forge GitHub.
Adding ATs
Adding an Access Transformer to your mod project is as simple as adding a single line into your build.gradle:
// This block is where your mappings version is also specified
minecraft {
accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg')
}
After adding or modifying the Access Transformer, the gradle project must be refreshed for the transformations to take effect.
During development, the AT file can be anywhere specified by the line above. However, when loading in a non-development environment, Forge will only search for the exact path of META-INF/accesstransformer.cfg in your JAR file.
Comments
All text after a # until the end of the line will be treated as a comment and will not be parsed.
Access Modifiers
Access modifiers specify to what new member visibility the given target will be transformed to. In decreasing order of visibility:
public- visible to all classes inside and outside its packageprotected- visible only to classes inside the package and subclassesdefault- visible only to classes inside the packageprivate- visible only to inside the class
A special modifier +f and -f can be appended to the aforementioned modifiers to either add or remove respectively the final modifier, which prevents subclassing, method overriding, or field modification when applied.
Warning
Directives only modify the method they directly reference; any overriding methods will not be access-transformed. It is advised to ensure transformed methods do not have non-transformed overrides that restrict the visibility, which will result in the JVM throwing an error.
Examples of methods that can be safely transformed are private methods, final methods (or methods in final classes), and static methods.
Targets and Directives
Important
When using Access Transformers on Minecraft classes, the SRG name must be used for fields and methods.
Classes
To target classes:
<access modifier> <fully qualified class name>
Inner classes are denoted by combining the fully qualified name of the outer class and the name of the inner class with a $ as separator.
Fields
To target fields:
<access modifier> <fully qualified class name> <field name>
Methods
Targeting methods require a special syntax to denote the method parameters and return type:
<access modifier> <fully qualified class name> <method name>(<parameter types>)<return type>
Specifying Types
Also called “descriptors”: see the Java Virtual Machine Specification, SE 8, sections 4.3.2 and 4.3.3 for more technical details.
B-byte, a signed byteC-char, a Unicode character code point in UTF-16D-double, a double-precision floating-point valueF-float, a single-precision floating-point valueI-integer, a 32-bit integerJ-long, a 64-bit integerS-short, a signed shortZ-boolean, atrueorfalsevalue[- references one dimension of an array- Example:
[[Srefers toshort[][] L<class name>;- references a reference type- Example:
Ljava/lang/String;refers tojava.lang.Stringreference type (note the use of slashes instead of periods) (- references a method descriptor, parameters should be supplied here or nothing if no parameters are present- Example:
<method>(I)Zrefers to a method that requires an integer argument and returns a boolean V- indicates a method returns no value, can only be used at the end of a method descriptor- Example:
<method>()Vrefers to a method that has no arguments and returns nothing
Examples
# Makes public the ByteArrayToKeyFunction interface in Crypt
public net.minecraft.util.Crypt$ByteArrayToKeyFunction
# Makes protected and removes the final modifier from 'random' in MinecraftServer
protected-f net.minecraft.server.MinecraftServer f_129758_ #random
# Makes public the 'makeExecutor' method in Util,
# accepting a String and returns an ExecutorService
public net.minecraft.Util m_137477_(Ljava/lang/String;)Ljava/util/concurrent/ExecutorService; #makeExecutor
# Makes public the 'leastMostToIntArray' method in UUIDUtil,
# accepting two longs and returning an int[]
public net.minecraft.core.UUIDUtil m_235872_(JJ)[I #leastMostToIntArray
Forge Dev: Introduction
Getting Started
If you have decided to contribute to Forge, you will have to take some special steps to get started with developing. A simple mod development environment will not suffice to work with Forge’s codebase directly. Instead, you can use the following guide to help you with your setup and get you started with improving Forge!
Forking and Cloning the Repository
Like most major open source projects you will find, Forge is hosted on GitHub. If you have contributed to another project before, you will know this process already and can skip right ahead to the next section.
For those who are beginners when it comes to collaboration via Git, here are two easy steps to get you started.
Note
This guide assumes that you already have a GitHub account set up. If you do not, visit their registration page to create an account. Furthermore, this guide is not a tutorial for git’s usage. Please consult different sources first if you are struggling to get it working.
Forking
First of all, you have to “fork” the MinecraftForge repository by clicking the “Fork” button in the upper right hand corner. If you are in an organization, select the account you want your fork to be hosted on.
Forking the repository is necessary since not every GitHub user can have free access to every repository. Instead, you create a copy of the original repository to later contribute your changes via a so called Pull Request, which you will learn more about later.
Cloning
After forking the repository, it is time to get local access to actually make some changes. For this, you need to clone the repository onto your local machine.
Using your favorite git client, simply clone your fork into a directory of your choice. As general example, here is a command line snippet that should work on all correctly configured systems and clones the repository into a directory called “MinecraftForge” under the current directory (note that you have to replace <User> with your username):
git clone
Checking out the Correct Branch
Forking and cloning the repository are the only mandatory steps to develop for Forge. However, to ease the process of creating Pull Requests for you, it is best to work with branches.
It is recommended to create and check out a branch for each PR you plan to submit. This way, you can always keep around the latest changes of Forge for new PRs while you still work on older patches.
After completing this step, you are ready to go and set up your development environment.
Setting Up the Environment
Depending on your favorite IDE, there is a different set of recommended steps you have to follow to successfully set up a development environment.
Eclipse
Due to the way Eclipse workspaces work, ForgeGradle can do most of the work involved to get you started with a Forge workspace.
- Open a terminal/command prompt and navigate to the directory of your cloned fork.
- Type
./gradlew setupand hit enter. Wait until ForgeGradle is done. - Type
./gradlew genEclipseRunsand hit enter. Once again, wait until ForgeGradle is done. - Open your Eclipse workspace and go to
File -> Import -> General -> Existing Gradle Project. - Browse to the repo directory for the “Project root directory” option in the dialog that opens.
- Complete the import by clicking the “Finish” button.
That is all it takes to get you up and running with Eclipse. There is no extra steps required to get the test mods running. Simply hit “Run” like in any other project and select the appropriate run configuration.
IntelliJ IDEA
JetBrains’ flagship IDE comes with great integrated support for Gradle: Forge’s build system of choice. Due to some peculiarities of Minecraft mod development, however, there are additional steps required to get everything to work properly.
IDEA 2021 onwards
- Start IntelliJ IDEA 2021. - If you already have another project open, close the project with the File -> Close project option.
- In the projects tab of the “Welcome to IntelliJ IDEA” window, click the “Open” button on the top right and select the MinecraftForge folder you cloned earlier.
- Click “Trust Project” if prompted.
- After IDEA is done importing the project and indexing its files, run the Gradle setup task. You can do this by:
- Open the Gradle sidebar on the right hand side of your screen, then open the forge project tree, select Tasks, then other and double-click the
setuptask (may also appear asMinecraftForge[Setup]) found in Forge -> Tasks -> other ->setup. - Generate the run configurations:
- Open the Gradle sidebar on the right hand side of your screen, then open the forge project tree, select Tasks, then other and double-click the
genIntellijRunstask (may also appear asMinecraftForge[genIntellijRuns]) found in Forge -> Tasks -> forgegradle runs ->genIntellijRuns. - If you get a licensing error during build before making any changes, running theupdateLicensestask may help. This task is found in Forge -> Tasks -> other as well.
IDEA 2019-2020
There are a few minor differences between IDEA 2021 and these versions for setup.
- Import Forge’s
build.gradleas an IDEA project. For this, simply clickImport Projectfrom theWelcome to IntelliJ IDEAsplash screen, then select thebuild.gradlefile. - After IDEA is done importing the project and indexing the files, run the Gradle setup task. Either:
- Open the Gradle sidebar on the right hand side of your screen, then open the
forgeproject tree, selectTasks, thenotherand double-click thesetuptask (may also appear asMinecraftForge[Setup]. Or alternatively: - Tap the CTRL key twice, and type
gradle setupin theRuncommand window that pops up.
You can then run Forge using the forge_client gradle task (Tasks -> fg_runs -> forge_client): right-click the task and select either Run or Debug as desired.
You should now be able to work with your mod using the changes you introduce to the Forge and Vanilla codebase.
Making Changes and Pull Requests
Once you have set up your development environment, it is time to make some changes to Forge’s codebase. There are, however, some pitfalls you have to avoid when editing the project’s code.
The most important thing to note is that if you wish to edit Minecraft source code, you must only do so in the “Forge” sub-project. Any changes in the “Clean” project will mess with ForgeGradle and generating the patches. This can have disastrous consequences and might render your environment completely useless. If you wish to have a flawless experience, make sure you only edit code in the “Forge” project!
Generating Patches
After you have made changes to the code base and tested them thoroughly, you may go ahead and generate patches. This is only necessary if you work on the Minecraft code base (i.e. in the “Forge” project), but this step is vital for your changes to work elsewhere. Forge works by injecting only changed things into Vanilla Minecraft and hence needs those changes available in an appropriate format. Thankfully, ForgeGradle is capable of generating the changeset for you to commit it.
To initiate the patch generation, simply run the genPatches Gradle task from your IDE or the command line. After its completion, you can commit all your changes (make sure you do not add any unnecessary files) and submit your Pull Request!
Pull Requests
The last step before your contribution is added to Forge is a Pull Request (PR in short). This is a formal request to incorporate your fork’s changes into the live code base. Creating a PR is easy. Simply go to this GitHub page and follow the proposed steps. It is now that a good setup with branches pays off, since you are able to select precisely the changes you want to submit.
Note
Pull Requests are bound to rules; not every request will blindly be accepted. Follow this document to get further information and to ensure the best quality of your PR! If you want to maximize the chances of your PR getting accepted, follow these PR guidelines!
Forge Dev: Pull Request Guidelines
Pull Request Guidelines
Mods are built on top of Forge, but there are some things that Forge does not support, and that limits what mods can do.
When modders run into something like that, they can make a change to Forge to support it, and submit that change as a Pull Request on Github.
To make the best use of both your and the Forge team’s time, it is recommended to follow some rough guidelines when preparing a Pull Request. The following points are the most important aspects to keep in mind when it comes to writing a good Pull Request.
What Exactly is Forge?
At a high level, Forge is a mod compatibility layer on top of Minecraft.
Early mods edited Minecraft’s code directly (like coremods do now), but they ran into conflicts with each other when they edited the same things. They also ran into issues when one mod changed behavior in ways that the other mods could not anticipate (like coremods do now), causing mysterious issues and lots of headaches.
By using something like Forge, mods can centralize common changes and avoid conflicts.
Forge also includes supporting structures for common mod features like Capabilities, Registries, and others that allow mods to work together better.
When writing a good Forge Pull Request, you also have to know what Forge is at a lower level.
There are two main types of code in Forge: Minecraft patches, and Forge code.
Patches
Patches are applied as direct changes to Minecraft’s source code, and aim to be as minimal as possible.
Every time Minecraft code changes, all the Forge patches need to be looked over carefully and applied correctly to the new code.
This means that large patches that change lots of things are difficult to maintain, so Forge aims to avoid those and keep patches as small as possible.
In addition to making sure the code makes sense, reviews for patches will focus on minimizing the size.
There are many strategies to make small patches, and reviews will often point out better methods to do things.
Forge patches often insert a single line that fires an event or a code hook, which affects the code after it if the event meets some condition.
This allows most of the code to exist outside of the patch, which keeps the patch small and simple.
For more detailed information about creating patches, see the GitHub wiki.
Forge Code
Aside from the patches, Forge code is just normal Java code. It can be event code, compatibility features, or anything else that is not directly editing Minecraft code. When Minecraft updates, Forge code has to update just like everything else. However, it is much easier because it is not directly entangled in the Minecraft code.
Because this code stands on its own, there is no size restriction like there is with the patches.
In addition to making sure the code makes sense, reviews will focus on making the code clean: with proper formatting and Java documentation.
Explain Yourself
All Pull Requests need to answer the question: why is this necessary?
Any code added to Forge needs to be maintained, and more code means more potential for bugs, so solid justification is needed for adding code.
A common Pull Request issue is offering no explanation, or giving cryptic examples for how the Pull Request might theoretically be used.
This only delays the Pull Request process.
A clear explanation for the general case is good, but also give a concrete example of how your mod needs this Pull Request.
Sometimes there is better way to do what you wanted, or a way to do it without a Pull Request at all. Code changes can not be accepted until those possibilities have been completely ruled out.
Show that it Works
The code you submit to Forge should work perfectly, and it is up to you to convince the reviewers that it does.
One of the best ways to do that is to add an example mod or JUnit test to Forge that makes use of your new code and shows it working.
To set up and run a Forge Environment with the example mods, see this guide.
Breaking Changes in Forge
Forge cannot make changes that break the mods that depend on it.
This means that Pull Requests have to ensure that they do not break binary compatibility with previous Forge versions.
A change that breaks binary compatibility is called a Breaking Change.
There are some exceptions to this:
- Forge accepts Breaking Changes at the beginning of new Minecraft versions, where Minecraft itself already causes Breaking Changes for modders.
- Sometimes an emergency breaking change is required outside of that time window, but it is rare and can cause dependency headaches for everyone in the modded Minecraft community.
Outside of those exceptional times, Pull Requests with breaking changes are not accepted. They must be adapted to support the old behavior or wait for the next Minecraft version.
Be Patient, Civil, and Empathetic
When submitting Pull Requests, you will often have to survive code review and make several changes before it is the best Pull Request possible.
Keep in mind that code review is not judgement against you. Bugs in your code are not personal. Nobody is perfect, and that is why we are working together.
Negativity will not help. Threatening to give up on your Pull Request and write a coremod instead will just make people upset and make the modded ecosystem worse.
It is important that while working together you assume the best intentions of the people who are reviewing your Pull Request and not take things personally.
Review
If you do your best to understand the slow and perfectionistic nature of the Pull Request process, we will do our best to understand your point of view as well.
After your Pull Request has been reviewed and cleaned up to the best of everyone’s ability, it will be marked for a final review by Lex, who has the final say on what is included in the project or not.
Legacy Versions: Introduction
Documentation for Legacy Versions
Forge has existed for years, and you can still easily access builds of Forge for Minecraft versions as old as Minecraft 1.1. There are significant differences between each and every version, and it would be an impossible task to support so many different versions. Therefore, Forge uses an LTS system where a previous major Minecraft version is deemed as “LTS” (Long Term Support). Only the latest version and any current LTS versions will have easily accessible documentation and be included in the version dropdown in the sidebar. However, some older versions were LTS once or the latest version at some point and had documentation written. Links to old sites with documentation for those versions can be found here.
Important
These old documentation sites are for reference purposes only. Do not ask for help with old versions on the Forge discord or the Forge forums. You will not receive support when you are using older versions.
List of Previously Documented versions
Unfortunately, not all versions were used for a significant amount of time, and the documentation for that version may be incomplete. Whenever a new version is released, the documentation from the previous version is copied and adjusted over time to include new and updated information. When a version wasn’t supported for long, the information was never updated. The accuracy percentages represent how much of the information that should have been updated was actually updated.
| Version | Accuracy | Link |
|---|---|---|
| 1.12.x | 100% | |
| 1.13.x | 10% | |
| 1.14.x | 10% | |
| 1.15.x | 85% | |
| 1.16.x | 85% | |
| 1.17.x | 85% | |
| 1.18.x | 90% | |
| 1.19.2 | 90% | |
| 1.19.x | 90% | |
| 1.20.1 | 90% |
RetroGradle
RetroGradle is an archival initiative to update the older ForgeGradle 1.x to 2.3 toolchains and their Minecraft versions to use the modern ForgeGradle 4.x and above toolchain. The goal is to preserve all past released versions of Minecraft Forge by moving them to a verifiably working and modern toolchain which is data-driven and not hardcoded for version-specific workflows.
If any developer wishes to contribute to this archival effort, please visit The Forge Project discord server and ask for directions to the designated channel. Please note that this initiative only aims to preserve these old versions for the benefit of the community, not to support developing mods for these old, unsupported versions. There will not be any support for using or developing for unsupported versions.
Legacy Versions: Porting
Porting to Minecraft 1.21
Here you can find a list of primers on how to port from old versions to the current version. Some versions are lumped together since that particular version never saw much usage.
| From -> To | Primer |
|---|---|
| 1.12 -> 1.13/1.14 | Primer by williewillus |
| 1.14 -> 1.15 | Primer by williewillus |
| 1.15 -> 1.16 | Primer by 50ap5ud5 |
| 1.16 -> 1.17 | Primer by 50ap5ud5 |
| 1.19.2 -> 1.19.3 | Primer by ChampionAsh5357 |
| 1.19.3 -> 1.19.4 | Primer by ChampionAsh5357 |
| 1.19.4 -> 1.20 | Primer by ChampionAsh5357 |
| 1.20.4 -> 1.20.5/6 | Primer by ChampionAsh5357 |
| 1.20.6 -> 1.21 | Primer by ChampionAsh5357 |
Meta: Contributing to the Docs
Contributing to This Documentation
You can make a contribution via a PR on GitHub.
This documentation is meant to be explanatory. Please explain how to do things, and break it down into reasonable chunks. We have a wiki elsewhere that can capture more comprehensive code examples.
Our audience is anyone who wants to understand how to build a mod using Forge.
Please don’t try to turn this documentation into a tutorial on Java Development - it is intended for people who understand how a Java class works, and other fundamental structures of Java.
Style Guide
Important
Please use two spaces to indent, not tabs.
Titles should be capitalized in the standard titling format. For example,
- Guide For Contributing to This Documentation
- Building and Testing Your Mod
Essentially, capitalize everything but unimportant words.
Spelling, grammar, and syntax should follow those of American English. Also, prefer using separate words over contractions (e.g. “are not” instead of “aren’t”).
Please use equals and dash underlines, instead of # and ##. For h3 and lower, ### etc. is fine. The source of this file contains an example for equals and dash underlining. Equals underlines create h1 text, and dash underlines create h2 text.
When referencing fields and methods outside of code block snippets, they should use a # separator (e.g. ClassName#methodName). Inner classes should use a $ separator (e.g. ClassName$InnerClassName).
JSON code block snippets should use js syntax highlighting.
All links should have their location specified at the bottom of the page. Any internal links should reference the page via their relative path.
Admonitions (represented by !!! <type>) must be formatted as documented; otherwise they may end up rendering incorrectly.