Loot Events
dev.architectury.event.events.common.LootEvent
Modify or replace loot tables as they load.
Events
| Event | Listener method | Returns |
|---|---|---|
MODIFY_LOOT_TABLE | modifyLootTable(HolderGetter.Provider registries, ResourceKey<LootTable> key, LootTableModificationContext context, boolean builtin) | void - add pools to the existing table. |
REPLACE_LOOT_TABLE | replaceLootTable(HolderGetter.Provider registries, ResourceKey<LootTable> key, LootTable original) | CompoundEventResult<LootTable> - interrupt to swap the whole table. |
The registries parameter
Both listeners receive a HolderGetter.Provider, which resolves the holders and tags a loot table
refers to through lookupOrThrow, get(ResourceKey) and get(TagKey). It cannot enumerate
registries or build a RegistryOps.
HolderLookup.ProviderThe parameter was widened from HolderLookup.Provider to HolderGetter.Provider in Architectury
API 22.0, because the loaders genuinely differ here. Fabric's LootTableEvents passes a real
HolderLookup.Provider, so an instanceof check succeeds there. NeoForge passes a method
reference that implements HolderGetter.Provider and nothing else, so the same check is always
false and a cast always throws.
A listener that recovers the wider type therefore works on Fabric and fails on every NeoForge
install. If you need a RegistryOps to deserialize loot content, build it outside these events
from a provider you own.
Adding to a table
Use context.addPool(LootPool.Builder) to add a pool to the table.
The builtin flag tells you where the table came from: true means it's built in to vanilla or
a mod; false means it's from a user data pack. A common pattern is to only modify built-in
tables, so user data packs can fully override them.
On NeoForge, builtin is always true. The event does run for every loot table there, but
LootTableLoadEvent does not expose where a table came from, so data pack tables cannot be told
apart from built-in ones. Only Fabric reports the real value.
Example: add diamonds to the dirt loot table
LootEvent.MODIFY_LOOT_TABLE.register((registries, key, context, builtin) -> {
if (builtin && Blocks.DIRT.getLootTable().equals(Optional.ofNullable(key))) {
LootPool.Builder pool = LootPool.lootPool()
.add(LootItem.lootTableItem(Items.DIAMOND));
context.addPool(pool);
}
});
Replacing a table
Where MODIFY_LOOT_TABLE appends pools to the existing table, REPLACE_LOOT_TABLE swaps the
whole table for a different one. Interrupt the
CompoundEventResult with the replacement table to take
effect, or return CompoundEventResult.pass() to leave the table alone. The first listener to
interrupt wins, and later listeners still see the original table.
This mirrors NeoForge's LootTableLoadEvent#setTable and Fabric's LootTableEvents.REPLACE.
Example: replace a table outright
LootEvent.REPLACE_LOOT_TABLE.register((registries, key, original) -> {
if (key.identifier().equals(MyMod.id("chests/my_structure"))) {
return CompoundEventResult.interruptTrue(buildMyTable());
}
return CompoundEventResult.pass();
});