Custom Recipes
If a mod supports Datapack recipes, you can add recipes for it without any addon mod support! If a mod has a GitHub repository or other source code, you can find the relevant JSON files in /src/generated/resources/data/<modname>/recipes/. Otherwise, you may be able to find it by unzipping the mod's .jar file.
Writing a custom recipe
Here's an example of adding a Farmer's Delight cutting board recipe, which defines an input, output, and tool taken straight from their GitHub. Notice how the variables match between the datapack file and an event.custom recipe replicating it.
Looping
Of course, this starts becoming powerful when you start to combine them with a loop, function or otherwise, allowing you to easily add tons of custom recipes without having to manage a single datapack file.
let redstoneTransmute = (input, output) => {
event.custom({
"type": "tconstruct:casting_basin",
"cast": { "item": input },
"cast_consumed": true,
"fluid": {
"name": "thermal:redstone",
"amount": 50
},
"result": output,
"cooling_time": 30
})
}
redstoneTransmute("minecraft:cobblestone", "minecraft:netherrack")
redstoneTransmute("minecraft:sand", "minecraft:red_sand")
// etc...
Recipe Schemas
Work In Progress
The contents found here are not completely documented, please consider submitting a Pull Request with additional information.
Note
KubeJS 1.21 handles this differently.
Now, if you have a recipe type that you use frequently, it might be worth writing your own recipe schema for it. Recipe schemas are sets of keys that define how a recipe is constructed. How this works is covered in more detail in the reflection chapter (for those unfamiliar with Java, please read that first), and assumes you have at least basic knowledge of how recipes work.
Defining the Recipe Schema
To begin, you first need to define the schema you're going to use in your startup scripts.
What Goes In?
Let's use Extended Crafting's Combination Crafting as an example. It might seem complicated compared to some other recipe types, but it's manageable once you break it down. As a refresher, here's how it works if you haven't used Extended Crafting before.
Here's a datapack example:
{
"type": "extendedcrafting:combination",
"powerCost": 400000,
"input": {
"item": "minecraft:iron_ingot"
},
"ingredients": [
{
"item": "minecraft:potato"
},
{
"item": "minecraft:potato"
},
{
"item": "minecraft:potato"
},
{
"item": "minecraft:potato"
},
{
"item": "minecraft:potato"
},
{
"item": "minecraft:potato"
},
{
"item": "minecraft:potato"
},
{
"item": "minecraft:potato"
}
],
"result": {
"item": "minecraft:stone"
}
}
Let's break it down into it's parts:
-
An ItemStack
inputfield -
An ItemStack
ingredientsarray -
An ItemStack
resultfield -
An integer
powerCostfield
Reviewing the Java implementation reveals an additional field:
- An optional integer
power_ratefield
The Java implementation
public static class Serializer implements RecipeSerializer<CombinationRecipe> {
public static final MapCodec<CombinationRecipe> CODEC = RecordCodecBuilder.mapCodec(builder ->
builder.group(
Ingredient.CODEC_NONEMPTY.fieldOf("input").forGetter(recipe -> recipe.input),
Ingredient.CODEC_NONEMPTY
.listOf()
.fieldOf("ingredients")
.flatXmap(
field -> {
var max = 48;
var ingredients = field.toArray(Ingredient[]::new);
if (ingredients.length == 0) {
return DataResult.error(() -> "No ingredients for Combination recipe");
} else {
return ingredients.length > max
? DataResult.error(() -> "Too many ingredients for Combination recipe. The maximum is: %s".formatted(max))
: DataResult.success(NonNullList.of(Ingredient.EMPTY, ingredients));
}
},
DataResult::success
)
.forGetter(recipe -> recipe.inputs),
ItemStack.STRICT_CODEC.fieldOf("result").forGetter(recipe -> recipe.result),
Codec.INT.fieldOf("power_cost").forGetter(recipe -> recipe.powerCost),
Codec.INT.optionalFieldOf("power_rate", ModConfigs.CRAFTING_CORE_POWER_RATE.get()).forGetter(recipe -> recipe.powerRate)
).apply(builder, CombinationRecipe::new)
);
}
Schema Creation
After reviewing KubeJS's schema and component classes, you can create the schema using ItemComponents and NumberComponent:
const $RecipeSchema = Java.loadClass("dev.latvian.mods.kubejs.recipe.schema.RecipeSchema"); // (1)
const $ItemComponents = Java.loadClass("dev.latvian.mods.kubejs.recipe.component.ItemComponents");
const $NumberComponent = Java.loadClass("dev.latvian.mods.kubejs.recipe.component.NumberComponent");
StartupEvents.recipeSchemaRegistry(event => {
event.register("extendedcrafting:combination", new $RecipeSchema( // (2)
$ItemComponents.OUTPUT.key("result"),
$ItemComponents.INPUT.key("input"),
$ItemComponents.UNWRAPPED_INPUT_ARRAY.key("ingredients"),
$NumberComponent.INT.key("powerCost").optional(500000).preferred("powerCost"), // (3)
$NumberComponent.INT.key("powerRate").defaultOptional().preferred("powerRate")
)
);
})
- Imports the
RecipeSchemaclass, needed to write your own recipe schemas - Note that the order of the keys does not have to match the java code or any existing datapack files, so feel free to organize it into whatever way is most comfortable for writing with.
- If not set, defaults to a value of 500000
Now for the fun part: Actually using it!