Skip to content

Data Generation

JohnSmith474 edited this page Sep 21, 2026 · 14 revisions

While writing JSON files by hand is perfectly valid, it can quickly become tedious and prone to syntax errors—especially when dealing with deeply nested objects like the Spell Field API. Thankfully, EnchantmentCore is built natively on Mojang's serialization framework DataFixerUpper utilizing Codecs. This means every component, effect, and parameter fully supports Minecraft's Data Generation (Datagen) ecosystem.

When maintaining a mod across Fabric, Forge, and NeoForge, duplicating data generation logic is an immense waste of time. Because the output of datagen (JSON files) is platform-agnostic, you can establish a Single Source of Truth for your entire workspace.

This guide covers how to set up a unified datagen pipeline, construct enchantments scalably, and seamlessly integrate dynamic configuration wrappers.


1: Workspace Setup

The strategy is to write all Data Providers using vanilla APIs inside the common project, execute the Data Generator using your preferred loader platform as the "driver," and route the output back into a shared generated folder within common.

1.1 Common Project Configuration

First, instruct Gradle to treat a newly created generated directory inside common as a valid resource folder, and expose the directories to the platform projects.

In your common/build.gradle:

sourceSets {
    main {
        resources {
            // Include standard resources
            srcDirs += ['src/main/resources']
            // Include our routed datagen output
            srcDirs += ['src/main/generated']
        }
    }
}

artifacts {
    // Expose directory elements to platform projects
    commonJava sourceSets.main.java.sourceDirectories.elements
    commonResources sourceSets.main.resources.sourceDirectories.elements
}

1.2 Multiloader Dependency Fix

Due to strict type enforcement in Gradle 8+, the multiloader script needs explicit file collections for the common dependencies.

In your buildSrc/src/main/groovy/multiloader-loader.gradle, update the dependencies block:

dependencies {
    compileOnly(project(':common')) {
        capabilities {
            requireCapability "$group:$mod_id"
        }
    }
    // Extract source directories into explicit file collections
    commonJava files(project(":common").sourceSets.main.java.srcDirs)
    commonResources files(project(":common").sourceSets.main.resources.srcDirs)
}

2: Platform Drivers

You only need to run the datagen task from one platform, but you can configure all of them to support it.

Fabric Setup

fabric/build.gradle:

loom {
    runs {
        datagen {
            inherit client
            name "Data Generation"
            vmArg "-Dfabric-api.datagen"
            vmArg "-Dfabric-api.datagen.output-dir=${project(":common").file("src/main/generated")}"
            vmArg "-Dfabric-api.datagen.modid=${mod_id}"
            runDir "build/datagen"
        }
    }
}

fabric/src/main/resources/fabric.mod.json:

{
    "entrypoints": {
        "fabric-datagen": [
            "com.yourname.yourmod.datagen.FabricDatagenEntrypoint"
        ]
    }
}

FabricDatagenEntrypoint.java:

public class FabricDatagenEntrypoint implements DataGeneratorEntrypoint {
    @Override
    public void onInitializeDataGenerator(FabricDataGenerator fabricDataGenerator) {
        FabricDataGenerator.Pack pack = fabricDataGenerator.createPack();
        pack.addProvider((output, registries) -> new FabricDynamicRegistryProvider(output, registries) {
            @Override
            protected void configure(HolderLookup.Provider registries, Entries entries) {
                entries.addAll(registries.lookupOrThrow(Registries.ENCHANTMENT));
            }
            @Override
            public String getName() { return "My Mod Datapack"; }
        });
    }

    @Override
    public void buildRegistry(RegistrySetBuilder registryBuilder) {
        registryBuilder.add(Registries.ENCHANTMENT, EnchantmentRegistryBootstrap::bootstrap);
    }
}
NeoForge Setup

neoforge/build.gradle:

neoforge {
    runs {
        data {
            data()
            programArguments.addAll '--mod', mod_id,
                    '--all',
                    '--output', rootProject.file("common/src/main/generated").absolutePath,
                    '--existing', rootProject.file("common/src/main/resources").absolutePath
        }
    }
}

