Fabric for Minecraft 1.20.5 & 1.20.6
Minecraft 1.20.5 is to be released in the near future with significant changes affecting mod makers.
But first, we need to make this clear: 1.20.5 is a record-breaking update. This development cycle spans from December 18, 2023 to April of 2024, the longest for any “dot release”. We saw 15 snapshots, the first time it became two digits for a dot release (previous record was 1.20.3 for 8 snapshots). And no surprise: the amount of changes is also unprecedented.
For this reason, we ask all players to be patient, and give mod developers time to update to this new version. We ask everyone kindly not to pester them. 1.20.5 is expected to be the last update of the 1.20 series.
We recommend all players to make a backup of the world. For content mod users, creating a new world is recommended, as most mods do not handle world data upgrades of this magnitude.
Here is a list of all major modder-facing changes in this version. Note that all code references are using Yarn mappings; modders using alternative mappings may need to use different names.
Java 21
Minecraft 1.20.5 now requires Java 21 to run. This means mods can be compiled for Java 21 and use the latest features. This also marks the end of 32-bit support.
To set up a development environment you will need to use Java 21, Loom 1.6, and Gradle 8.6 or higher.
Fabric changes
Developers should use Loom 1.6 (at the time of writing) to develop mods for Minecraft 1.20.5. Players should install the latest stable version of Fabric Loader (currently 0.15.10).
Loom 1.5 & 1.6
There has been two major updates to Loom since the last blogpost: 1.5 and 1.6.
Loom 1.5 includes several performance boosts and small bug fixes. Notably, the mixins can now be remapped using the Tiny Remapper instead of Mixin annotation processor. When enabled, the mixins are remapped in-place and the output jar no longer contains a refmap. This is currently experimental and opt-in.
Loom 1.6 also brought more performance improvements. Decompiler caches were added, meaning that changes to access wideners no longer cause the entire game to be decompiled. There is also an update to how Loom handles existing locks (such as when two Gradle runs occur simultaneously) - it will no longer clear the cache, instead waiting for one process to exit. The error message for locked files is now more detailed, as well.
Please see the Loom 1.5 and Loom 1.6 release notes for all of the changes.
Localization
Fabric has recently moved to using Crowdin to help manage translations for our projects. This makes it much easier to contribute translations across all Fabric projects.
Current projects include Fabric API and installer. If you are able to help translate Fabric into your language, please visit the Crowdin website.
New Fabric API changes
With the help of many contributors, Fabric API has received some new features since the last update blog post:
- New API: Data Attachments. This allows attaching custom data to block entities, chunks, entites, and worlds. Attached data can be saved or persisted between mob conversions. (Syst3ms)
- Interaction Events: add client after block break event (kevinthegreat1)
- GameTest API: add a system property for a custom output directory for gametest structures (ErrorCraft)
- Entity Events: add mob conversion event (Syst3ms)
- Fabric Rendering: add
AtlasSourceTypeRegistry
(PepperCode1) - Data Generation: add
FabricCodecDataProvider
(ErrorCraft) - Resource Loader: significant refactors to support
pack.mcmeta
metadata like filters and overlays. (apple502j) - Fabric Rendering: add support for custom
ColorResolver
s (PepperCode1) - Fluids Rendering: expose a function for querying the non-default fluid renderer (jellysquid3)
- Lifecycle Events: add save events to
ServerLifecycleEvents
(MrNavaStar) - Item API: add enchantments API (Syst3ms)
There is one more addition for advanced developers: you can now declare Fabric API as a dependency using BOM or version catalogs. Check the pull request for more details.
Breaking changes and deprecations
Note: breaking changes related to vanilla changes are discussed below.
The following APIs were removed:
fabric-containers-v0
(previously deprecated)fabric-events-lifecycle-v0
(previously deprecated)fabric-mining-level-api-v1
ScreenRegistry
andScreenHandlerRegistry
ModifyItemAttributeModifiersCallback
from the Item API was removed without replacement due to code changes making it infeasible to port.
In Object Builder API, FabricItemSettings
was removed. Use vanilla Item.Settings
instead; interface injection provides settings added by Fabric API. Similarly, FabricBlockSettings
was deprecated and replaced with vanilla AbstractBlock.Settings
. FabricEntityTypeBuilder
and FabricBlockEntityTypeBuilder
were also deprecated; use vanilla builders instead.
Codec-based Resource Conditions
Unrelated to the 1.20.5 update, a significant change was made to Resource Conditions’ internal workings. This update was developed by Apollo and apple502j. The only breaking change to the JSON syntax is the removal of (block/item/fluid)_tags_populated
conditions, but data generation is heavily affected.
The ConditionJsonProvider
interface has been removed and been replaced by ResourceCondition
. ResourceCondition
has a test
method, for determining whether the condition should pass, and getType
, which returns a ResourceConditionType
. withConditions
methods in data providers now take ResourceCondition
instances. Methods in DefaultResourceConditions
that created conditions have been moved to ResourceConditions
. A custom ResourceConditionType
should be registered with ResourceConditions#register
.
In addition, fabric:true
condition was added. This condition always passes.
Convention Tag unification
Another significant change was made in the Convention Tags. As part of collaborative efforts with NeoForge, TelepathicGrunt has prepared (and we released) the version 2 of the tags. Version 1 is now deprecated.
As the changes are too long to list here, developers are encouraged to check the pull request.
Minecraft changes
In short, these are the veeery major changes:
- Item Components. Item stacks no longer use NBT as runtime data storage, instead using predefined “components” to keep values like custom name, enchantments, or damage.
- Networking. Instead of manually processing
PacketByteBuf
, packets are now serialized usingPacketCodec
s. In Fabric Networking API, the previousPacketByteBuf
-based APIs were removed; the newer,FabricPacket
-based API was rewritten to make use of the vanillaCustomPayload
interface. - Registries. Likely as a preparation for data-driven blocks and items, serialization/deserialization of most objects now require
RegistryWrapper.WrapperLookup
instance. This is most noticeable in texts and data generation. In addition, loot tables are now managed by a new type of registry called “reloadable registries”.
Item Components
We skip the general description of the item components system, which you can check in the slicedlime’s video: News in Data Pack Version 33 (24w09a): Item Components!.
Now, we begin the journey into the implementation of item components:
There are five main objects in the item components:
DataComponentType
, a type of components, serving as keys;- Component classes (can be any object), serving as values;
ComponentMap
, a read-only view of components;ComponentMapImpl
, which is aComponentMap
that can be modified, and internally a pair of unmodifiable baseComponentMap
and the “overrides” that are modified; andComponentChanges
, a map of component type to the changes for the value (either setting it to a specific value or removing it). This can be used as a diff applied toItemStack
; you apply an instance ofComponentChanges
toItemStack
/ComponentMapImpl
, so that the overrides part of the map reflects the changes. In other parts of the code,ComponentChanges
is just used asComponentMapImpl
minus the base.
So, how does this work?
Each item has the base components. In addition to the base components common to all items, like having empty EnchantmentsComponent
, some items provide additional base components. A notable example is DataComponentTypes#DAMAGE
for armors, tools, and weapons. A component type that exists in the base has a corresponding default value; here the default damage is 0
.
An ItemStack
can either 1) add an additional component not present in the base; 2) change the components from the one in the base; or 3) remove a component that exists in the base. The final components of ItemStack
, obtainable as ComponentMap
from ItemStack#getComponents
, reflects all three.
Using Item Components
Item components on item stacks have a similar API surface to a map. Here are some common operations that can be done, using the example of an item’s custom name (note that Text
is directly used as the component value):
// Similar to Map#get
@Nullable Text name = stack.get(DataComponentTypes.CUSTOM_NAME);
// Similar to Map#put
// Returns the previous value, if any
@Nullable Text oldName = stack.set(DataComponentTypes.CUSTOM_NAME, Text.literal("Lorem ipsum"));
// Similar to Map#getOrDefault
name = stack.getOrDefault(DataComponentTypes.CUSTOM_NAME, Text.empty());
// Similar to Map#remove
@Nullable Text removedName = stack.remove(DataComponentTypes.CUSTOM_NAME);
// Similar to Map#compute
// In this example, we change the color of the item name
// (Note: stack#getName already checks for custom name, so in reality this does not need apply() call.)
stack.apply(DataComponentTypes.CUSTOM_NAME, stack.getName(), current -> current.copy().formatted(Formatting.RED))
Getting/setting the custom data (NBT). This is useful for datapack makers and server-side modders, because custom item components (see below) need to be synced to the clients, while custom data remains an unparsed NBT.
Unlike the previous example, this uses a component record NbtComponent
.
Note: component values are supposed to be immutable - even if you can modify it, don’t. Always copy and set()
.
NbtCompound nbt = ...;
NbtComponent component = NbtComponent.of(nbt);
// Setting
stack.set(DataComponentTypes.CUSTOM_DATA, component);
// Getting a copy
@Nullable var data = stack.get(DataComponentTypes.CUSTOM_DATA);
if (data != null) {
NbtCompound value = data.copyNbt();
}
// Using NbtComponent#apply, which copies automatically, to modify the NBT
// Note: you might want to use the static method NbtComponent#set instead,
// which makes this a bit shorter
stack.apply(DataComponentTypes.CUSTOM_DATA, NbtComponent.DEFAULT, comp -> comp.apply(currentNbt -> {
currentNbt.putInt("key", 0);
}));
// Checking if a certain NBT is a subset of the stack NBT
// (also known as: "non-strict matching")
NbtCompound requiredNbt = ...;
boolean match = stack.getOrDefault(DataComponentTypes.CUSTOM_DATA, NbtComponent.DEFAULT).matches(requiredNbt);
// use createPredicate to make Predicate<ItemStack>
Using ComponentChanges
to mass-modify the components of ItemStack
:
// stack is a damageable item.
// Repair the item and remove custom name.
// Note that the resulting stack has no components because 0 damage is the default from the base components.
var changes = ComponentChanges.builder().add(DataComponentTypes.DAMAGE, 0).remove(DataComponentTypes.CUSTOM_NAME).build();
stack.applyChanges(changes);
Making an item with base components:
Item item = new Item(new Item.Settings().component(DataComponentTypes.CUSTOM_NAME, Text.literal("Hello")));
// Call #component multiple times for multiple base components.
// Note: calling maxDamage automatically adds DAMAGE component.
Making a custom component. Note that like many registered entries, these must be present in both the client and the server.
public static final DataComponentType<Integer> WEIRDNESS = DataComponentType.builder().codec(Codec.INT).packetCodec(PacketCodecs.VAR_INT).build();
// in the initializer
Registry.register(Registries.DATA_COMPONENT_TYPE, new Identifier("example", "weirdness"), WEIRDNESS);
Some caveats
- Component values should not be modified directly. Using
Record
or other immutable object is highly recommended. Always copy, modify, then set. - Setting an invalid value for a component does not cause an error immediately, but will lead to a crash during save, and possible data corruption. A common example is
NONNEGATIVE_INT
being used for an incrementing value; when it overflows and the value is set to negatives, saving it would crash.
BlockEntity interaction with components
BlockEntity
stores the components when placing a block item stack with components and when pick-blocking. Components are not currently used to serialize block entities themselves. A block entity that uses components, like custom containers, should override addComponents
, readComponents
, and removeFromCopiedStackNbt
methods:
addComponents
adds the block entity’s stored data to a component builder.readComponents
reads the block entity data from components.removeFromCopiedStackNbt
removes NBT keys which are now serialized using components when in item stacks. For example, a chest’s held stacks are stored underItems
key when the block entity itself is serialized, and stored under a component when an item stack for the block entity is serialized. To prevent double serialization, this method removesItems
from the item stack NBT.
Note that LootableContainerBlockEntity
handles component changes themselves, so you do not have to reinvent the wheel if you use them.
Item Component-related Fabric API changes
allowNbtUpdateAnimations
method of FabricItem
was renamed to allowComponentsUpdateAnimations
.
FabricItem.getAttributeModifiers
changes?
isSuitableFor
and getFoodComponents
of FabricItemStack
removed/replaced with vanilla components.
Recipe API
DefaultCustomIngredients#nbt
and support for NBT ingredients were removed, as the game no longer uses NBT to store item stack-specific data.
To check for custom name, enchantments, etc that were previously kept inside the NBT but are now recorded in the components (see below for details), use the new components
ingredient. To check for custom data component (a NBT data not used by the game but can be used by data packs or commands), use the customData
ingredient.
Transfer API
TransferVariant
(such as FluidVariant
or ItemVariant
) is now a pair of the object and the components, instead of the object and NBT. getNbt
, hasNbt
, and nbtMatches
methods were replaced with getComponents
, hasComponents
, and componentsMatch
methods. The following methods were removed: copyNbt
, copyOrCreateNbt
, toNbt
, and toPacket
.
FluidVariant#of
and ItemVariant#of
now takes ComponentChanges
instead of @Nullable NbtCompound
.
TransferVariant
is now serialized using codecs and packet codecs. Therefore, fromNbt
and fromPacket
static methods were removed.
SingleVariantStorage#writeNbt
instance method was removed; subclasses now provide separate methods named writeNbt
. Both readNbt
and writeNbt
now require passing a RegistryWrapper.WrapperLookup
instance; they should be available in the methods from which they are called (or you can use the world’s DynamicRegistryManager
instance).
Other Item changes
- As noted in the video, an empty item stack is now serialized as omitting the field or an empty object.
ItemStack#fromNbt
now returnsOptional<ItemStack>
whilefromNbtOrEmpty
supports empty NBT object. Both now require passing the registries. These methods log an error when an item with IDminecraft:air
is encountered. writeNbt
method was renamed toencode
. The one withNbtCompound
argument allows adding to the existing compound, likewriteNbt
.- The methods in
ItemStack
that referenceNbt
are generally renamed to referenceComponents
instead. Item#isNbtSynced
was removed, specify custom packet codecs instead.Item#getBreakSound
was added.- Attack damage and mining speed of
MiningToolItem
andSwordItem
are now specified in item settings viaattributeModifiers
setting. - Item tooltips are now given a “tooltip type”, and
TooltipContext
is now underItem
.ItemTooltipCallback
Fabric API event now passes the type as well. Note thathide_tooltip
item component prevents the event from being invoked.
Item damages
When damaging an item stack held in the hands or in the armor slots, you now pass the EquipmentSlot
instead of a callback:
- stack.damage(1, entity, p -> p.sendToolBreakStatus(Hand.MAIN_HAND));
+ stack.damage(1, entity, EquipmentSlot.MAINHAND);
When damaging an item stack held by non-entities (like shears in dispensers), the stack must be broken by the newly passed callback, not by checking the return value.
- if (stack.damage(1, random, null)) stack.setCount(0);
+ stack.damage(1, random, null, () -> stack.setCount(0));
In Fabric API, CustomDamageHandler
callback’s signature was changed from ItemStack stack, int amount, LivingEntity entity, Consumer breakCallback
to ItemStack stack, int amount, LivingEntity entity, EquipmentSlot slot, Runnable breakCallback
. Additionally, calling breakCallback
now ignores the return value and vanilla damage handler (as the item is already broken).
Networking
Previously, networking code was simple: reading from, and writing to, PacketByteBuf
. Although this method still works, Mojang has added an abstraction layor: PacketCodec
.
PacketCodec
works like the DataFixerUpper codecs used in JSON/NBT serialization - although they are not compatible with each other. To keep it simple, PacketCodec
is a pair of deserializers and serializers - or, decoders and encoders. This change reduces bugs related to wrong read/write order - which is one of the most difficult bugs to identify.
How to create a PacketCodec
Note: A new subclass of PacketByteBuf
, RegistryByteBuf
, is used for PLAY-phase networking (the one you’re probably using). For ConfigurationNetworking
, use PacketByteBuf
instead of RegistryByteBuf
. PacketCodec
s are usually stored in a public static final
field.
Suppose we are serializing this record:
public record VirtualHugs(int count) {
// Note: replaced PacketByteBuf with RegistryByteBuf
public VirtualHugs(RegistryByteBuf buf) {
this(buf.readVarInt());
}
// Instance method
public void write(RegistryByteBuf buf) {
buf.writeVarInt(count);
}
// Static method, buffer-first
public static void write2(RegistryByteBuf buf, VirtualHugs hugs) {
buf.writeVarInt(hugs.count())
}
}
- The easy method - adopting existing code
PacketCodec<RegistryByteBuf, VirtualHugs> PACKET_CODEC = PacketCodec.of(VirtualHugs::write, VirtualHugs::new);
// Or, using static method:
PACKET_CODEC = PacketCodec.ofStatic(VirtualHugs::write2, VirtualHugs::new);
- The new method - building codecs from other codecs, like in DataFixerUpper
// "tuple" is an ordered, un-keyed set of stuff.
// Up to 3 fields can be serialized with vanilla method.
// The constructor always comes last.
CODEC = PacketCodec.tuple(PacketCodecs.VAR_INT, VirtualHugs::count, VirtualHugs::new);
// Or, since there is only one field in VirtualHugs, this works too.
CODEC = PacketCodecs.VAR_INT.xmap(VirtualHugs::new, VirtualHugs::count);
You might see some nasty casting errors in some cases. Call .cast()
on the codec to make it go away; it’s generally safe to do so.
Advanced PacketCodecs examples
Here are some more examples:
// For a singleton (like an empty packet). Encoding something other than INSTANCE will error.
// It does not actually touch the buffer.
var singleton = PacketCodec.unit(INSTANCE);
// Using PacketCodec from classic API
Text text = TextCodecs.PACKET_CODEC.decode(buf);
TextCodecs.PACKET_CODEC.encode(buf, text);
// Codec for Optional<Text>
// Compatible with PacketByteBuf#readOptional
var optText = TextCodecs.PACKET_CODEC.collect(PacketCodecs::optional);
// Codec for List<String>
var strings = PacketCodecs.STRING.collect(PacketCodecs.toList());
// or Set<String>, each max 16 chars
// both work fine, compatible with PacketByteBuf#readCollection
var stringSet = PacketCodecs.string(16).collect(PacketCodecs.toCollection(HashSet::new));
stringSet = PacketCodecs.collection(HashSet::new, PacketCodecs.string(16));
// Map<String, BlockPos>
var positions = PacketCodecs.map(HashMap::new, PacketCodecs.STRING, BlockPos.PACKET_CODEC);
// What? DataFixerUpper codecs?
// Nah, these are just serialized as NBTs.
var advancement = PacketCodecs.codec(Advancement.CODEC);
// Serializing NbtCompound
var nbt = PacketCodecs.NBT_COMPOUND;
// Serializing a registry value (by raw ID)
// Note: these require RegistryByteBuf!
PacketCodec<RegistryByteBuf, Item> item = PacketCodecs.registryValue(RegistryKeys.ITEM);
// Or, RegistryEntry
PacketCodec<RegistryByteBuf, Biome> biome = PacketCodecs.registryEntry(RegistryKeys.BIOME);
// Serializing an Enum
var axis = PacketCodecs.indexed(i -> Direction.Axis.VALUES[i], Direction.Axis::ordinal);
Fabric Networking API
Networking API no longer directly uses PacketByteBuf
, except in LoginNetworking
. Instead, you need to subclass CustomPayload
and register it. Note that CustomPayload
is a vanilla interface. While this is similar to the packet object-based networking introduced recently, it is different in some ways. (PacketType
and FabricPacket
were, therefore, removed.)
Compare the following, pre-snapshot packet:
public record SlapPacket(UUID slapped) implements FabricPacket {
public static final Identifier ID = new Identifier(...);
public static final PacketType<SlapPacket> TYPE = PacketType.create(ID, SlapPacket::new);
public SlapPacket(PacketByteBuf buf) {
this(buf.readUuid());
}
@Override
public void write(PacketByteBuf buf) {
buf.writeUuid(slapped);
}
}
And a new one:
public record SlapPacket(UUID slapped) implements CustomPayload {
public static final CustomPayload.Id<SlapPacket> PACKET_ID = new CustomPayload.Id<>(new Identifier(...));
public static final PacketCodec<RegistryByteBuf, SlapPacket> PACKET_CODEC = Uuids.PACKET_CODEC.xmap(SlapPacket::new, SlapPacket::slapped).cast();
}
There are three differences you will notice:
- Instead of now-removed
FabricPacket
, we implementCustomPayload
. - Each
CustomPayload
implementation has its ownCustomPayload.Id
. UnlikePacketType
, it lacks reference to the decoder. - The latter does not have a
write
method orPacketByteBuf
-taking constructor. Instead,PACKET_CODEC
field was added. (Note, you can make yourPacketCodec
from the constructor and thewrite
method, so don’t just remove those!)
But, you may ask, how do you get the decoder if it’s not in Id
? Good question. The answer: you need to register it in ModInitializer
on both ends (sender and receiver). In fact, Fabric API will happily crash if you don’t before calling registerGlobalReceiver
.
// SlapPacket is sent during gameplay
// from the client to the server (serverbound; C2S)
PayloadTypeRegistry.playC2S().register(SlapPacket.PACKET_ID, SlapPacket.PACKET_CODEC);
// Replace with configurationS2C, playS2C, etc.
Then, you can change the receiver. Instead of long parameters, the event callback now gets only 2 parameters: the payload that was sent, and a context object. (Don’t worry, it won’t eat the RAM.)
- ServerPlayNetworking.registerGlobalReceiver(SlapPacket.TYPE, (packet, player, sender) -> {
- var world = player.getServerWorld();
+ServerPlayNetworking.registerGlobalReceiver(SlapPacket.PACKET_ID, (payload, context) -> {
+ var world = context.player().getServerWorld();
var entity = world.getEntity(packet.slapped());
});
Here are the available fields in the context object:
PayloadTypeRegistry |
Class | Context Fields |
---|---|---|
playC2S |
ServerPlayNetworking |
player , responseSender |
playS2C |
ClientPlayNetworking |
client , player , responseSender |
configurationC2S |
ServerConfigurationNetworking |
networkHandler , responseSender |
configurationS2C |
ClientConfigurationNetworking |
responseSender |
Note that the field shortcuts might be added later.
Finally, sending the packet also uses CustomPayload
. Remember to register the codec first!
ClientPlayNetworking.send(SlapPacket.PACKET_ID, new SlapPacket(...));
// for PacketSender:
sender.sendPacket(new SlapPacket(...));
Note: PacketSender
no longer provides APIs that use PacketByteBuf
. For login networking where custom payloads are not used yet, use the subinterface LoginPacketSender
.
Some other Networking API related changes:
FutureListeners
was removed.GenericFutureListener
-taking methods inPacketSender
were removed. Use the one takingPacketCallbacks
.ServerPlayNetworking#getServer
was removed.handler.player.server
should work.
Networking-adjacent changes in Fabric API
Particles API
FabricParticleTypes#complex
now requires you to pass both Codec
and PacketCodec
for serializing the particle type. The string-based parser, ParticleType.Factory
, was removed.
Recipe API
CustomIngredientSerializer
must now override getPacketCodec
. The read
and write
methods were removed. Use Ingredient#PACKET_CODEC
for representing another ingredient (which Fabric API patches up to also support ours).
Screen Handler API
ExtendedScreenHandlerType
now uses a payload object instead of PacketByteBuf
. In the type constructor, you now need to pass PacketCodec
:
public static final ExtendedScreenHandlerType<OvenScreenHandler> OVEN = new ExtendedScreenHandlerType((syncId, inventory, data) -> ..., OvenData.PACKET_CODEC);
Notice that the callback got data
instead of buf
, as well. Note that the payload object can be of any type, whether it be Identifier
or your custom record. (These don’t have to be registered in PayloadTypeRegistry
.)
ExtendedScreenHandlerFactory#writeScreenOpeningData
was replaced with getScreenOpeningData
. It takes the player as the sole argument and returns the data class, in this case, OvenData
.
Registries
RegistryWrapper & datagen
Many methods for serialization now require RegistryWrapper.WrapperLookup
instance. This is used to query registries, and DynamicRegistryManager
instance can be used for this purpose. This change is most prominent in text and data generation-related code. This registry is used to query data-driven values during serialization.
Data provider constructors in Fabric API now take RegistryWrapper.WrapperLookup
as a parameter; modders should change their constructors to take the instance and pass to the super
constructor. In addition, FabricAdvancementProvider#generateAdvancement
now also passes the instance.
public class TestBlockLootTableProvider extends FabricBlockLootTableProvider {
- private TestBlockLootTableProvider(FabricDataOutput output) {
- super(output);
+ private TestBlockLootTableProvider(FabricDataOutput output, CompletableFuture<RegistryWrapper.WrapperLookup> registryLookup) {
+ super(output, registryLookup);
}
Loot Registries
Loot tables (including loot-adjacent stuff, namely predicates and item modifiers) are now managed by a special type of registry, called “reloadable registries”. Unlike ordinary registries, they are reloaded during /reload
.
LootDataLookup
was removed; ReloadableRegistries.Lookup
can be obtained from server.getReloadableRegistries()
. From there, item modifiers and predicates can be looked up via getRegistryManager
; a shortcut exists for loot tables via getLootTable
.
Registry-ification of loot table also means that they are now identified with RegistryKey
. Use RegistryKey.of(RegistryKeys.LOOT_TABLE, id)
to get the registry key. In Fabric API, VillagerInteractionRegistries#registerGiftLootTable
now takes the registry key, while the ID-taking method is deprecated.
Fabric API’s Loot API received breaking changes. For the arguments passed to events, ResourceMaanger
and LootManager
is gone. In the LOAD_ALL
event, the loot manager was replaced with Registry<LootTable>
. In all places, Identifier
is replaced with RegistryKey
as described above. In addition, because mutating registry is impossible now, the loot tables are modified before addition; this means that per-table events don’t get the table registry anymore.
Other changes
A new syncing protocol is in place for DynamicRegistryManager
entries. Vanilla data packs provide knownPackInfo
fields, which are sent by the server on joined clients. Clients then reply with the subset of the info they understand (i.e. loaded on their side). When a registry entry is loaded from a pack with knownPackInfo
, and the client has acknowledged the info for the pack, the server tells the client to refer to its own copy of the data, skipping sending the serialized entry over the network. Most importantly, this is currently not used by mod-provided packs, so the registry entries from mods will be sent even if the client has the same mod.
SimpleRegistry
no longer supports changing the registered value post-registration (such as by the set
method). For this reason, the RegistryEntryRemovedCallback
event and the registeyEntryRemoved
shortcut in DynamicRegistryView
were removed.
Finally, SculkSensorFrequencyRegistry.register
now takes RegistryKey<GameEvent>
instead of GameEvent
.
Blocks
All AbstractBlock
public methods to be overridden were made protected
and no longer marked as @Deprecated
.
- public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity entity, BlockHitResult hit) {
+ protected ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity entity, BlockHitResult hit) {
}
Enchantments
Enchantment
constructor now takes Enchantment.Properties
, a record of enchantment properties. EnchantmentTarget
enum was replaced with tags that specifies enchantment-supporting items.
Brewing
FabricBrewingRecipeRegistry
was replaced with FabricBrewingRecipeRegistryBuilder
. The new FabricBrewingRecipeRegistryBuilder.BUILD
event can be used to add new recipes to the brewing stand.
Entities
SpawnRestriction.Location
enum was replaced with an interface, SpawnLocation
. SpawnLocationTypes
provides predefined SpawnLocation
s.
Client
In GUI, setting the initial focus of a screen is now done by overriding setInitialFocus
, rather than calling it in init
. MatrixStack
was replaced with the actual matrix in several rendering code.
Rendering
Some places that previously take rgb
was updated to take argb
instead. The new DyedColorComponent
takes argb
. Item color providers also take argb
instead of rgb
, so make sure to wrap your color with ColorHelper.Argb.fullAlpha()
or else your item may appear transparent if it is registered to the translucent render layer. Block color providers also argb
now, but passing rgb
still works.
Other
DataFixerUpper was updated. This includes several breaking changes, such as MapCodec
being used in places defining extra fields. Use RecordCodecBuilder#mapCodec
instead of create
in this case. Another significant change: optionalFieldOf
is now strict by default. This means that a decoding error in the field value is now treated as an error, instead of being silently skipped. The lenientOptionalFieldOf
method restores old behavior.
On the Minecraft game code side, Util#getResult
was replaced with decode(...).getOrThrow()
method. Several methods and fields in Codecs
were moved to the DFU itself, such as either
, xor
, and withAlternative
(formerly Codecs#alternatively
).