- Kotlin 100%
| gradle/wrapper | Initial commit | |
| repo/martian/dapper | Publish 1.4-beta | |
| src | Add more smelting helpers, clean up code, and document recipe utils | |
| .gitignore | We kinda need that jar | |
| build.gradle.kts | Add more smelting helpers, clean up code, and document recipe utils | |
| gradle.properties | Publish 1.4-beta | |
| license-mit.txt | Initial commit | |
| license-unlicense.txt | Initial commit | |
| readme.md | Add more smelting helpers, clean up code, and document recipe utils | |
| settings.gradle.kts | Initial commit | |
| todo.md | Initial commit | |
Dapper 🎩
Dapper is a library intending to abstract a large majority of the NeoForge data generation API.
Instead of going on about what Dapper does, I'll just show some examples of what it looks like:
// Recipe data generation in Java without Dapper// This will look basically the same in Kotlin without Dapperpublicclass MFRecipeProviderextendsRecipeProvider{publicMFRecipeProvider(PackOutputoutput,CompletableFuture<HolderLookup.Provider>registries){super(output,registries);}protectedvoidbuildRecipes(RecipeOutputrecipeOutput,HolderLookup.ProviderholderLookup){SimpleCookingRecipeBuilder.smelting(Ingredient.of(MFItems.RAW_RUBBER.get()),RecipeCategory.MISC,MFItems.RUBBER_INGOT.get(),0.1f,200).unlockedBy("has",has(MFItems.RAW_RUBBER)).save(recipeOutput,id("smelting/raw_rubber_to_rubber_ingot"));SimpleCookingRecipeBuilder.smelting(Ingredient.of(MFTags.Items.RUBBER_INGOT),RecipeCategory.MISC,MFItems.RAW_PLASTIC,0.1f,200).unlockedBy("has",has(MFItems.RUBBER_INGOT)).save(recipeOutput,id("smelting/rubber_ingot_to_raw_plastic"));ShapedRecipeBuilder.shaped(RecipeCategory.MISC,MFItems.PLASTIC_SHEETS,4).pattern("pp").pattern("pp").define('p',MFItems.RAW_PLASTIC).unlockedBy("has",has(MFItems.RAW_PLASTIC)).save(recipeOutput,id("shaped/plastic_sheets"));}}// With Dapper, this can be changed into this:
class MFRecipeProvider(event: GatherDataEvent) : DapperRecipeProvider(event) {
override fun buildRecipes() {
// These parentheses are here to allow us to use infix notation on another line
(MFItems.RAW_RUBBER smeltsTo MFItems.RUBBER_INGOT
unlockWith MFItems.RAW_RUBBER
save "smelting/rubber_ingot_from_raw_rubber".id)
(MFItems.RUBBER_INGOT smeltsTo MFItems.RAW_PLASTIC
unlockWith MFItems.RUBBER_INGOT
save "smelting/raw_plastic_from_rubber_ingot".id)
(MFItems.PLASTIC_SHEETS.shapedRecipeBuilder(count = 4)
pattern2x2 MFItems.RAW_PLASTIC
unlockWith MFItems.RAW_PLASTIC
save "shaped/plastic_sheets".id)
// Alternatively, you can use Kotlin's .apply:
MFItems.RAW_RUBBER.smeltsTo(MFItems.RUBBER_INGOT).apply {
unlockWith(MFItems.RAW_RUBBER)
save("smelting/rubber_ingot_from_raw_rubber".id)
}
MFItems.RUBBER_INGOT.smeltsTo(MFItems.RAW_PLASTIC).apply {
unlockWith(MFItems.RUBBER_INGOT)
save("smelting/raw_plastic_from_rubber_ingot".id)
}
MFItems.PLASTIC_SHEETS.shapedRecipeBuilder(count = 4).apply {
pattern2x2(MFItems.RAW_PLASTIC)
unlockWith(MFItems.RAW_PLASTIC)
save("shaped/plastic_sheets".id)
}
}
}
Usage
Buildscript time :D
Add the following to your buildscript:
repositories {
// Groovy
maven {
url = "https://codeberg.org/EmmaTheMartian/dapper/raw/branch/main/repo/"
}
// Kotlin
maven("https://codeberg.org/EmmaTheMartian/dapper/raw/branch/main/repo/")
}
dependencies {
// Architectury Loom and ModDevGradle
implementation("martian:dapper:1.0.0")
// NeoGradle
implementation(fg.deobf("martian:dapper:1.0.0"))
}
If your codebase is written in Java, but you want to use Dapper as a Kotlin library, you can do that! Although it takes a good bit more buildscript effort.
To do that in Architectury Loom, expand the details/summary block below
I am unfamiliar with ModDevGradle and NeoGradle, so I do not have a guide for it quite yet.
(this is the details/summary block)
To do this, we will be adding the Kotlin JVM plugin and making a new source-set
specifically for data generation. You can also make this source set just be the
same one used for generated assets (src/generated/) if you prefer.
// This buildscript is written in Kotlin, but most of it works in Groovy
// without too many changes.
// Add the Kotlin JVM plugin to allow us to compile Kotlin code
plugins {
id("org.jetbrains.kotlin.jvm") version "2.0.0"
}
repositories {
// You will see why this is needed once we get to the `dependencies` block.
maven("https://thedarkcolour.github.io/KotlinForForge/")
}
// Set the Kotlin compiler to target Java 21
kotlin.compilerOptions.jvmTarget = JvmTarget.JVM_21
sourceSets {
// Make the `main` source set also use the generated resources
// You may already have this if you have used data generation already
main {
resources {
srcDir("src/generated/resources")
exclude("*.cache/")
}
}
// Make the `data` source set to contain our new data generation code
create("data") {
kotlin {
srcDirs("src/data/kotlin")
}
// This allows us to reference code in `src/main/` from `src/data/`
compileClasspath += main.get().compileClasspath + main.get().output
runtimeClasspath += main.get().runtimeClasspath + main.get().output
}
}
// Tell Kotlin that `src/data/kotlin/` is source that it should compile
tasks.compileKotlin {
source(sourceSets["data"].kotlin)
}
loom {
runs {
// You may already have a run task for data generation, but you will
// still need to modify it a little bit.
register("data") {
data()
// This is the important line, it tells the data generator to use
// the `src/data/` source set for its source.
source(sourceSets["data"])
programArgs("--all", "--mod", prop("mod_id"),
"--output", file("src/generated/resources/").absolutePath,
"--existing", file("src/main/resources/").absolutePath)
}
}
}
// Next, we need Kotlin to use Dapper. We could just depend on all the Kotlin
// stdlib manually, but another way it just to simply use KotlinForForge as a
// dependency. This will only be used for data generation, so KFF will not be
// needed for users downloading your mod.
dependencies {
implementation("thedarkcolour:kotlinforforge-neoforge:${prop("kff_version")}") {
// https://github.com/thedarkcolour/KotlinForForge/issues/103
exclude(group = "net.neoforged.fancymodloader", module = "loader")
}
}
// This isn't needed, but I highly recommend it. This line excludes our `data`
// source set from any jars we build.
tasks.withType<Jar>().configureEach {
// This should be the package containing your datagen code but with slashes
// instead of dots, and with a wildcard (asterisk) at the end.
exclude("com/example/datagen/*")
}
And we are done with the buildscript changes!
Now for the actual code. This is all going to be in Kotlin however you can translate to Java if you would like. However, Dapper is designed with Kotlin in mind so the API may not feel too comfortable to work with in Java.
Dapper makes a lot of helper methods for assorted things, so I will include the imports for those whenever I use them. Unfortunately Kotlin is not able to do wildcard imports of an object ;-;
First we need to make an event bus subscriber for the GatherDataEvent:
@EventBusSubscriber(modid = YourModClass.MODID, bus = EventBusSubscriber.Bus.MOD)
object ModDataGenerator {
@JvmStatic
@SubscribeEvent
fun onGatherData(event: GatherDataEvent) {
// We will start adding to this once we make our first data provider.
}
}
Now we can make a provider. We will use a recipe provider as the example.
// The infix functions seen here are all from martian.dapper.api.server.recipe.DapperShapedRecipeUtil
class MFRecipeProvider(event: GatherDataEvent) : DapperRecipeProvider(event) {
override fun buildRecipes() {
(Items.COAL smeltsTo Items.DIAMOND
unlockWith Items.COAL
save ResourceLocation.fromNamespaceAndPath("modid", "smelting/coal_to_diamond"))
// ofStack is an infix function at martian.dapper.api.ofStack
(Items.DIAMOND smeltsTo (Items.EMERALD ofStack 4)
unlockWith Items.DIAMOND
save ResourceLocation.fromNamespaceAndPath("modid", "smelting/diamond_to_emeralds"))
// This is an example straight from one of my own mods
(MFItems.PLASTIC_SHEETS.shapedRecipeBuilder(count = 4)
pattern2x2 MFItems.RAW_PLASTIC
unlockWith MFItems.RAW_PLASTIC
save "shaped/plastic_sheets".id)
// An alternate method of writing it is:
// MFItems.PLASTIC_SHEETS.shapedRecipeBuilder(count = 4).apply {
// pattern2x2(MFItems.RAW_PLASTIC)
// unlockWith(MFItems.RAW_PLASTIC)
// save("shaped/plastic_sheets".id)
// }
}
}
For this to actually do anything, we need to add it to our ModDataGenerator
object that we made above:
// In `#onGatherData`
// `GatherDataEvent#addProviders` and `DataGenerator#add` are extension
// methods, which can each be found here respectively:
// import martian.minefactorial.datagen.api.add
// import martian.minefactorial.datagen.api.addProviders
event.addProviders(
server = { it: DataGenerator ->
it.add(ModRecipeProvider(event))
}
)
Now we can run the gradle runData to run our brand-new data generator.
License
Dapper is dual-licensed under MIT and the Unlicense.
Some people have a vendetta against crayon licenses regardless of it being just a mod for a block-game. Along with that, I have heard that some countries apparently don't let developers put their code into the public domain, meaning the Unlicense is unusable to them. For these two reasons, Dapper is dual-licensed. Pick whichever one works better for you.
How Dapper got its Name
Data API -> DAPI ("dappy") -> Dapper