Difference between revisions of "Datageneration/Loot Tables"

From Forge Community Wiki
 
(12 intermediate revisions by 3 users not shown)
Line 1: Line 1:
{{Under construction}}
+
==The LootTableProvider class==
Loot Tables are not so polished like the other Providers. You need need to do some work to get them to a good state.
+
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".
  
== Preperation ==
+
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.
First you would need a new Class that extend the <code>LootTableProvider</code>. In this class you would override the <code>act</code> and optionally <code>getName</code> Method.
 
In the <code>getName</code> you just return the Name shown in the Logs.
 
Also you should create a abstract Method which you would override in subclasses but this is not strictly needed.
 
Also you need a Gson constant. You would create it like this <code>  private static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create(); </code>
 
You should also save the <code>DataGenerator</code> for later use since you can't access from the <code>LootTableProvider</code>.
 
Also you would need two Maps, one with the the Class which is used to get the Lootable Resource Location in this example the Block and one the Loot Table Builder.
 
The second Map consist of a <code>ResourceLocation</code> and the actual <code>LootTable</code>. Look at the Code for more info.
 
  
 +
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">
  private static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
+
    @Override
 
+
    protected List<Pair<Supplier<Consumer<BiConsumer<ResourceLocation, LootTable.Builder>>>, LootContextParamSet>> getTables() {
  protected final Map<Block, LootTable.Builder> lootTables = new HashMap<>();
+
        return List.of(
  public static Map<ResourceLocation, LootTable> tables = new HashMap<>();
+
            Pair.of(MyBlockLoot::new, LootContextParamSets.BLOCK),
  protected final DataGenerator generator;
+
            Pair.of(MyEntityLoot::new, LootContextParamSets.ENTITY),
 +
            Pair.of(MyChestLoot::new, LootContextParamSets.CHEST));
 +
    }
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Also you would need a Function to save the Tables
+
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">
 
<syntaxhighlight lang="java">
  private void writeTables(DirectoryCache cache, Map<ResourceLocation, LootTable> tables) {
+
    @Override
    Path outputFolder = this.generator.getOutputFolder();
+
    protected void validate(Map<ResourceLocation, LootTable> map, ValidationContext validationtracker) {
    tables.forEach((key, lootTable) -> {
+
        map.forEach((location, lootTable) -> LootTables.validate(validationtracker, location, lootTable);
      Path path = outputFolder.resolve("data/" + key.getNamespace() + "/loot_tables/" + key.getPath() + ".json");
+
     }
      try {
 
        IDataProvider.save(GSON, cache, LootTableManager.serialize(lootTable), path);
 
      } catch (IOException e) {
 
        LOGGER.error("Couldn't write loot table {}", path, (Object) e);
 
      }
 
     });
 
  }
 
 
</syntaxhighlight>
 
</syntaxhighlight>
  
For the writeTables method you would need to convert the First Map to the second map, for this we need to iterate over the first map.
+
==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">
     lootTables.forEach(blockBuilderMap -> {
+
     @Override
      for (Map.Entry<Block, LootTable.Builder> entry : blockBuilderMap.entrySet()) {
+
    protected void addTables() {
         tables.put(entry.getKey().getLootTable(), entry.getValue().build());
+
        dropSelf(ModBlocks.BLOCK1.get());
      }
+
         add(ModBlocks.BLOCK2.get(), ...);
     });
+
     }
 
</syntaxhighlight>
 
</syntaxhighlight>
  
in the act Method you would then first call the Method where you create the tables or just create them in there (if you do you can ignore the next section), then you would convert the Tables and at last you would save the loottables.
+
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).
  
== The actual Class for Lootables ==
+
You must override <code>getKnownBlocks</code> or <code>getKnownEntities</code>, respectively, to return only the blocks or entity types registered by your mod.
Here's an example Lootprovider class. The class is abstract and will be extended by the actual lootprovider class. This is however, optional. The class could also be used directly.
 
 
<syntaxhighlight lang="java">
 
<syntaxhighlight lang="java">
public abstract class BaseLootTableProvider extends LootTableProvider {
+
    @Override
 
+
     protected Iterable<Block> getKnownBlocks() {
     private static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
+
        return ModBlocks.BLOCKS.getEntries().stream().map(RegistryObject::get).toList();
    private static final Logger LOGGER = LogManager.getLogger();
 
 
 
    protected final Map<Block, LootTable.Builder> lootTables = new HashMap<>();
 
    private final DataGenerator generator;
 
 
 
    public BaseLootTableProvider(DataGenerator dataGeneratorIn) {
 
        super(dataGeneratorIn);
 
        this.generator = dataGeneratorIn;
 
 
     }
 
     }
 +
</syntaxhighlight>
  
