This is a high level, non-exhaustive overview on how to migrate your mod from 1.19.2 to 1.19.3 using Forge.
This primer is licensed under the Creative Commons Attribution 4.0 International, so feel free to use it as a reference and leave a link so that other readers can consume the primer.
If there's any incorrect or missing information, please leave a comment below. Thanks!
Feature Flags are a behavior/logic toggle for blocks and items. They determine whether certain actions or generations related to the object can trigger (e.g. bamboo boats can only be placed when the minecraft:update_1_20
flag is enabled).
Vanilla currently provides three flags as of this version (via FeatureFlags
):
minecraft:vanilla
: For everything in vanilla minecraftminecraft:bundle
: For the bundle feature pack and in the creative tabminecraft:update_1_20
: For objects related to the upcoming 1.20 release
You can force an item or block to require one of these features by adding it as a property via #requiredFeatures
. This will affect all behavior and logic associated with the object.
// In some supplied instance
new Block(BlockBehaviour.Properties.of(/*...*/).requiredFeatures(/*features here*/));
new Item(new Item.Properties().requiredFeatures(/*features here*/));
If you only want to affect some behavior or logic associated with the object, then you will need to check FeatureFlagSet#contains
yourself on whatever system you are using.
Do not add your own
FeatureFlag
s. Currently, they are limited to only 6 and are hardcoded to vanilla's highly-specific implementation.
Registries have received a significant overhaul, resulting in better stability.
Dynamic registries such as dimension types or features can no longer be declared in-code. Instead, a JSON of each must now be provided.
The locations of the registry keys and the registries themselves have moved. Registry keys are now in net.minecraft.core.registries.Registries
. Static registries are now in net.minecraft.core.registries.BuiltInRegistries
.
Dynamic registries can only be obtained from the RegistryAccess
from the MinecraftServer
. There is no more static access to it, such as using RegistryAccess#builtinCopy
.
The registry order has changed as well due to a bunch of stability fixes. This should not matter in most cases if you want to support registry replacement as a Supplier
of the value is necessary, but this will be noted down regardless. This is the current order in which registries load:
minecraft:sound_event
minecraft:fluid
minecraft:block
minecraft:attribute
minecraft:mob_effect
minecraft:particle_type
minecraft:item
minecraft:entity_type
minecraft:sensor_type
minecraft:memory_module_type
minecraft:potion
minecraft:game_event
minecraft:enchantment
minecraft:block_entity_type
minecraft:painting_variant
minecraft:stat_type
minecraft:custom_stat
minecraft:chunk_status
minecraft:rule_test
minecraft:pos_rule_test
minecraft:menu
minecraft:recipe_type
minecraft:recipe_serializer
minecraft:position_source_type
minecraft:command_argument_type
minecraft:villager_type
minecraft:villager_profession
minecraft:point_of_interest_type
minecraft:schedule
minecraft:activity
minecraft:loot_pool_entry_type
minecraft:loot_function_type
minecraft:loot_condition_type
minecraft:loot_number_provider_type
minecraft:loot_nbt_provider_type
minecraft:loot_score_provider_type
minecraft:float_provider_type
minecraft:int_provider_type
minecraft:height_provider_type
minecraft:block_predicate_type
minecraft:worldgen/carver
minecraft:worldgen/feature
minecraft:worldgen/structure_processor
minecraft:worldgen/structure_placement
minecraft:worldgen/structure_piece
minecraft:worldgen/structure_type
minecraft:worldgen/placement_modifier_type
minecraft:worldgen/block_state_provider_type
minecraft:worldgen/foliage_placer_type
minecraft:worldgen/trunk_placer_type
minecraft:worldgen/root_placer_type
minecraft:worldgen/tree_decorator_type
minecraft:worldgen/feature_size_type
minecraft:worldgen/biome_source
minecraft:worldgen/chunk_generator
minecraft:worldgen/material_condition
minecraft:worldgen/material_rule
minecraft:worldgen/density_function_type
minecraft:worldgen/structure_pool_element
minecraft:cat_variant
minecraft:frog_variant
minecraft:banner_pattern
minecraft:instrument
forge:biome_modifier_serializers
forge:entity_data_serializers
forge:fluid_type
forge:global_loot_modifier_serializers
forge:holder_set_type
forge:structure_modifier_serializers
minecraft:worldgen/biome
CreativeModeTab
s are no longer set through a property on the item; they are harcoded onto the creative tab itself. To get around this, the CreativeModeTabEvent
was added, allowing a modder to create a creative tab or add items to an existing creative tab. All events are on the mod event bus.
Items can be added to a creative tab via CreativeModeTabEvent$BuildContents
. The event contains the tab to add contents to, the set feature flags, and whether the user as OP permissions. An item can be added to the creative tab by calling #accept
or #acceptAll
. If you want to inject between an item already in the creative tab, you can call MutableHashedLinkedMap#putBefore
or MutableHashedLinkedMap#putAfter
within the provided entry list (#getEntries
).
// Registered on the MOD event bus
// Assume we have RegistryObject<Item> and RegistryObject<Block> called ITEM and BLOCK
@SubscribeEvent
public void buildContents(CreativeModeTabEvent.BuildContents event) {
// Add to ingredients tab
if (event.getTab() == CreativeModeTabs.INGREDIENTS) {
event.accept(ITEM);
event.accept(BLOCK); // Takes in an ItemLike, assumes block has registered item
}
}
A custom CreativeModeTab
can be created via CreativeModeTabEvent$Register#registerCreativeModeTab
. This takes in the name of the tab along with a consumer of the builder. An additional overload is specified to determine between which tabs this tab should be located.
// Registered on the MOD event bus
// Assume we have RegistryObject<Item> and RegistryObject<Block> called ITEM and BLOCK
@SubscribeEvent
public void buildContents(CreativeModeTabEvent.Register event) {
event.registerCreativeModeTab(new ResourceLocation(MOD_ID, "example"), builder ->
// Set name of tab to display
builder.title(Component.translatable("item_group." + MOD_ID + ".example"))
// Set icon of creative tab
.icon(() -> new ItemStack(ITEM.get()))
// Add default items to tab
.displayItems((enabledFlags, populator, hasPermissions) -> {
populator.accept(ITEM.get());
populator.accept(BLOCK.get());
})
);
}
Additionally, to use a subclass of the CreativeModeTab
, you can pass in a function which takes in the builder to #withTabFactory
.
Mojang has migrated from using their own math classes for rendering to using JOML, a open source math library for OpenGL. You can fix most of these issues by changing the package of the vector, matrix, etc. to org.joml
; however it is not a 1 to 1 translation. Some changes are functional: Matrix*
methods modify a mutable instance instead of creating a new one, or Quaternionf
using AxisAngle*
instead of Vector3f
.
SoundEvent
s have slightly changed in this version. Specifically, they are constructed through a static method constructor which introduces a new system: fixed range sound. The standard SoundEvent
from previous versions can be constructed using SoundEvent#createVariableRangeEvent
, whose range changes depending on the volume of the sound with a minimum of 16 blocks. Sounds constructed from SoundEvent#createFixedRangeEvent
can be heard from the range specified, regardless of the volume.
// In some supplied instance
// Will change depending on volume, minimum 16 blocks
SoundEvent.createVariableRangeEvent("sound.example_mod.variable_example");
// Will only be heard within specified range (e.g. 5 blocks)
SoundEvent.createFixedRangeEvent("sound.example_mod.fixed_example", 5f);
Pack
s and PackResources
have changed slightly between the two versions.
First PackResources#hasResource
no longer exists. Instead #getResource
should be checked for nullability instead before getting the InputStream
from the IoSupplier
.
// Given some PackResources resources
var io = resources.getResource(PackType.SERVER_DATA, new ResourceLocation(/**/));
if (io != null) {
InputStream input = io.get();
// Same as before
}
Additionally, #getResources
has been replaced by #listResources
with the ResourceLocation
predicate changing to a PackResources$ResourceOutput
which acts as a consumer taking in the resource and the IoSupplier
the resource would be located in.
Finally, Pack
has become a private constructor, only constructed through one of the static constructors #readMetaAndCreate
, which acts similarly to the 1.19.2 #create
, or #create
. A common case that this will encounter is for the AddPackFindersEvent
. Here are the list of changes:
Supplier<PackResources>
has turned intoPack$ResourcesSupplier
which takes in the pack id.PackType
is now a parameter to check whether the pack is compatible for the current version.- The description, feature flags, and hidden check is stored on the
Pack$Info
, which is either obtained from the metadata file or constructed from the record.
Data Generators have changed quite a bit from how providers are added to the providers themselves.
Starting off, all providers now take in a PackOutput
compared to the DataGenerator
itself. The PackOutput
simply specifies the directory where the pack will be generated. Because of this change, the #addProvider
method now takes in either the provider instance or a function that takes in a PackOutput
and constructs a DataProvider
.
// On the mod event bus
@SubscribeEvent
public void gatherData(GatherDataEvent event) {
event.addProvider(true, output -> /*create provider here*/);
}
GatherDataEvent
now provides a CompletableFuture
containing a HolderLookup$Provider
, which is used to get registries and their objects. This can be obtained via #getLookupProvider
.
All DataProvider
s now return a CompletableFuture
on #run
, which is used to write the data to its apropriate file.
RecipeProvider
s now construct recipes in #buildRecipes
. Additionally, each recipe builder, besides dynamic recipes, must specify a RecipeCategory
which determines the subdirectory on where the recipe will be generated.
// In RecipeProvider#buildRecipes(writer)
ShapedRecipeBuilder builder = ShapedRecipeBuilder.shaped(RecipeCategory.MISC, result)
.pattern("a a") // Create recipe pattern
.define('a', item) // Define what the symbol represents
.unlockedBy("criteria", criteria) // How the recipe is unlocked
.save(writer); // Add data to builder
TagsProvider
s can only add objects through their ResourceKey
. To add objects directly, an IntrinsicHolderTagsProvider
should be used instead. This takes in a function which extracts a key from the object itself. Vanilla creates these for Item
s, EntityType
s, Fluid
s, and GameEvent
s with Forge adding one for Block
s. Any others need to specify the function.
// 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
);
}
LootTableProvider
has received a massive overhaul, no longer needing to subtype the class and instead just supply arguments to the constructor. In addition to the PackOutput
, it takes in a set of table names to validate whether they have been created and a list of SubProviderEntry
s, which are used to generate the tables for each LootContextParamSet
.
// 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, /*...*/)
)
);
}
A LootTableSubProvider
is used to generate the loot tables in the SubProviderEntry
, essentially functioning the same as a Supplier<Consumer<BiConsumer<ResourceLocation, LootTable.Builder>>>
.
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
}
}
// In the list passed into the LootTableProvider constructor
new LootTableProvider.SubProviderEntry(
ExampleSubProvider::new,
// Loot table generator for the 'empty' param set
LootContextParamSets.EMPTY
)
The overrides for blocks and entities still exist being called BlockLootSubProvider
and EntityLootSubProvider
respectively. They both take in the feature flags, with the block sub provider taking in a set of items to which are resistant to explosions. The method used to generate the tables have been changed from #addTables
to #generate
. You still need to override the #getKnown*
methods for validation.
// In some BlockLootSubProvider subclass
public MyBlockLootSubProvider() {
super(Collections.emptySet(), FeatureFlags.REGISTRY.allFlags());
}
@Override
public void generate() {
// Add tables here
}
AdvancementProvider
has also received a massive overhaul, no longer needing to subtype the class and instead just supply arguments to the constructor. In addition to the PackOutput
, it takes in the holder lookup for registries and the AdvancementSubProvider
s, which are used to generate the advancements. For ease of access to the ExistingFileHelper
, Forge has added an extension on the provider aptly named ForgeAdvancementProvider
which takes in ForgeAdvancementProvider$AdvancementGenerator
s which contain the ExistingFileHelper
as a parameter.
// 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, /*...*/)
)
);
}
The ForgeAdvancementProvider$AdvancementGenerator
is responsible for generating advancements. To be able to effectively generate advancements the ExistingFileHelper
should be passed in such that the advancement can be built using the Advancement$Builder#save
method which takes it in.
// In some ForgeAdvancementProvider$AdvancementGenerator or as a lambda reference
@Override
public void generate(HolderLookup.Provider registries, Consumer<Advancement> writer, ExistingFileHelper existingFileHelper) {
// Build advancements here
}
JsonCodecProvider#forDatapackRegistry
was removed in favor of using the vanilla RegistriesDatapackGenerator
for generating dynamic registry objects. To expand upon this provider, Forge introduced the DatapackBuiltinEntriesProvider
, which can take in a RegistrySetBuilder
to generate the specific dynamic objects to use, and a set of mod ids to determine which mod's dynamic registry objects to generate.
// 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 objects to generate
new RegistrySetBuilder()
.add(Registries.NOISE_SETTINGS, context -> {
// Generate noise generator settings
context.register(
ResourceKey.create(Registries.NOISE_SETTINGS, new ResourceLocation(MOD_ID, "example_settings")),
NoiseGeneratorSettings.floatingIslands(context)
);
}),
// Generate dynamic registry objects for this mod
Set.of(MOD_ID)
)
);
}
Rendering has changed a massive amount in this update. The most common being those explicitly mentioned by minecraft such as item and block textures only being in models/item
and models/block
respectively or the asynchronous loading and writing of assets and data. Textures for items and blocks are also required to be in textures/item
and textures/block
respectively as textures are loaded before model are processed. These texture directories are specified in JSON in the atlases
directory.
This section will try to cover all the updates in 1.19.3 as these issues get fixed.
To resolve models that are nested within other models, IUnbakedGeometry#resolveParents
was added. This is a defaulted method, so no issues should occur in custom loaders; however, those who are resolving models within models will need to pivot to use this new method.
ModelEvent$ModifyBakingResult
is an event fired on the mod event bus used to handle the caching of state -> model map done previously in ModelEvent$BakingCompleted
. The usecases typically stem from users who need to make adjustments to models due to cases where custom loaders are not possible or that the context provided is insufficient.
Due to the event being fired on a worker thread, you should only access those objects provided by the event itself as it is otherwise unsafe.
Behaviors are being migrated to use a new system built around the abstractions and overengineering provided by DataFixerUpper (DFU). As these systems tend to be complex and convoluted, this will only provide a brief overview of the changes and implementations.
In nearly all cases, I, ChampionAsh5357, would recommend using SmartBrainLib over a vanilla
Brain
implementation. It is much simpler to understand and more intuitive than the vanilla system.
The Behavior
class is no longer the base for behavior logic. That has been relegated to BehaviorControl
: an interface which Behavior
implements. BehaviorControl
checks whether the logic can start (#tryStart
), its ticking and stop check (#tickOrStop
), and finally the stop logic (#doStop
).
Some classes affected by this are DoNothing
and GateBehavior
which implements BehaviorControl
.
OneShot
is a behavior control which executes once via a Trigger
statement. If the Trigger#trigger
returns true, the behavior control is executed; though it only sets the state to running for a single tick.
BehaviorBuilder and TriggerGate are essentially the DFU-implemented classes which creates OneShot
triggers such as waking up or strolling to a point of interest.
For a basic understanding, a BehaviorBuilder
acts similarly to a RecordCodecBuilder
: it creates the behavior instance, takes in the necessary MemoryModuleType
s and whether it is registered, present, or absent, and then applies to construct a Trigger
. The BehaviorBuilder
also contains methods to make triggers act sequentially or if a given predicate is met.
TriggerGate
is an holder of state methods for GateBehavior
s. It either triggers a single instance at random (#triggerOneShuffled
) or executed based on the ordering and run policy selected (#triggerGate
).
This is a list of minor changes which probably won't affect the modding experience but is convenient to know anyways.
Entity#getAddEntityPacket
is no longer abstract, instead defaulting to the ClientboundAddEntityPacket
. If you need to send additional data on entity creation, you should still use NetworkHooks#getEntitySpawningPacket
.
AbstractContainerMenu
has now migrated from using the Container
to check whether the menu could still be kept open to using a static method called #stillValid
in the instance of the same name. To use this method, you need to provide a ContainerLevelAccess
, the player, and the Block
the menu is attached to.
// 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());
}
The BlockStateProvider
now contains a new method to generate a simple block state for a single block model along with an associated item model.
// In some BlockStateProvider#registerStatesAndModels
// Assume there is a RegistryObject<Block> BLOCK
this.simpleBlockWithItem(BLOCK.get(), this.cubeAll(BLOCK.get()));
The context-sensitive methods within AbstractMegaTreeGrower
and AbstractTreeGrower
have been removed and as such no longer have access to the world or position as the methods now return the keys of the ConfiguredFeature
rather than the feature itself.
The Music
class, used to provide a background track during different situations, now takes in a Holder<SoundEvent>
rather than the SoundEvent
itself.
Minecraft has removed KeyboardHandler#sendRepeatsToGui
when a key was held down such that it repeated within a GUI. There is no replacement, so the handler will now always send repeats to the GUI.
The following classes were renamed or refactored within Minecraft and Forge:
net.minecraft.client.gui.components.Widget
->net.minecraft.client.gui.components.Renderable
net.minecraft.data.tags.BlockTagsProvider
->net.minecraftforge.common.data.BlockTagsProvider
net.minecraft.data.loot.BlockLoot
->net.minecraft.data.loot.BlockLootSubProvider
net.minecraft.data.loot.EntityLoot
->net.minecraft.data.loot.EntityLootSubProvider
Hey. I found a problem / something that wasn't mentioned. I've been storing my mod's textures under mod/textures/blocks. Mojang added Atlases to Resource Packs in 1.19.3. If you look into the atlas file assets/minecraft/atlases/blocks.json, it requires block textures to be in the block folder (no s). None of my textures were being loaded because they were in the blocks folder, not block. My item textures were similarly not being loading because they were in the items folder, not item folder. I don't think your textures had to be in a certain folder before now (just in the textures folder), since you could specify the path in the model files.
Just mention that where you store textures are now more restrictive. Block textures now have to be in textures/block, and textures/item.
We have to follow minecraft's convention now.
Edit:
After looking a little more on how an atlas file works, I supposed modders could add their own atlas file, and manually add their texture directories. That might work, but I haven't tried. They're just configuration files. I don't think they are at all related to the real built-in atlas textures that are stitched together from base textures and used by the gpu.