diff --git a/Common/src/extension/groovy/io/github/groovymc/cgl/api/extension/ItemExtensions.groovy b/Common/src/extension/groovy/io/github/groovymc/cgl/api/extension/ItemExtensions.groovy new file mode 100644 index 0000000..ae9c865 --- /dev/null +++ b/Common/src/extension/groovy/io/github/groovymc/cgl/api/extension/ItemExtensions.groovy @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2022 GroovyMC and contributors + * SPDX-License-Identifier: LGPL-3.0-or-later + */ + +package io.github.groovymc.cgl.api.extension + +import groovy.transform.CompileStatic +import net.minecraft.core.BlockPos +import net.minecraft.core.Holder +import net.minecraft.core.Position +import net.minecraft.nbt.CompoundTag +import net.minecraft.tags.TagKey +import net.minecraft.world.item.Item +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.crafting.Ingredient +import net.minecraft.world.level.ItemLike +import net.minecraft.world.phys.Vec3 +import org.codehaus.groovy.runtime.DefaultGroovyMethods + +@CompileStatic +class ItemExtensions { + static ItemStack count(ItemLike self, int count) { + new ItemStack(self, count) + } + + static ItemStack multiply(ItemLike self, int count) { + new ItemStack(self, count) + } + + static ItemStack multiply(Integer count, ItemLike item) { + new ItemStack(item, count) + } + + static ItemStack count(ItemStack self, int count) { + self.setCount(count) + return self + } + + static ItemStack tag(ItemStack self, CompoundTag tag) { + self.setTag(tag) + return self + } + + static ItemStack tag(ItemStack self, Object tag) { + self.setTag(StaticNBTExtensions.from(null, tag) as CompoundTag) + return self + } + + static Ingredient ingredient(ItemLike item) { + return Ingredient.of(item) + } + + static Ingredient ingredient(TagKey tag) { + return Ingredient.of(tag) + } + + static T asType(ItemStack self, Class type) { + return switch (type) { + case Ingredient -> (T) ingredient(self.item) + case ItemLike, Item -> (T) self.item + case Holder -> (T) self.itemHolder + default -> (T) DefaultGroovyMethods.asType(self, type) + } + } + + static T asType(ItemLike self, Class type) { + return switch (type) { + case Ingredient -> (T) ingredient(self.asItem()) + case ItemStack -> (T) self.asItem().defaultInstance + default -> (T) DefaultGroovyMethods.asType(self, type) + } + } +} diff --git a/Common/src/extension/groovy/io/github/groovymc/cgl/api/extension/StaticNBTExtensions.groovy b/Common/src/extension/groovy/io/github/groovymc/cgl/api/extension/StaticNBTExtensions.groovy index ae6a203..326b15a 100644 --- a/Common/src/extension/groovy/io/github/groovymc/cgl/api/extension/StaticNBTExtensions.groovy +++ b/Common/src/extension/groovy/io/github/groovymc/cgl/api/extension/StaticNBTExtensions.groovy @@ -45,6 +45,7 @@ final class StaticNBTExtensions { case Collection -> new ListTag().tap { list -> toConvert.each { list.add(from(null, it)) } } + case Map -> of(null, toConvert as Map) default -> (Tag) null } } diff --git a/Common/src/extension/resources/META-INF/groovy/org.codehaus.groovy.runtime.ExtensionModule b/Common/src/extension/resources/META-INF/groovy/org.codehaus.groovy.runtime.ExtensionModule index 92d50e2..bee32f7 100644 --- a/Common/src/extension/resources/META-INF/groovy/org.codehaus.groovy.runtime.ExtensionModule +++ b/Common/src/extension/resources/META-INF/groovy/org.codehaus.groovy.runtime.ExtensionModule @@ -1,4 +1,4 @@ moduleName=CGL Extensions moduleVersion=0.2.0 -extensionClasses=io.github.groovymc.cgl.api.extension.registry.RegistryExtension,io.github.groovymc.cgl.api.extension.math.ArithmeticExtension,io.github.groovymc.cgl.api.extension.chat.ComponentExtension,io.github.groovymc.cgl.api.extension.TagExtensions,io.github.groovymc.cgl.api.extension.client.MinecraftExtensions,io.github.groovymc.cgl.api.extension.NBTExtensions,io.github.groovymc.cgl.api.extension.CodecExtensions,io.github.groovymc.cgl.api.extension.brigadier.CommandExtensions,io.github.groovymc.cgl.api.extension.brigadier.CommandContextExtensions,io.github.groovymc.cgl.api.extension.brigadier.ArgumentExtensions,io.github.groovymc.cgl.api.extension.GeneralExtensions +extensionClasses=io.github.groovymc.cgl.api.extension.registry.RegistryExtension,io.github.groovymc.cgl.api.extension.math.ArithmeticExtension,io.github.groovymc.cgl.api.extension.chat.ComponentExtension,io.github.groovymc.cgl.api.extension.TagExtensions,io.github.groovymc.cgl.api.extension.client.MinecraftExtensions,io.github.groovymc.cgl.api.extension.NBTExtensions,io.github.groovymc.cgl.api.extension.CodecExtensions,io.github.groovymc.cgl.api.extension.brigadier.CommandExtensions,io.github.groovymc.cgl.api.extension.brigadier.CommandContextExtensions,io.github.groovymc.cgl.api.extension.brigadier.ArgumentExtensions,io.github.groovymc.cgl.api.extension.GeneralExtensions,io.github.groovymc.cgl.api.extension.ItemExtensions staticExtensionClasses=io.github.groovymc.cgl.api.extension.StaticGeneralExtensions,io.github.groovymc.cgl.api.extension.chat.StyleExtension,io.github.groovymc.cgl.api.extension.StaticNBTExtensions diff --git a/Common/src/main/groovy/io/github/groovymc/cgl/api/datagen/recipe/BaseRecipeBuilder.groovy b/Common/src/main/groovy/io/github/groovymc/cgl/api/datagen/recipe/BaseRecipeBuilder.groovy new file mode 100644 index 0000000..0504e2a --- /dev/null +++ b/Common/src/main/groovy/io/github/groovymc/cgl/api/datagen/recipe/BaseRecipeBuilder.groovy @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2022 GroovyMC and contributors + * SPDX-License-Identifier: LGPL-3.0-or-later + */ + +package io.github.groovymc.cgl.api.datagen.recipe + +import groovy.transform.CompileStatic +import net.minecraft.core.registries.BuiltInRegistries +import net.minecraft.data.recipes.RecipeBuilder +import net.minecraft.resources.ResourceLocation +import net.minecraft.world.item.Item +import net.minecraft.world.item.ItemStack +import net.minecraft.world.level.ItemLike + +@CompileStatic +trait BaseRecipeBuilder extends SaveableRecipe implements RecipeBuilder { + ItemStack result + + ItemStack result(ItemLike result) { + this.result = result.asItem().getDefaultInstance() + return this.result + } + + void setResult(ItemLike result) { + this.result(result) + } + + ItemStack result(ItemStack result) { + this.result = result + return this.result + } + + void setResult(ItemStack result) { + this.result(result) + } + + Item getResult() { + return this.@result.item + } + + ItemStack getResultStack() { + return this.@result + } + + /** + * Saves this recipe to a location representing the {@link #getResult() result's} registry name. + */ + void save() { + this.save(BuiltInRegistries.ITEM.getKey(getResult())) + } + + /** + * {@inheritDoc} + */ + void save(ResourceLocation location) { + provider.forgottenRecipes.remove(this) + this.save(this.getProvider().writer, location) + } +} diff --git a/Common/src/main/groovy/io/github/groovymc/cgl/api/datagen/recipe/GCookingRecipeBuilder.groovy b/Common/src/main/groovy/io/github/groovymc/cgl/api/datagen/recipe/GCookingRecipeBuilder.groovy new file mode 100644 index 0000000..8aa520a --- /dev/null +++ b/Common/src/main/groovy/io/github/groovymc/cgl/api/datagen/recipe/GCookingRecipeBuilder.groovy @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2022 GroovyMC and contributors + * SPDX-License-Identifier: LGPL-3.0-or-later + */ + +package io.github.groovymc.cgl.api.datagen.recipe + +import com.google.gson.JsonObject +import groovy.contracts.Requires +import groovy.time.TimeDuration +import groovy.transform.CompileStatic +import net.minecraft.advancements.Advancement +import net.minecraft.advancements.AdvancementRewards +import net.minecraft.advancements.RequirementsStrategy +import net.minecraft.advancements.critereon.RecipeUnlockedTrigger +import net.minecraft.core.registries.BuiltInRegistries +import net.minecraft.data.recipes.FinishedRecipe +import net.minecraft.resources.ResourceLocation +import net.minecraft.world.item.BlockItem +import net.minecraft.world.item.Item +import net.minecraft.world.item.crafting.AbstractCookingRecipe +import net.minecraft.world.item.crafting.CookingBookCategory +import net.minecraft.world.item.crafting.Ingredient +import net.minecraft.world.item.crafting.RecipeSerializer +import net.minecraft.world.level.ItemLike +import org.jetbrains.annotations.Nullable + +import java.util.function.Consumer + +@CompileStatic +class GCookingRecipeBuilder implements SimpleRecipeBuilder { + private CookingBookCategory bookCategory + private Ingredient ingredient + private float experience = 20 + private int cookingTime = 200 + private RecipeSerializer serializer + + GCookingRecipeBuilder() { + this(RecipeSerializer.SMELTING_RECIPE) + } + + GCookingRecipeBuilder(RecipeSerializer serializer) { + this.serializer = serializer + } + + GCookingRecipeBuilder serializer(RecipeSerializer serializer) { + return setSerializer(serializer) + } + + GCookingRecipeBuilder setSerializer(RecipeSerializer serializer) { + this.serializer = serializer + return this + } + + GCookingRecipeBuilder ingredient(Ingredient ingredient) { + setIngredient(ingredient) + return this + } + + void setIngredient(Ingredient ingredient) { + this.ingredient = ingredient + } + + void setExperience(float exp) { + this.experience = exp + } + + GCookingRecipeBuilder experience(float exp) { + setExperience(exp) + return this + } + + void setCookingTime(int cookingTime) { + this.cookingTime = cookingTime + } + + GCookingRecipeBuilder cookingTime(int cookingTime) { + setCookingTime(cookingTime) + return this + } + + void setCookingTime(TimeDuration cookingTime) { + this.cookingTime = cookingTime.seconds * 20 + } + + GCookingRecipeBuilder cookingTime(TimeDuration cookingTime) { + setCookingTime(cookingTime) + return this + } + + @Requires({ ingredient && serializer && cookingTime > 0 }) + void save(Consumer finishedRecipeConsumer, ResourceLocation recipeId) { + this.advancement.parent(ROOT_RECIPE_ADVANCEMENT).addCriterion('has_the_recipe', RecipeUnlockedTrigger.unlocked(recipeId)).rewards(AdvancementRewards.Builder.recipe(recipeId)).requirements(RequirementsStrategy.OR); + finishedRecipeConsumer.accept(new Result(recipeId, this.group ?: '', this.bookCategory ?: determineRecipeCategory(this.serializer, this.result), this.ingredient, this.result, this.experience, this.cookingTime, this.advancement, recipeId.withPrefix("recipes/" + this.category.getFolderName() + "/"), this.serializer)); + } + + private static CookingBookCategory determineRecipeCategory(RecipeSerializer serializer, ItemLike result) { + if (serializer == RecipeSerializer.SMELTING_RECIPE) { + if (result.asItem().isEdible()) { + return CookingBookCategory.FOOD + } else { + return result.asItem() instanceof BlockItem ? CookingBookCategory.BLOCKS : CookingBookCategory.MISC + } + } else if (serializer == RecipeSerializer.BLASTING_RECIPE) { + return result.asItem() instanceof BlockItem ? CookingBookCategory.BLOCKS : CookingBookCategory.MISC + } else { + return CookingBookCategory.MISC + } + } + + static class Result implements FinishedRecipe { + private final ResourceLocation id + private final String group + private final CookingBookCategory category + private final Ingredient ingredient + private final Item result + private final float experience + private final int cookingTime + private final Advancement.Builder advancement + private final ResourceLocation advancementId + private final RecipeSerializer serializer + + Result(ResourceLocation resourceLocation, String string, CookingBookCategory cookingBookCategory, Ingredient ingredient, Item item, float exp, int time, Advancement.Builder builder, ResourceLocation resourceLocation2, RecipeSerializer recipeSerializer) { + this.id = resourceLocation + this.group = string + this.category = cookingBookCategory + this.ingredient = ingredient + this.result = item + this.experience = exp + this.cookingTime = time + this.advancement = builder + this.advancementId = resourceLocation2 + this.serializer = recipeSerializer + } + + void serializeRecipeData(JsonObject json) { + if (!this.group.isEmpty()) { + json.addProperty('group', this.group); + } + + json.addProperty('category', this.category.getSerializedName()) + json.add('ingredient', this.ingredient.toJson()) + json.addProperty('result', BuiltInRegistries.ITEM.getKey(this.result).toString()) + json.addProperty('experience', this.experience) + json.addProperty('cookingtime', this.cookingTime) + } + + RecipeSerializer getType() { + this.serializer + } + + ResourceLocation getId() { + this.id + } + + @Nullable + JsonObject serializeAdvancement() { + this.advancement.criteria.size() == 1 ? null : this.advancement.serializeToJson() + } + + @Nullable + ResourceLocation getAdvancementId() { + this.advancementId + } + } +} diff --git a/Common/src/main/groovy/io/github/groovymc/cgl/api/datagen/recipe/GRecipeProvider.groovy b/Common/src/main/groovy/io/github/groovymc/cgl/api/datagen/recipe/GRecipeProvider.groovy new file mode 100644 index 0000000..5a05d67 --- /dev/null +++ b/Common/src/main/groovy/io/github/groovymc/cgl/api/datagen/recipe/GRecipeProvider.groovy @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2022 GroovyMC and contributors + * SPDX-License-Identifier: LGPL-3.0-or-later + */ + +package io.github.groovymc.cgl.api.datagen.recipe + +import groovy.transform.CompileStatic +import groovy.transform.stc.ClosureParams +import groovy.transform.stc.FirstParam +import groovy.transform.stc.SimpleType +import groovy.util.logging.Slf4j +import net.minecraft.advancements.Advancement +import net.minecraft.advancements.CriterionTriggerInstance +import net.minecraft.core.registries.BuiltInRegistries +import net.minecraft.data.PackOutput +import net.minecraft.data.recipes.FinishedRecipe +import net.minecraft.data.recipes.RecipeBuilder +import net.minecraft.data.recipes.RecipeCategory +import net.minecraft.data.recipes.RecipeProvider +import net.minecraft.resources.ResourceLocation +import net.minecraft.world.item.Item +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.crafting.RecipeSerializer +import net.minecraft.world.level.ItemLike +import org.jetbrains.annotations.Nullable + +import java.util.function.Consumer + +/** + *

A {@link RecipeProvider} with a few additions that better support Groovy.

+ *

All the G-recipe builders do not require an advancement criterion, unlike the vanilla recipe builders.

+ *

They are designed to be used along side closure and parenthesis-less calls to make them easier to both read and write.

+ * An example of how a {@link GShapelessRecipeBuilder} is inteded to be used: + *
+ * {@code
+ * shapeless {
+ *      result Items.SPRUCE_BUTTON * 12
+ *      requires Items.AZALEA
+ *
+ *      category RecipeCategory.DECORATIONS
+ *      group 'spruce_stuff'
+ * } save 'sprucex12'
+ * }
+ * 
+ */ +@Slf4j +@CompileStatic +abstract class GRecipeProvider extends RecipeProvider { + public Consumer writer + protected final String defaultNamespace + + protected final List forgottenRecipes = [] + + /** + * @param defaultNamespace the default namespace to be used by {@link SaveableRecipe#save(java.lang.String)} + */ + GRecipeProvider(PackOutput packOutput, String defaultNamespace = 'minecraft') { + super(packOutput) + this.defaultNamespace = defaultNamespace + } + + @Override + protected void buildRecipes(Consumer writer) { + this.writer = writer + this.buildRecipes() + this.writer = null + + if (!forgottenRecipes.isEmpty()) { + log.error("It seems like ${forgottenRecipes.size()} recipes created in provider ${this} have not been saved. You may save them automatically by calling GRecipeProvider#saveForgotten in buildRecipes().") + throw new RuntimeException("${forgottenRecipes.size()} recipes have not been saved!") + } + } + + /** + * Override this method to build your recipes.
+ * You may use the {@link #writer} as a {@code Consumer}, but it is not usually necessary as there are methods such as {@link #normal(net.minecraft.data.recipes.RecipeBuilder)} + * which allow you to more easily save recipes. + */ + protected abstract void buildRecipes() + + /** + * Saves all forgotten recipes of type {@link BaseRecipeBuilder}.
+ * Note that this means the recipes will be saved to a path representing the result's registry name. + */ + protected void saveForgotten() { + List.copyOf(this.forgottenRecipes).each { + if (it instanceof BaseRecipeBuilder) { + it.save() + } + } + } + + protected GSingleItemRecipeBuilder stonecutting(@DelegatesTo(value = GSingleItemRecipeBuilder, strategy = Closure.DELEGATE_FIRST) @ClosureParams(value = SimpleType, options = 'io.github.groovymc.cgl.api.datagen.GSingleItemRecipeBuilder') Closure clos) { + recipe(new GSingleItemRecipeBuilder(RecipeSerializer.STONECUTTER), clos) + } + + protected GSingleItemRecipeBuilder singleItem(@DelegatesTo(value = GSingleItemRecipeBuilder, strategy = Closure.DELEGATE_FIRST) @ClosureParams(value = SimpleType, options = 'io.github.groovymc.cgl.api.datagen.GSingleItemRecipeBuilder') Closure clos) { + recipe(new GSingleItemRecipeBuilder(), clos) + } + + protected GCookingRecipeBuilder smelting(@DelegatesTo(value = GCookingRecipeBuilder, strategy = Closure.DELEGATE_FIRST) @ClosureParams(value = SimpleType, options = 'io.github.groovymc.cgl.api.datagen.GCookingRecipeBuilder') Closure clos) { + recipe(new GCookingRecipeBuilder(), clos) + } + + protected GCookingRecipeBuilder blasting(@DelegatesTo(value = GCookingRecipeBuilder, strategy = Closure.DELEGATE_FIRST) @ClosureParams(value = SimpleType, options = 'io.github.groovymc.cgl.api.datagen.GCookingRecipeBuilder') Closure clos) { + recipe(new GCookingRecipeBuilder(RecipeSerializer.BLASTING_RECIPE), clos) + } + + protected GCookingRecipeBuilder campfireCooking(@DelegatesTo(value = GCookingRecipeBuilder, strategy = Closure.DELEGATE_FIRST) @ClosureParams(value = SimpleType, options = 'io.github.groovymc.cgl.api.datagen.GCookingRecipeBuilder') Closure clos) { + recipe(new GCookingRecipeBuilder(RecipeSerializer.CAMPFIRE_COOKING_RECIPE), clos) + } + + protected GCookingRecipeBuilder smoking(@DelegatesTo(value = GCookingRecipeBuilder, strategy = Closure.DELEGATE_FIRST) @ClosureParams(value = SimpleType, options = 'io.github.groovymc.cgl.api.datagen.GCookingRecipeBuilder') Closure clos) { + recipe(new GCookingRecipeBuilder(RecipeSerializer.SMOKING_RECIPE), clos) + } + + protected GShapedRecipeBuilder shaped(@DelegatesTo(value = GShapedRecipeBuilder, strategy = Closure.DELEGATE_FIRST) @ClosureParams(value = SimpleType, options = 'io.github.groovymc.cgl.api.datagen.GShapedRecipeBuilder') Closure clos) { + recipe(new GShapedRecipeBuilder(), clos) + } + + protected GShapelessRecipeBuilder shapeless(@DelegatesTo(value = GShapelessRecipeBuilder,strategy = Closure.DELEGATE_FIRST) @ClosureParams(value = SimpleType, options = 'io.github.groovymc.cgl.api.datagen.GShapelessRecipeBuilder') Closure clos) { + recipe(new GShapelessRecipeBuilder(), clos) + } + + protected T recipe(@DelegatesTo.Target('recipe') Class recipeClass, @DelegatesTo(target = 'recipe', genericTypeIndex = 0, strategy = Closure.DELEGATE_FIRST) @ClosureParams(FirstParam.FirstGenericType) Closure clos) { + return recipe(recipeClass.getDeclaredConstructor().newInstance(), clos) + } + + protected T recipe(@DelegatesTo.Target('recipe') T recipe, @DelegatesTo(target = 'recipe', strategy = Closure.DELEGATE_FIRST) @ClosureParams(FirstParam) Closure clos) { + recipe.provider = this + clos.resolveStrategy = Closure.DELEGATE_FIRST + clos.delegate = recipe + clos(recipe) + forgottenRecipes.add(recipe) + return recipe + } + + protected SaveableRecipe normal(@DelegatesTo.Target('recipe') T recipe, @DelegatesTo(target = 'recipe', strategy = Closure.DELEGATE_FIRST) @ClosureParams(FirstParam) Closure clos = {}) { + clos.resolveStrategy = Closure.DELEGATE_FIRST + clos.delegate = recipe + clos(recipe) + return new SaveableRecipe() { + @Override + void save(ResourceLocation location) { + forgottenRecipes.remove(this) + recipe.save(getProvider().writer, location) + } + + @Override + GRecipeProvider getProvider() { + return GRecipeProvider.this + } + + @Override + void setProvider(GRecipeProvider provider) { + + } + } + } +} \ No newline at end of file diff --git a/Common/src/main/groovy/io/github/groovymc/cgl/api/datagen/recipe/GShapedRecipeBuilder.groovy b/Common/src/main/groovy/io/github/groovymc/cgl/api/datagen/recipe/GShapedRecipeBuilder.groovy new file mode 100644 index 0000000..774d1b0 --- /dev/null +++ b/Common/src/main/groovy/io/github/groovymc/cgl/api/datagen/recipe/GShapedRecipeBuilder.groovy @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2022 GroovyMC and contributors + * SPDX-License-Identifier: LGPL-3.0-or-later + */ + +package io.github.groovymc.cgl.api.datagen.recipe + +import com.google.common.collect.Lists +import com.google.common.collect.Maps +import com.google.common.collect.Sets +import com.google.gson.JsonArray +import com.google.gson.JsonObject +import groovy.transform.CompileStatic +import net.minecraft.advancements.Advancement +import net.minecraft.advancements.AdvancementRewards +import net.minecraft.advancements.RequirementsStrategy +import net.minecraft.advancements.critereon.RecipeUnlockedTrigger +import net.minecraft.core.registries.BuiltInRegistries +import net.minecraft.data.recipes.CraftingRecipeBuilder +import net.minecraft.data.recipes.FinishedRecipe +import net.minecraft.resources.ResourceLocation +import net.minecraft.tags.TagKey +import net.minecraft.world.item.Item +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.crafting.CraftingBookCategory +import net.minecraft.world.item.crafting.Ingredient +import net.minecraft.world.item.crafting.RecipeSerializer +import net.minecraft.world.level.ItemLike +import org.jetbrains.annotations.Nullable + +import java.util.function.Consumer + +@CompileStatic +class GShapedRecipeBuilder extends CraftingRecipeBuilder implements SimpleRecipeBuilder { + private final List rows = Lists.newArrayList() + private final Map key = Maps.newLinkedHashMap() + private boolean showNotification = true + + GShapedRecipeBuilder define(String symbol, TagKey tag) { + this.define(symbol, Ingredient.of(tag)) + } + + GShapedRecipeBuilder define(String symbol, ItemLike item) { + this.define(symbol, Ingredient.of(item)) + } + + GShapedRecipeBuilder define(String symbol, Ingredient ingredient) { + if (this.key.containsKey(symbol)) { + throw new IllegalArgumentException("Symbol '$symbol' is already defined!") + } else if (symbol == ' ') { + throw new IllegalArgumentException("Symbol ' ' (whitespace) is reserved and cannot be defined") + } else { + this.key.put(symbol.charAt(0), ingredient) + return this + } + } + + GShapedRecipeBuilder pattern(String pattern) { + this.pattern(pattern.stripIndent().split('\n')) + } + + GShapedRecipeBuilder pattern(String... patterns) { + patterns.each { pattern -> + if (!this.rows.isEmpty() && pattern.length() !== this.rows[0].length()) { + throw new IllegalArgumentException('Pattern must be the same width on every line!') + } else { + this.rows.add(pattern) + } + } + return this + } + + GShapedRecipeBuilder showNotification(boolean showNotification) { + this.setShowNotification(showNotification) + return this + } + + void setShowNotification(boolean showNotification) { + this.@showNotification = showNotification + } + + void save(Consumer finishedRecipeConsumer, ResourceLocation recipeId) { + this.ensureValid(recipeId) + this.advancement.parent(ROOT_RECIPE_ADVANCEMENT).addCriterion("has_the_recipe", RecipeUnlockedTrigger.unlocked(recipeId)).rewards(AdvancementRewards.Builder.recipe(recipeId)).requirements(RequirementsStrategy.OR) + finishedRecipeConsumer.accept(new Result(recipeId, this.resultStack, this.group ?: '', determineBookCategory(this.category), this.rows, this.key, this.advancement, recipeId.withPrefix("recipes/" + this.category.getFolderName() + "/"), this.showNotification)) + } + + private void ensureValid(ResourceLocation id) { + if (this.rows.isEmpty()) { + throw new IllegalStateException("No pattern is defined for shaped recipe $id!") + } else { + Set set = Sets.newHashSet(this.key.keySet()) + set.remove(' ') + final itr = this.rows.iterator() + + while (itr.hasNext()) { + final row = itr.next() + + for (int i = 0; i < row.length(); ++i) { + char c = row.charAt(i) + if (!this.key.containsKey(c) && c !== ' ' as char) { + throw new IllegalStateException("Pattern in recipe $id uses undefined symbol '$c'") + } + + set.remove(c) + } + } + + if (!set.isEmpty()) { + throw new IllegalStateException("Ingredients are defined but not used in pattern for recipe $id") + } + } + } + + private static class Result extends CraftingRecipeBuilder.CraftingResult { + private final ResourceLocation id + private final ItemStack result + private final String group + private final List pattern + private final Map key + private final Advancement.Builder advancement + private final ResourceLocation advancementId + private final boolean showNotification + + Result(ResourceLocation resourceLocation, ItemStack result, String string, CraftingBookCategory craftingBookCategory, List list, Map map, Advancement.Builder builder, ResourceLocation resourceLocation2, boolean bl) { + super(craftingBookCategory) + this.id = resourceLocation + this.result = result + this.group = string + this.pattern = list + this.key = map + this.advancement = builder + this.advancementId = resourceLocation2 + this.showNotification = bl + } + + void serializeRecipeData(JsonObject json) { + super.serializeRecipeData(json) + if (!this.group.isEmpty()) { + json.addProperty('group', this.group) + } + + JsonArray jsonArray = new JsonArray() + this.pattern.each { + jsonArray.add(it) + } + + json.add('pattern', jsonArray) + + JsonObject jsonObject = new JsonObject() + Iterator itr = this.key.entrySet().iterator() + + while (itr.hasNext()) { + Map.Entry entry = (Map.Entry)itr.next() + jsonObject.add(String.valueOf(entry.getKey()), ((Ingredient)entry.getValue()).toJson()) + } + + json.add('key', jsonObject) + JsonObject jsonObject2 = new JsonObject() + jsonObject2.addProperty('item', BuiltInRegistries.ITEM.getKey(this.result.item).toString()) + if (this.result.count > 1) { + jsonObject2.addProperty('count', this.result.count) + } + // TODO - tag support + + json.add('result', jsonObject2) + json.addProperty('show_notification', this.showNotification) + } + + RecipeSerializer getType() { + RecipeSerializer.SHAPED_RECIPE + } + + ResourceLocation getId() { + return this.id + } + + @Nullable + JsonObject serializeAdvancement() { + return this.advancement.criteria.size() == 1 ? null : this.advancement.serializeToJson() + } + + @Nullable + ResourceLocation getAdvancementId() { + return this.advancementId + } + } +} diff --git a/Common/src/main/groovy/io/github/groovymc/cgl/api/datagen/recipe/GShapelessRecipeBuilder.groovy b/Common/src/main/groovy/io/github/groovymc/cgl/api/datagen/recipe/GShapelessRecipeBuilder.groovy new file mode 100644 index 0000000..de37239 --- /dev/null +++ b/Common/src/main/groovy/io/github/groovymc/cgl/api/datagen/recipe/GShapelessRecipeBuilder.groovy @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2022 GroovyMC and contributors + * SPDX-License-Identifier: LGPL-3.0-or-later + */ + +package io.github.groovymc.cgl.api.datagen.recipe + +import com.google.common.collect.Lists +import com.google.gson.JsonArray +import com.google.gson.JsonObject +import groovy.transform.CompileStatic +import net.minecraft.advancements.Advancement +import net.minecraft.advancements.RequirementsStrategy +import net.minecraft.advancements.critereon.RecipeUnlockedTrigger +import net.minecraft.core.registries.BuiltInRegistries +import net.minecraft.data.recipes.CraftingRecipeBuilder +import net.minecraft.data.recipes.FinishedRecipe +import net.minecraft.resources.ResourceLocation +import net.minecraft.tags.TagKey +import net.minecraft.world.item.Item +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.crafting.CraftingBookCategory +import net.minecraft.world.item.crafting.Ingredient +import net.minecraft.world.item.crafting.RecipeSerializer +import net.minecraft.world.level.ItemLike +import org.jetbrains.annotations.Nullable + +import java.util.function.Consumer + +@CompileStatic +class GShapelessRecipeBuilder extends CraftingRecipeBuilder implements SimpleRecipeBuilder { + private final List ingredients = Lists.newArrayList() + + GShapelessRecipeBuilder requires(TagKey tag) { + this.requires(Ingredient.of(tag)) + } + + GShapelessRecipeBuilder requires(ItemLike item) { + this.requires(item, 1) + } + + GShapelessRecipeBuilder requires(ItemLike item, int quantity) { + for (int i = 0; i < quantity; ++i) { + this.requires(Ingredient.of(new ItemLike[]{item})) + } + return this + } + + GShapelessRecipeBuilder requires(Ingredient ingredient) { + return this.requires(ingredient, 1) + } + + GShapelessRecipeBuilder requires(Ingredient ingredient, int quantity) { + for (int i = 0; i < quantity; ++i) { + this.ingredients.add(ingredient) + } + return this + } + + void save(Consumer finishedRecipeConsumer, ResourceLocation recipeId) { + this.advancement.parent(ROOT_RECIPE_ADVANCEMENT).addCriterion("has_the_recipe", RecipeUnlockedTrigger.unlocked(recipeId)).rewards(net.minecraft.advancements.AdvancementRewards.Builder.recipe(recipeId)).requirements(RequirementsStrategy.OR); + finishedRecipeConsumer.accept(new Result(recipeId, this.resultStack, this.group ?: '', determineBookCategory(this.category), this.ingredients, this.advancement, recipeId.withPrefix("recipes/" + this.category.getFolderName() + "/"))); + } + + static class Result extends CraftingRecipeBuilder.CraftingResult { + private final ResourceLocation id; + private final ItemStack result + private final String group; + private final List ingredients; + private final Advancement.Builder advancement; + private final ResourceLocation advancementId; + + Result(ResourceLocation resourceLocation, ItemStack result, String string, CraftingBookCategory craftingBookCategory, List list, Advancement.Builder builder, ResourceLocation resourceLocation2) { + super(craftingBookCategory) + this.id = resourceLocation + this.result = result + this.group = string + this.ingredients = list + this.advancement = builder + this.advancementId = resourceLocation2 + } + + void serializeRecipeData(JsonObject json) { + super.serializeRecipeData(json) + if (!this.group.isEmpty()) { + json.addProperty('group', this.group) + } + + JsonArray jsonArray = new JsonArray() + this.ingredients.each { + jsonArray.add(it.toJson()) + } + + json.add('ingredients', jsonArray) + final jsonObject = new JsonObject() + jsonObject.addProperty('item', BuiltInRegistries.ITEM.getKey(this.result.item).toString()) + if (this.result.count > 1) { + jsonObject.addProperty('count', this.result.count) + } + + json.add('result', jsonObject) + } + + RecipeSerializer getType() { + return RecipeSerializer.SHAPELESS_RECIPE + } + + ResourceLocation getId() { + return this.id + } + + @Nullable + JsonObject serializeAdvancement() { + return this.advancement.criteria.size() === 1 ? null : this.advancement.serializeToJson() + } + + @Nullable + ResourceLocation getAdvancementId() { + return this.advancementId + } + } +} diff --git a/Common/src/main/groovy/io/github/groovymc/cgl/api/datagen/recipe/GSingleItemRecipeBuilder.groovy b/Common/src/main/groovy/io/github/groovymc/cgl/api/datagen/recipe/GSingleItemRecipeBuilder.groovy new file mode 100644 index 0000000..e624c1a --- /dev/null +++ b/Common/src/main/groovy/io/github/groovymc/cgl/api/datagen/recipe/GSingleItemRecipeBuilder.groovy @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2022 GroovyMC and contributors + * SPDX-License-Identifier: LGPL-3.0-or-later + */ + +package io.github.groovymc.cgl.api.datagen.recipe + +import com.google.gson.JsonObject +import groovy.contracts.Requires +import groovy.transform.CompileStatic +import net.minecraft.advancements.Advancement +import net.minecraft.advancements.AdvancementRewards +import net.minecraft.advancements.RequirementsStrategy +import net.minecraft.advancements.critereon.RecipeUnlockedTrigger +import net.minecraft.core.registries.BuiltInRegistries +import net.minecraft.data.recipes.FinishedRecipe +import net.minecraft.resources.ResourceLocation +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.crafting.Ingredient +import net.minecraft.world.item.crafting.RecipeSerializer +import org.jetbrains.annotations.Nullable + +import java.util.function.Consumer + +@CompileStatic +class GSingleItemRecipeBuilder implements SimpleRecipeBuilder { + private Ingredient ingredient + private RecipeSerializer type + + GSingleItemRecipeBuilder() {} + + GSingleItemRecipeBuilder(RecipeSerializer type) { + setType(type) + } + + GSingleItemRecipeBuilder ingredient(Ingredient ingredient) { + setIngredient(ingredient) + return this + } + + void setIngredient(Ingredient ingredient) { + this.ingredient = ingredient + } + + GSingleItemRecipeBuilder type(RecipeSerializer type) { + setType(type) + return this + } + + void setType(RecipeSerializer type) { + this.type = type + } + + @Requires({ this.ingredient && this.type }) + void save(Consumer finishedRecipeConsumer, ResourceLocation recipeId) { + this.advancement.parent(ROOT_RECIPE_ADVANCEMENT).addCriterion("has_the_recipe", RecipeUnlockedTrigger.unlocked(recipeId)).rewards(AdvancementRewards.Builder.recipe(recipeId)).requirements(RequirementsStrategy.OR) + finishedRecipeConsumer.accept(new Result(recipeId, this.type, this.group ?: '', this.ingredient, this.resultStack, this.advancement, recipeId.withPrefix("recipes/" + this.category.getFolderName() + "/"))) + } + + static class Result implements FinishedRecipe { + private final ResourceLocation id + private final String group + private final Ingredient ingredient + private final ItemStack result + private final Advancement.Builder advancement + private final ResourceLocation advancementId + private final RecipeSerializer type + + Result(ResourceLocation resourceLocation, RecipeSerializer recipeSerializer, String group, Ingredient ingredient, ItemStack result, Advancement.Builder builder, ResourceLocation advId) { + this.id = resourceLocation + this.type = recipeSerializer + this.group = group + this.ingredient = ingredient + this.result = result + this.advancement = builder + this.advancementId = advId + } + + void serializeRecipeData(JsonObject json) { + if (!this.group.isEmpty()) { + json.addProperty('group', this.group) + } + + json.add('ingredient', this.ingredient.toJson()) + json.addProperty('result', BuiltInRegistries.ITEM.getKey(this.result.item).toString()) + json.addProperty("count", this.result.count) + } + + ResourceLocation getId() { + this.id + } + + RecipeSerializer getType() { + this.type + } + + @Nullable + JsonObject serializeAdvancement() { + this.advancement.criteria.size() == 1 ? null : this.advancement.serializeToJson() + } + + @Nullable + ResourceLocation getAdvancementId() { + this.advancementId + } + } +} diff --git a/Common/src/main/groovy/io/github/groovymc/cgl/api/datagen/recipe/SaveableRecipe.groovy b/Common/src/main/groovy/io/github/groovymc/cgl/api/datagen/recipe/SaveableRecipe.groovy new file mode 100644 index 0000000..8ad4c1f --- /dev/null +++ b/Common/src/main/groovy/io/github/groovymc/cgl/api/datagen/recipe/SaveableRecipe.groovy @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2022 GroovyMC and contributors + * SPDX-License-Identifier: LGPL-3.0-or-later + */ + +package io.github.groovymc.cgl.api.datagen.recipe + +import groovy.transform.CompileStatic +import net.minecraft.resources.ResourceLocation + +@CompileStatic +trait SaveableRecipe { + GRecipeProvider provider + + /** + * Saves this recipe to the given {@code location}, using the provider's {@link GRecipeProvider#defaultNamespace default namespace}. + */ + void save(String location) { + this.save(location.contains(':') ? new ResourceLocation(location) : new ResourceLocation(provider.defaultNamespace, location)) + } + + /** + * Saves this recipe to the given {@code location}. + */ + abstract void save(ResourceLocation location) +} \ No newline at end of file diff --git a/Common/src/main/groovy/io/github/groovymc/cgl/api/datagen/recipe/SimpleRecipeBuilder.groovy b/Common/src/main/groovy/io/github/groovymc/cgl/api/datagen/recipe/SimpleRecipeBuilder.groovy new file mode 100644 index 0000000..f45e4f4 --- /dev/null +++ b/Common/src/main/groovy/io/github/groovymc/cgl/api/datagen/recipe/SimpleRecipeBuilder.groovy @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2022 GroovyMC and contributors + * SPDX-License-Identifier: LGPL-3.0-or-later + */ + +package io.github.groovymc.cgl.api.datagen.recipe + +import groovy.transform.CompileStatic +import net.minecraft.advancements.Advancement +import net.minecraft.advancements.CriterionTriggerInstance +import net.minecraft.data.recipes.RecipeCategory +import org.jetbrains.annotations.Nullable + +@CompileStatic +trait SimpleRecipeBuilder extends BaseRecipeBuilder { + private RecipeCategory category = RecipeCategory.MISC + @Nullable + private String group + private final Advancement.Builder advancement = Advancement.Builder.advancement() + + RecipeCategory getCategory() { + return this.@category + } + void setCategory(RecipeCategory category) { + this.@category = category + } + + T category(RecipeCategory category) { + setCategory(category) + return (T) this + } + + String getGroup() { + return this.@group + } + void setGroup(String group) { + this.@group = group + } + + T group(String group) { + setGroup(group) + return (T) this + } + + T unlockedBy(String criterionName, CriterionTriggerInstance criterionTrigger) { + this.@advancement.addCriterion(criterionName, criterionTrigger) + return (T) this + } + + Advancement.Builder getAdvancement() { + return this.@advancement + } +} \ No newline at end of file diff --git a/Forge/build.gradle b/Forge/build.gradle index 0fb456e..a5b350d 100644 --- a/Forge/build.gradle +++ b/Forge/build.gradle @@ -46,13 +46,13 @@ minecraft { data { ideaModule "${rootProject.name}.${project.name}.main" - args '--mod', mod_id, '--all', '--output', file('src/generated/resources/'), '--existing', file('src/main/resources/') + args '--mod', mod_id, '--mod', 'cgltest', '--all', '--output', file('src/test/generated/resources/'), '--existing', file('src/main/resources/') taskName 'Data' } } } -sourceSets.main.resources.srcDir 'src/generated/resources' +sourceSets.main.resources.srcDir 'src/test/generated/resources' sourceSets { transform {} diff --git a/Forge/src/test/generated/resources/cgltest/.cache/9fb1092f32d4fcbf9e061ffd718d4ec689c6c95e b/Forge/src/test/generated/resources/cgltest/.cache/9fb1092f32d4fcbf9e061ffd718d4ec689c6c95e new file mode 100644 index 0000000..2abc6be --- /dev/null +++ b/Forge/src/test/generated/resources/cgltest/.cache/9fb1092f32d4fcbf9e061ffd718d4ec689c6c95e @@ -0,0 +1,9 @@ +// 1.19.4 2023-05-19T20:51:34.6991639 Recipes +c77d782a99edd1c0996eb899f82184cc8d44f41a data/anothermod/advancements/recipes/decorations/yesyes.json +7087581372cb9ebf895e12bcc3eb4430609d6788 data/anothermod/recipes/yesyes.json +0c5b9caa42269c7d408273d712efad02abb504c0 data/cgltest/recipes/my_stairs.json +520af23a1b5c56b3529c20f7ee1083e55efaaa30 data/cgltest/recipes/sprucex12.json +ac80faac08adba0a3780519b5ce69a0602b682fc data/cgltest/recipes/test_stonecutting.json +792fac73fab43971d32004ce6fba4744ca787e95 data/minecraft/recipes/acacia_boat.json +018c2e8a055e1428adf76f0d21688abdc6bd3566 data/minecraft/recipes/acacia_log.json +bb0034d4637a11f5503ca3e228616ffbeb123f22 data/minecraft/recipes/iron_axe.json diff --git a/Forge/src/test/generated/resources/cgltest/data/anothermod/advancements/recipes/decorations/yesyes.json b/Forge/src/test/generated/resources/cgltest/data/anothermod/advancements/recipes/decorations/yesyes.json new file mode 100644 index 0000000..7e0bf98 --- /dev/null +++ b/Forge/src/test/generated/resources/cgltest/data/anothermod/advancements/recipes/decorations/yesyes.json @@ -0,0 +1,34 @@ +{ + "parent": "minecraft:recipes/root", + "criteria": { + "has_the_recipe": { + "conditions": { + "recipe": "anothermod:yesyes" + }, + "trigger": "minecraft:recipe_unlocked" + }, + "yes": { + "conditions": { + "items": [ + { + "items": [ + "minecraft:allium" + ] + } + ] + }, + "trigger": "minecraft:inventory_changed" + } + }, + "requirements": [ + [ + "yes", + "has_the_recipe" + ] + ], + "rewards": { + "recipes": [ + "anothermod:yesyes" + ] + } +} \ No newline at end of file diff --git a/Forge/src/test/generated/resources/cgltest/data/anothermod/recipes/yesyes.json b/Forge/src/test/generated/resources/cgltest/data/anothermod/recipes/yesyes.json new file mode 100644 index 0000000..ffd74c0 --- /dev/null +++ b/Forge/src/test/generated/resources/cgltest/data/anothermod/recipes/yesyes.json @@ -0,0 +1,10 @@ +{ + "type": "minecraft:blasting", + "category": "blocks", + "cookingtime": 2000, + "experience": 12.0, + "ingredient": { + "item": "minecraft:acacia_boat" + }, + "result": "minecraft:allium" +} \ No newline at end of file diff --git a/Forge/src/test/generated/resources/cgltest/data/cgltest/recipes/my_stairs.json b/Forge/src/test/generated/resources/cgltest/data/cgltest/recipes/my_stairs.json new file mode 100644 index 0000000..5076090 --- /dev/null +++ b/Forge/src/test/generated/resources/cgltest/data/cgltest/recipes/my_stairs.json @@ -0,0 +1,18 @@ +{ + "type": "minecraft:crafting_shaped", + "category": "equipment", + "group": "yes", + "key": { + "A": { + "item": "minecraft:acacia_log" + } + }, + "pattern": [ + "AAA" + ], + "result": { + "count": 2, + "item": "minecraft:acacia_stairs" + }, + "show_notification": true +} \ No newline at end of file diff --git a/Forge/src/test/generated/resources/cgltest/data/cgltest/recipes/sprucex12.json b/Forge/src/test/generated/resources/cgltest/data/cgltest/recipes/sprucex12.json new file mode 100644 index 0000000..4e0b32e --- /dev/null +++ b/Forge/src/test/generated/resources/cgltest/data/cgltest/recipes/sprucex12.json @@ -0,0 +1,13 @@ +{ + "type": "minecraft:crafting_shapeless", + "category": "misc", + "ingredients": [ + { + "item": "minecraft:azalea" + } + ], + "result": { + "count": 12, + "item": "minecraft:spruce_button" + } +} \ No newline at end of file diff --git a/Forge/src/test/generated/resources/cgltest/data/cgltest/recipes/test_stonecutting.json b/Forge/src/test/generated/resources/cgltest/data/cgltest/recipes/test_stonecutting.json new file mode 100644 index 0000000..a23ccd2 --- /dev/null +++ b/Forge/src/test/generated/resources/cgltest/data/cgltest/recipes/test_stonecutting.json @@ -0,0 +1,8 @@ +{ + "type": "minecraft:stonecutting", + "count": 1, + "ingredient": { + "item": "minecraft:pink_candle" + }, + "result": "minecraft:end_stone_brick_slab" +} \ No newline at end of file diff --git a/Forge/src/test/generated/resources/cgltest/data/minecraft/recipes/acacia_boat.json b/Forge/src/test/generated/resources/cgltest/data/minecraft/recipes/acacia_boat.json new file mode 100644 index 0000000..34d1d33 --- /dev/null +++ b/Forge/src/test/generated/resources/cgltest/data/minecraft/recipes/acacia_boat.json @@ -0,0 +1,13 @@ +{ + "type": "minecraft:crafting_shapeless", + "category": "redstone", + "group": "no", + "ingredients": [ + { + "tag": "minecraft:coal_ores" + } + ], + "result": { + "item": "minecraft:acacia_boat" + } +} \ No newline at end of file diff --git a/Forge/src/test/generated/resources/cgltest/data/minecraft/recipes/acacia_log.json b/Forge/src/test/generated/resources/cgltest/data/minecraft/recipes/acacia_log.json new file mode 100644 index 0000000..89274d5 --- /dev/null +++ b/Forge/src/test/generated/resources/cgltest/data/minecraft/recipes/acacia_log.json @@ -0,0 +1,10 @@ +{ + "type": "minecraft:campfire_cooking", + "category": "misc", + "cookingtime": 200, + "experience": 20.0, + "ingredient": { + "item": "minecraft:azalea" + }, + "result": "minecraft:acacia_log" +} \ No newline at end of file diff --git a/Forge/src/test/generated/resources/cgltest/data/minecraft/recipes/iron_axe.json b/Forge/src/test/generated/resources/cgltest/data/minecraft/recipes/iron_axe.json new file mode 100644 index 0000000..ca449a9 --- /dev/null +++ b/Forge/src/test/generated/resources/cgltest/data/minecraft/recipes/iron_axe.json @@ -0,0 +1,10 @@ +{ + "type": "minecraft:smelting", + "category": "misc", + "cookingtime": 260, + "experience": 4.0, + "ingredient": { + "item": "minecraft:bamboo" + }, + "result": "minecraft:iron_axe" +} \ No newline at end of file diff --git a/Forge/src/test/groovy/cgltest/Datagen.groovy b/Forge/src/test/groovy/cgltest/Datagen.groovy new file mode 100644 index 0000000..9ab99f6 --- /dev/null +++ b/Forge/src/test/groovy/cgltest/Datagen.groovy @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2022 GroovyMC and contributors + * SPDX-License-Identifier: LGPL-3.0-or-later + */ + +package cgltest + +import com.matyrobbrt.gml.bus.EventBusSubscriber +import com.matyrobbrt.gml.bus.type.ModBus +import groovy.time.TimeCategory +import groovy.transform.CompileDynamic +import groovy.transform.CompileStatic +import groovy.transform.PackageScope +import io.github.groovymc.cgl.api.datagen.recipe.GRecipeProvider +import io.github.groovymc.cgl.api.datagen.recipe.GShapelessRecipeBuilder +import net.minecraft.data.PackOutput +import net.minecraft.data.recipes.RecipeCategory +import net.minecraft.data.recipes.SimpleCookingRecipeBuilder +import net.minecraft.resources.ResourceLocation +import net.minecraft.tags.ItemTags +import net.minecraft.world.item.Items +import net.minecraft.world.item.crafting.Ingredient +import net.minecraftforge.data.event.GatherDataEvent +import net.minecraftforge.eventbus.api.SubscribeEvent + +@CompileStatic +@EventBusSubscriber(ModBus) +class Datagen extends GRecipeProvider { + @SubscribeEvent @PackageScope + static void onDatagen(final GatherDataEvent event) { + event.generator.addProvider(event.includeServer(), new Datagen(event.generator.packOutput)) + } + + Datagen(PackOutput packOutput) { + super(packOutput, 'cgltest') + } + + @Override + protected void buildRecipes() { + shaped { + result Items.ACACIA_STAIRS count 2 + category = RecipeCategory.COMBAT + group = 'yes' + + pattern 'AAA' + define 'A', Items.ACACIA_LOG + } save 'my_stairs' + + recipe(GShapelessRecipeBuilder) { + result Items.ACACIA_BOAT + category = RecipeCategory.REDSTONE + group = 'no' + + requires ItemTags.COAL_ORES + }.save() + + shapeless { + result Items.SPRUCE_BUTTON * 12 + requires Items.AZALEA + } save 'sprucex12' + + normal(SimpleCookingRecipeBuilder.blasting( + Items.ACACIA_BOAT.ingredient(), + RecipeCategory.DECORATIONS, + Items.ALLIUM, + 12f, + 2000 + )) { + unlockedBy('yes', has(Items.ALLIUM)) + } save new ResourceLocation('anothermod:yesyes') + + campfireCooking { + result Items.ACACIA_LOG + ingredient Items.AZALEA as Ingredient + category RecipeCategory.BREWING + } + + stonecutting { + result Items.END_STONE_BRICK_SLAB + ingredient Items.PINK_CANDLE.ingredient() + } save 'test_stonecutting' + + dynamic() + + saveForgotten() + } + + @CompileDynamic + private void dynamic() { + smelting { + result Items.IRON_AXE + ingredient Items.BAMBOO.ingredient() + use (TimeCategory) { + cookingTime 13.seconds + } + experience 4f + } + } +}