Skip to content

Custom Datagen Base Classes

IchHabeHunger54 edited this page Jun 16, 2023 · 2 revisions

In order to add your own custom data generators, you can either extend AbstractDataProvider or AbstractRecipeProvider, depending on your usecase. The way they work is pretty much the same (see the bottom of this page for the differences). Since this is better shown with an example, let's just say you're adding crushing recipes, because that's something no mod has ever done before. In any place you see a variation of the word "crushing", you can use the thing you're actually datagenning instead.

Our generator consists of two classes: the provider and the builder. The builder is used to build single crushing recipe instances, while the provider gets all the crushing recipe instances added, and in the end writes them to disk. If you want, you can make the builder an inner class of the provider, however this is not required.

The builder

Let's start with the builder, because that is generally the easier part:

public class CrushingBuilder extends AbstractRecipeBuilder<CrushingBuilder> {
    public CrushingBuilder(ResourceLocation id, CrushingProvider provider) {
        super(id, provider);
    }

    @Override
    protected void toJson(JsonObject json) {
        
    }
}

The toJson method is where the magic happens. In here, everything needed is serialized onto the provided JsonObject. To get the values needed in toJson, you can and should add helper methods. For example, if our crushing recipe has a required Ingredient input and an optional secondary Ingredient input, the code would look something like this:

public class CrushingBuilder extends AbstractRecipeBuilder<CrushingBuilder> {
    private final Ingredient input;
    private Ingredient secondaryInput;

    public CrushingBuilder(ResourceLocation id, CrushingProvider provider, Ingredient input) {
        super(id, provider);
        this.input = input;
    }

    public CrushingBuilder setSecondaryInput(Ingredient secondaryInput) {
        this.secondaryInput = secondaryInput;
        return this;
    }

    @Override
    protected void toJson(JsonObject json) {
        json.add("input", input.toJson());
        if (secondaryInput != null) {
            json.add("secondary_input", secondaryInput.toJson());
        }
    }
}

Generally, it is recommended to make required properties final and have them be constructor parameters, and have optional properties be handled by setter methods.

The provider

Our provider is comparably simple:

public abstract class CrushingProvider extends AbstractRecipeProvider<CrushingBuilder> {
    public CrushingProvider(String namespace, DataGenerator generator) {
        super("crushing", namespace, generator);
    }

    @Override
    public String getName() {
        return "Crushing[" + namespace + "]";
    }

    public CrushingBuilder builder(String id, Ingredient input) {
        return new CrushingBuilder(new ResourceLocation(namespace, id), this, input); //everything after `this` should match the builder's constructor
    }
}

You can add as many builder() methods as you like. It is not required, but highly recommended to have at least one.

The provider can then be extended, like so:

public class MyCrushingProvider extends CrushingProvider {
    public MyCrushingProvider(DataGenerator generator) {
        super("mymodid", generator);
    }

    @Override
    public void generate() {
        //add things to generate here, for example:
        builder("tin_ore_crushing", Ingredient.of(MyItems.TIN_ORE.get()))
                .setSecondaryInput(Ingredient.of(Items.CHARCOAL))
                .build(); //don't forget the build() call!
    }
}

And that mod-specific provider can then be added in the GatherDataEvent like any other provider.

PotentiallyAbsentItemStack

As you might have noticed, using Ingredient or Item doesn't work when using items that are not in any loaded mod. For example, if you wanted to use Create's experience nuggets in a recipe, how are you supposed to get it? PotentiallyAbsentItemStack solves exactly that problem. Using it instead of ItemStacks (especially when handling results and the like) effectively fakes an ItemStack to the generator, allowing the use of unknown item ids when generating. Simply create a new PotentiallyAbsentItemStack(new ResourceLocation("thirdpartymodid:thirdpartymoditem"), 10); and call toJson() on it where needed.

There is also PotentiallyAbsentFluidStack, which similarly fakes a FluidStack to the generator where needed. Both item and fluid variants each also have a WithChance subvariant that additionally accepts a chance parameter and adds it to the JSON when serializing.

AbstractDataProvider vs. AbstractRecipeProvider

You might remember the very beginning of this page, where AbstractDataProvider was mentioned. AbstractRecipeProvider is a subclass of AbstractDataProvider that is intended to be used for (you guessed it) recipes. Similarly, AbstractRecipeBuilder is a subclass of AbstractDataBuilder with additional functionality for recipes. The differences are as follows:

  • AbstractRecipeProvider always outputs into the data/<namespace>/recipes folder, whereas AbstractDataProvider outputs directly into the data/<namespace> folder. If the recipe type is not the same as the provider's namespace (which means that it's another mod), the recipe will be put in data/<namespace>/recipes/compat/<recipetypenamespace>/<recipetypepath> - so for example, if you add Create crushing recipes, then these will go into data/<namespace>/recipes/compat/create/crushing.
  • AbstractRecipeProvider automatically adds the recipe type as a property to the recipe JSON.
  • AbstractRecipeBuilder has support for recipe conditions. Add a condition using addCondition(). A forge:mod_loaded condition for the mod the recipe type is from is automatically added.

Long story short: If you're adding a new recipe type, use AbstractRecipeProvider and AbstractRecipeBuilder. If you're adding other, non-recipe datagennable objects, use AbstractDataProvider and AbstractDataBuilder instead.