Difference between revisions of "Datageneration/Loot Tables"

From Forge Community Wiki
(WIP and it is horriable)
 
 
(25 intermediate revisions by 4 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 strickly needded.
 
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 witch 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() {
 +
        return List.of(
 +
            Pair.of(MyBlockLoot::new, LootContextParamSets.BLOCK),
 +
            Pair.of(MyEntityLoot::new, LootContextParamSets.ENTITY),
 +
            Pair.of(MyChestLoot::new, LootContextParamSets.CHEST));
 +
    }
 +
</syntaxhighlight>
  
  protected final Map<Block, LootTable.Builder> lootTables = new HashSet<>();
+
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).
  public static Map<ResourceLocation, LootTable> tables = new HashMap<>();
+
<syntaxhighlight lang="java">
  protected final DataGenerator generator;
+
    @Override
 +
    protected void validate(Map<ResourceLocation, LootTable> map, ValidationContext validationtracker) {
 +
        map.forEach((location, lootTable) -> LootTables.validate(validationtracker, location, lootTable);
 +
    }
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Also you would need a Function to save the Tables
+
==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">
  private void writeTables(DirectoryCache cache, Map<ResourceLocation, LootTable> tables) {
+
     @Override
     Path outputFolder = this.generator.getOutputFolder();
+
     protected void addTables() {
     tables.forEach((key, lootTable) -> {
+
        dropSelf(ModBlocks.BLOCK1.get());
      Path path = outputFolder.resolve("data/" + key.getNamespace() + "/loot_tables/" + key.getPath() + ".json");
+
         add(ModBlocks.BLOCK2.get(), ...);
      try {
+
     }
         IDataProvider.save(GSON, cache, LootTableManager.toJson(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.
+
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">
     lootTables.forEach(blockBuilderMap -> {
+
     @Override
      for (Map.Entry<Block, LootTable.Builder> entry : blockBuilderMap.entrySet()) {
+
    protected Iterable<Block> getKnownBlocks() {
         tables.put(entry.getKey().getLootTable(), entry.getValue().build());
+
         return ModBlocks.BLOCKS.getEntries().stream().map(RegistryObject::get).toList();
      }
+
     }
     });
 
 
</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.
+
===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 actual Class for Lootables ==
+
===The LootTable Builder===
=== Another class (Optional) ===
+
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.
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
+
* <code>.name</code> is used for the pool name.
 +
* <code>.setRolls</code> is used for the amount.
 +
* <code>.add</code> is used to add an <code>LootPoolEntryContainer$Builder</code>. You can have multiple entries.
  
=== The LootPool Builder ===
+
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).
This is where the magic happens /s.
+
* <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">
 +
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);
 +
</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 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);