Views
Actions
Difference between revisions of "Datageneration/Loot Tables"
Ferri Arnus (talk | contribs) |
Nexus-Dino (talk | contribs) |
||
(3 intermediate revisions by 3 users not shown) | |||
Line 1: | Line 1: | ||
− | + | ==The LootTableProvider class== | |
− | + | First you would need a new class that extends <code>LootTableProvider</code>. In this class you will override the <code>getTables</code> and <code>validate</code> methods. | |
+ | You can optionally override <code>getName</code> 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 <code>getTables</code> 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. | ||
<syntaxhighlight lang="java"> | <syntaxhighlight lang="java"> | ||
− | + | @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)); | ||
+ | } | ||
+ | </syntaxhighlight> | ||
− | + | The <code>validate</code> 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). | |
− | + | <syntaxhighlight lang="java"> | |
− | + | @Override | |
+ | protected void validate(Map<ResourceLocation, LootTable> map, ValidationContext validationtracker) { | ||
+ | map.forEach((location, lootTable) -> LootTables.validate(validationtracker, location, lootTable); | ||
+ | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | ==Subproviders== | |
+ | Each subprovider is a <code>Consumer<BiConsumer<ResourceLocation, LootTable.Builder>></code>. 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 <code>addTables</code> and <code>getKnownBlocks</code> or <code>getKnownEntities</code> methods. From the <code>addTables</code> methods you will call the <code>add</code> method for each of the tables you wish to create (a handful of the helper functions in BlockLoot including <code>dropSelf</code> automatically add the tables they create). | ||
+ | |||
<syntaxhighlight lang="java"> | <syntaxhighlight lang="java"> | ||
− | + | @Override | |
− | + | protected void addTables() { | |
− | + | dropSelf(ModBlocks.BLOCK1.get()); | |
− | + | add(ModBlocks.BLOCK2.get(), ...); | |
− | + | } | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
</syntaxhighlight> | </syntaxhighlight> | ||
− | For the | + | The second parameter to <code>BlockLoot.add</code> 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 <code>EntityLoot.add</code>, 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 <code>getKnownBlocks</code> or <code>getKnownEntities</code>, respectively, to return only the blocks or entity types registered by your mod. | ||
<syntaxhighlight lang="java"> | <syntaxhighlight lang="java"> | ||
− | + | @Override | |
− | + | protected Iterable<Block> getKnownBlocks() { | |
− | + | return ModBlocks.BLOCKS.getEntries().stream().map(RegistryObject::get).toList(); | |
− | + | } | |
− | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | ===Implementing a simple subprovider=== | |
+ | The other vanilla subproviders don't have much worth extending for, so you can simply directly implement <code>Consumer<BiConsumer<ResourceLocation, LootTable.Builder>></code>. | ||
+ | <syntaxhighlight lang="java"> | ||
+ | public void accept(BiConsumer<ResourceLocation, LootTable.Builder> consumer) { | ||
+ | consumer.accept({ResourceLocation}, {LootTable.Builder}); | ||
+ | } | ||
+ | </syntaxhighlight> | ||
− | + | ===The LootTable Builder=== | |
− | + | This is where you actually make a loot table. You start with an empty <code>LootPool.Builder</code> and add the necessary attributes to it. | |
− | |||
− | |||
− | ===The | ||
− | This is where you actually make a | ||
* <code>.name</code> is used for the pool name. | * <code>.name</code> is used for the pool name. | ||
* <code>.setRolls</code> is used for the amount. | * <code>.setRolls</code> is used for the amount. | ||
− | * <code>.add</code> is used to add an <code> | + | * <code>.add</code> is used to add an <code>LootPoolEntryContainer$Builder</code>. You can have multiple entries. |
+ | |||
+ | A <code>LootPoolEntryContainer$Builder</code> defines what entry container is returned, and which functions and/or conditions are applied (see [https://minecraft.fandom.com/wiki/Loot_table Loot table] for the vanilla functions and conditions). | ||
+ | * <code>.when</code> is used to apply a condition. These themselves can have multiple operations. | ||
− | + | After all attributes have been added the <code>LootPool.Builder</code> can be used to make a <code>LootTable.Builder</code>. This builder can then be used in the subproviders as discussed above. | |
− | |||
− | |||
− | + | The <code>LootContextParamSet</code> is automatically applied by the vanilla <code>LootTableProvider.run</code> method. | |
<syntaxhighlight lang="java"> | <syntaxhighlight lang="java"> | ||
− | LootPool.Builder | + | LootPool.Builder poolBuilder = LootPool.lootPool() |
.name(...) | .name(...) | ||
.setRolls(...) | .setRolls(...) | ||
− | .add( | + | .add(LootItem.lootTableItem(...) |
− | .apply( | + | .apply(function1) |
− | .apply( | + | .apply(function2) |
− | . | + | .when(condition1) |
− | . | + | .when(condition2)) |
); | ); | ||
− | LootTable.lootTable().withPool( | + | LootTable.Builder tableBuilder = LootTable.lootTable().withPool(poolBuilder); |
</syntaxhighlight>[[Category:Data Generation]] | </syntaxhighlight>[[Category:Data Generation]] |
Latest revision as of 17:57, 20 September 2022
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 anLootPoolEntryContainer$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);