Datageneration/Loot Tables

The LootTableProvider class

First you would need a new class that extends LootTableProvider. In this class you will override the getTables and validate methods. You can optionally override getName to return a name for the logs, if you have multiple providers and don't want to simply show up as "LootTables".

None of the methods you will be overriding (either here or, where applicable, on the subproviders) should call the superclass methods - these superclass methods implement the vanilla data generation.

The getTables method will return a list of subproviders (explained further in the next section), the function signature looks a bit overwhelming but the actual method body is simple. Each subprovider will typically be a class, and you can simply use their constructors here. For more advanced scenarios (e.g. if you want to organize subproviders for multiple parameter sets into a single class), you can use a lambda that returns another lambda or a method reference.

@Override
    protected List<Pair<Supplier<Consumer<BiConsumer<ResourceLocation, LootTable.Builder>>>, LootContextParamSet>> getTables() {
        return List.of(
            Pair.of(MyBlockLoot::new, LootContextParamSets.BLOCK),
            Pair.of(MyEntityLoot::new, LootContextParamSets.ENTITY),
            Pair.of(MyChestLoot::new, LootContextParamSets.CHEST));
    }

The validate method needs to be overriden to eliminate the part of the vanilla code that verifies that the vanilla special loot tables have all been defined. If you have special loot tables (i.e. any loot table other than the main loot table for a block or entity), you may wish to add similar verification code here. The remaining code ensures that each table is consistent with the associated parameter set (e.g. that you're not trying to access a block state in an entity loot table).

@Override
    protected void validate(Map<ResourceLocation, LootTable> map, ValidationContext validationtracker) {
        map.forEach((location, lootTable) -> LootTables.validate(validationtracker, location, lootTable);
    }

Subproviders

Each subprovider is a Consumer<BiConsumer<ResourceLocation, LootTable.Builder>>. This is a fancy name that means that it has an accept function that is called with an argument containing another function that it can call with each loot table it wants to create.

Each vanilla subprovider is a class. BlockLoot and EntityLoot contain helper methods to ensure that every block or entity has a primary loot table and to help generate certain types of loot tables. BlockLoot in particular has a large collection of helper functions that are extremely useful for implementing standard forms of loot tables for block types such as ores, doors, slabs, crops, or any block that simply always drops itself as an item.

Extending BlockLoot or EntityLoot

If you are extending BlockLoot or EntityLoot, you will need to override the addTables and getKnownBlocks or getKnownEntities methods. From the addTables methods you will call the add method for each of the tables you wish to create (a handful of the helper functions in BlockLoot including dropSelf automatically add the tables they create).

@Override
    protected void addTables() {
        dropSelf(ModBlocks.BLOCK1.get());
        add(ModBlocks.BLOCK2.get(), ...);
    }

The second parameter to BlockLoot.add may be a function that accepts the Block and returns a LootTable.Builder, or the LootTable.Builder itself. Many of the helper functions in BlockLoot work this way. For EntityLoot.add, it must simply be the LootTable.Builder, but the first argument may be a ResourceLocation to create a special loot table rather than the primary loot table for the entity (this is used by vanilla for sheep colors).

You must override getKnownBlocks or getKnownEntities, respectively, to return only the blocks or entity types registered by your mod.

@Override
    protected Iterable<Block> getKnownBlocks() {
        return ModBlocks.BLOCKS.getEntries().stream().map(RegistryObject::get).toList();
    }

Implementing a simple subprovider

The other vanilla subproviders don't have much worth extending for, so you can simply directly implement Consumer<BiConsumer<ResourceLocation, LootTable.Builder>>.

public void accept(BiConsumer<ResourceLocation, LootTable.Builder> consumer) {
        consumer.accept({ResourceLocation}, {LootTable.Builder});
    }

The LootTable Builder

This is where you actually make a loot table. You start with an empty LootPool.Builder and add the necessary attributes to it.

  • .name is used for the pool name.
  • .setRolls is used for the amount.
  • .add is used to add an LootPoolEntryContainer$Builder. You can have multiple entries.

A LootPoolEntryContainer$Builder defines what entry container is returned, and which functions and/or conditions are applied (see Loot table for the vanilla functions and conditions).

  • .when is used to apply a condition. These themselves can have multiple operations.

After all attributes have been added the LootPool.Builder can be used to make a LootTable.Builder. This builder can then be used in the subproviders as discussed above.

The LootContextParamSet is automatically applied by the vanilla LootTableProvider.run method.

LootPool.Builder poolBuilder = LootPool.lootPool()
    .name(...)
    .setRolls(...)
    .add(LootItem.lootTableItem(...)
        .apply(function1)
        .apply(function2)
            .when(condition1)
            .when(condition2))
    );
LootTable.Builder tableBuilder = LootTable.lootTable().withPool(poolBuilder);