NeoForgeDatagenEntrypoint.java:

@EventBusSubscriber(modid = Constants.MOD_ID, bus = EventBusSubscriber.Bus.MOD)
public class NeoForgeDatagenEntrypoint {
    @SubscribeEvent
    public static void onGatherData(GatherDataEvent event) {
        DataGenerator generator = event.getGenerator();
        PackOutput output = generator.getPackOutput();
        RegistrySetBuilder builder = new RegistrySetBuilder()
                .add(Registries.ENCHANTMENT, EnchantmentRegistryBootstrap::bootstrap);

        generator.addProvider(
                event.includeServer(),
                new net.neoforged.neoforge.common.data.DatapackBuiltinEntriesProvider(
                        output, event.getLookupProvider(), builder, Set.of(Constants.MOD_ID)
                )
        );
    }
}
Forge Setup

forge/build.gradle:

minecraft {
    runs {
        data {
            workingDirectory project.file('run-data')
            args '--mod', mod_id,
                 '--all',
                 '--output', rootProject.file("common/src/main/generated").absolutePath,
                 '--existing', rootProject.file("common/src/main/resources").absolutePath
            mods { "${mod_id}" { source sourceSets.main } }
        }
    }
}

ForgeDatagenEntrypoint.java:

@Mod.EventBusSubscriber(modid = Constants.MOD_ID, bus = Mod.EventBusSubscriber.Bus.MOD)
public class ForgeDatagenEntrypoint {
    @SubscribeEvent
    public static void onGatherData(GatherDataEvent event) {
        DataGenerator generator = event.getGenerator();
        PackOutput output = generator.getPackOutput();
        RegistrySetBuilder builder = new RegistrySetBuilder()
                .add(Registries.ENCHANTMENT, EnchantmentRegistryBootstrap::bootstrap);

        generator.addProvider(
                event.includeServer(),
                new net.minecraftforge.common.data.DatapackBuiltinEntriesProvider(
                        output, event.getLookupProvider(), builder, Set.of(Constants.MOD_ID)
                )
        );
    }
}

3: Defining Enchantments

If you put all your enchantment logic in a single provider class, it will quickly become an unmanageable monolith. Instead, encapsulate each enchantment in its own class using static properties and Java Functions.

This guarantees registry tags and items are evaluated only when the BootstrapContext is actually ready and provided by the datagenerator.

ExplosiveStrike.java (Inside common project):

package com.example.mod.datagen.enchantment;

import johnsmith.enchantmentcore.enchantment.effect.ExplosionDefinition;
import johnsmith.enchantmentcore.enchantment.effect.ExplosionEffect;
import johnsmith.enchantmentcore.enchantment.value.PolynomialValue;

import net.minecraft.core.registries.Registries;
import net.minecraft.data.worldgen.BootstrapContext;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.ItemTags;
import net.minecraft.world.entity.EquipmentSlotGroup;
import net.minecraft.world.item.enchantment.Enchantment;
import net.minecraft.world.item.enchantment.EnchantmentEffectComponents;
import net.minecraft.world.item.enchantment.EnchantmentTarget;
import net.minecraft.world.item.enchantment.LevelBasedValue;

import java.util.Optional;
import java.util.function.Function;

public class ExplosiveStrike {
    // 1. Define the ResourceKey
    public static final ResourceKey<Enchantment> KEY = ResourceKey.create(
            Registries.ENCHANTMENT, ResourceLocation.fromNamespaceAndPath("example_mod", "explosive_strike")
    );

    // 2. Build the baseline definition using a Function
    public static final Function<BootstrapContext<Enchantment>, Enchantment.EnchantmentDefinition> DEFINITION = (context) -> 
            Enchantment.definition(
                    context.lookup(Registries.ITEM).getOrThrow(ItemTags.SWORD_ENCHANTABLE),
                    2, // Weight
                    5, // Max Level (This will be our fallback value later)
                    Enchantment.dynamicCost(10, 5), // Min cost
                    Enchantment.dynamicCost(30, 5), // Max cost
                    4, // Anvil cost
                    EquipmentSlotGroup.MAINHAND
            );