    protected abstract void addTables();
+
===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>.
    @Override
+
<syntaxhighlight lang="java">
    public void run(DirectoryCache cache) {
+
    public void accept(BiConsumer<ResourceLocation, LootTable.Builder> consumer) {
        addTables();
+
        consumer.accept({ResourceLocation}, {LootTable.Builder});
 
 
        Map<ResourceLocation, LootTable> tables = new HashMap<>();
 
        for (Map.Entry<Block, LootTable.Builder> entry : lootTables.entrySet()) {
 
            tables.put(entry.getKey().getLootTable(), entry.getValue().build());
 
        }
 
        writeTables(cache, tables);
 
 
     }
 
     }
 +
</syntaxhighlight>
  
    private void writeTables(DirectoryCache cache, Map<ResourceLocation, LootTable> tables) {
+
===The LootTable Builder===
        Path outputFolder = this.generator.getOutputFolder();
+
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.
        tables.forEach((key, lootTable) -> {
+
* <code>.name</code> is used for the pool name.
            Path path = outputFolder.resolve("data/" + key.getNamespace() + "/loot_tables/" + key.getPath() + ".json");
+
* <code>.setRolls</code> is used for the amount.
            try {
+
* <code>.add</code> is used to add an <code>LootPoolEntryContainer$Builder</code>. You can have multiple entries.
                IDataProvider.save(GSON, cache, LootTableManager.serialize(lootTable), path);
 
            } catch (IOException e) {
 
                LOGGER.error("Couldn't write loot table {}", path, (Object) e);
 
  
            }
+
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.
    }
 
  
    @Override
+
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.
    public String getName() {
 
        return "Example LootTables";
 
    }
 
}
 
</syntaxhighlight>
 
=== Another class (Optional) ===
 
Create a new class that extends from the Class you created in the Section above and override the abstract function in there you can begin to create your Lootables.
 
<syntaxhighlight lang="java">
 
public class LootTables extends BaseLootTableProvider{
 
  
    @Override
+
The <code>LootContextParamSet</code> is automatically applied by the vanilla <code>LootTableProvider.run</code> method.
    protected void addTables() {
 
lootTables.put(BLOCK, LootTable.Builder);
 
}
 
}
 
</syntaxhighlight>
 
=== The LootPool Builder ===
 
This is where you actually make a loottable (this is an explanation for a block loottable). If you have multiple blocks with similar loottables, making a general method could be a good idea. The method should return a <code>LootTable.Builder</code>. This builder can be made by using the <code>LootPool.lootPool()</code> method, but you still need to add attributes. You need a name for the pool, the amount you get and also "what" you get. The "what" can modified using functions and/or conditions (see [https://minecraft.fandom.com/wiki/Loot_table LootTables] for possible vanilla funtions and conditions). After having made the builder, you return <code>LootTable.lootTable().withPool(builder)</code>. A example of a "shulkerbox-like" block, copying its name, inventory and "energy" data to the block and restoring its contents.
 
 
<syntaxhighlight lang="java">
 
<syntaxhighlight lang="java">
protected LootTable.Builder createTable(String name, Block block) {
+
LootPool.Builder poolBuilder = LootPool.lootPool()
        LootPool.Builder builder = LootPool.lootPool()
+
    .name(...)
                .name(name)
+
    .setRolls(...)
                .setRolls(ConstantRange.exactly(1))
+
    .add(LootItem.lootTableItem(...)
                .add(ItemLootEntry.lootTableItem(block)
+
        .apply(function1)
                        .apply(CopyName.copyName(CopyName.Source.BLOCK_ENTITY))
+
        .apply(function2)
                        .apply(CopyNbt.copyData(CopyNbt.Source.BLOCK_ENTITY)
+
            .when(condition1)
                                .copy("inv", "BlockEntityTag.inv", CopyNbt.Action.REPLACE)
+
            .when(condition2))
                                .copy("energy", "BlockEntityTag.energy", CopyNbt.Action.REPLACE))
+
    );
                        .apply(SetContents.setContents()
+
LootTable.Builder tableBuilder = LootTable.lootTable().withPool(poolBuilder);
                                .withEntry(DynamicLootEntry.dynamicEntry(new ResourceLocation("minecraft", "contents"))))
+
</syntaxhighlight>[[Category:Data Generation]]
                );
 
        return LootTable.lootTable().withPool(builder).setParamSet(LootParameterSets.BLOCK);
 
    } 
 
</syntaxhighlight>
 
Thanks and creddits to McJty with his amazing tutorials: [https://wiki.mcjty.eu/modding/index.php?title=YouTube-Tutorials Full list of his tutorials] and [https://github.com/McJty/YouTubeModding14/tree/1.16/src/main/java/com/mcjty/mytutorial/datagen Lootprovider and other datagen classes]
 
[[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 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);