    // 3. Construct the EnchantmentCore specific payload
    public static final ExplosionEffect EFFECT = new ExplosionEffect(
            LevelBasedValue.constant(1.0f),
            // 3.1 Construct the custom effect payload
            new ExplosionDefinition(
                    // 3.1.1 Construct a LevelBasedValue
                    new PolynomialValue(1.5f, 1.2f, 0.0f, 0.0f),
                    false, // createFire
                    true,  // damageEntities
                    ExplosionDefinition.ExplosionInteraction.NONE,
                    Optional.empty(), // Immune filter
                    Optional.empty(), // Default small particles
                    Optional.empty(), // Default large particles
                    Optional.empty()  // Default sound
            )
    );

    // 4. Assemble the final builder
    public static final Function<BootstrapContext<Enchantment>, Enchantment> ENCHANTMENT = (context) ->
            Enchantment.enchantment(ExplosiveStrike.DEFINITION.apply(context))
                       .withEffect(EnchantmentEffectComponents.POST_ATTACK,
                                   EnchantmentTarget.ATTACKER,
                                   EnchantmentTarget.VICTIM,
                                   ExplosiveStrike.EFFECT
                       ).build(ExplosiveStrike.KEY.location());
}

Now, your central bootstrap class remains incredibly clean. You simply mass-register your modular enchantments:

EnchantmentRegistryBootstrap.java:

package com.example.mod.datagen;

import johnsmith.enchantmentcore.datagen.enchantment.ExplosiveStrike;

import net.minecraft.data.worldgen.BootstrapContext;
import net.minecraft.world.item.enchantment.Enchantment;

public class EnchantmentRegistryBootstrap {
    public static void bootstrap(BootstrapContext<Enchantment> context) {
        context.register(ExplosiveStrike.KEY, ExplosiveStrike.ENCHANTMENT.apply(context));
        // context.register(OtherEnchant.KEY, OtherEnchant.ENCHANTMENT.apply(context));
    }
}

4: Running Data Generation

Run the Gradle task for your chosen driver platform to serialize the vanilla-compliant JSON files into your common/src/main/generated/ folder.

  • Fabric: ./gradlew :fabric:runDatagen
  • NeoForge: ./gradlew :neoforge:runData
  • Forge: ./gradlew :forge:runData

5: Injecting Configuration Wrappers

Because Config Overhauled dynamic wrappers require live property evaluation to ensure fallback integrity, they cannot be injected natively during the headless Datagen phase. The generated files from Step 4 are standard, static vanilla JSONs.

To make your enchantments configurable, you will use Enchantment Core's in-game bulk processor.

  1. Prepare the Data: Move your generated static enchantments from the generated folder into your mod's primary data folder (e.g., src/main/resources/data/). This ensures the game actively loads them when you boot up the client.
  2. Launch the Game: Run the standard Minecraft client environment with your mod installed.
  3. Execute the Import Command: Enter a local single-player world and run the import command to map your enchantments to your configuration registry: /enchantment_core import <source_namespace> <target_namespace> Note: Replace <source_namespace> with your mod's namespace. The <target_namespace> specifies where the properties will be bound and MUST possess a registered ConfigManager via Config Overhauled.
  4. Update Your Workspace: The command automatically maps your enchantments through the DataTransformerRegistry, injects the { "config": {...}, "fallback": ... } JSON structures, and places the modified files in enchantment_core/import/<source_namespace>/enchantment/ inside your run directory.
  5. Overwrite: Move those modified JSON files directly into your common/src/main/generated/data/<source_namespace>/enchantment/ directory, overwriting the static files.

Your datapack is now perfectly formatted and fully bound to runtime configurations! Refer to the Config Overhauled wiki to generate translation keys for the configurable enchantments.

Clone this wiki locally