From a3b07909b07dfe9623e386c2afc1a7b45bb9c503 Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Fri, 26 Apr 2024 19:45:09 +0100 Subject: [PATCH] Replace some recipes with a more dynamic system This adds a new "recipe function" system, that allows transforming the result of a recipe according to some datapack-defined function. Currently, we only provide one function: computercraft:copy_components, which copies components from one of the ingredients to the result. This allows us to replace several of our existing recipes: - Turtle overlay recipes are now defined as a normal shapeless recipe that copies all (non-overlay) components from the input turtle. - Computer conversion recipes (e.g. computer -> turtle, normal -> advanced) copy all components from the input computer to the result. This is more complex (and thus more code), but also a little more flexible, which hopefully is useful for someone :). --- .../recipes/computer_advanced_upgrade.json | 3 +- .../pocket_computer_advanced_upgrade.json | 5 +- .../recipes/turtle_advanced.json | 3 +- .../turtle_rainbow_overlay.json | 16 +- .../turtle_trans_overlay.json | 16 +- .../recipes/turtle_advanced_upgrade.json | 3 +- .../computercraft/recipes/turtle_normal.json | 3 +- .../turtle_rainbow_overlay.json | 16 +- .../turtle_trans_overlay.json | 16 +- .../computercraft/data/RecipeProvider.java | 24 ++- .../computercraft/shared/ModRegistry.java | 32 +++- .../recipe/ComputerConvertRecipe.java | 53 ------ .../shared/recipe/CustomShapedRecipe.java | 2 +- .../shared/recipe/TransformShapedRecipe.java | 55 ++++++ .../recipe/TransformShapelessRecipe.java | 54 ++++++ .../recipe/function/CopyComponents.java | 179 ++++++++++++++++++ .../recipe/function/RecipeFunction.java | 90 +++++++++ .../turtle/recipes/TurtleOverlayRecipe.java | 68 ------- .../computercraft/shared/ComputerCraft.java | 5 + .../dan200/computercraft/ComputerCraft.java | 2 + 20 files changed, 489 insertions(+), 156 deletions(-) delete mode 100644 projects/common/src/main/java/dan200/computercraft/shared/computer/recipe/ComputerConvertRecipe.java create mode 100644 projects/common/src/main/java/dan200/computercraft/shared/recipe/TransformShapedRecipe.java create mode 100644 projects/common/src/main/java/dan200/computercraft/shared/recipe/TransformShapelessRecipe.java create mode 100644 projects/common/src/main/java/dan200/computercraft/shared/recipe/function/CopyComponents.java create mode 100644 projects/common/src/main/java/dan200/computercraft/shared/recipe/function/RecipeFunction.java delete mode 100644 projects/common/src/main/java/dan200/computercraft/shared/turtle/recipes/TurtleOverlayRecipe.java diff --git a/projects/common/src/generated/resources/data/computercraft/recipes/computer_advanced_upgrade.json b/projects/common/src/generated/resources/data/computercraft/recipes/computer_advanced_upgrade.json index a9392af47..9af31459c 100644 --- a/projects/common/src/generated/resources/data/computercraft/recipes/computer_advanced_upgrade.json +++ b/projects/common/src/generated/resources/data/computercraft/recipes/computer_advanced_upgrade.json @@ -1,6 +1,7 @@ { - "type": "computercraft:computer_convert", + "type": "computercraft:transform_shaped", "category": "redstone", + "function": [{"type": "computercraft:copy_components", "from": {"item": "computercraft:computer_normal"}}], "key": {"#": {"tag": "c:ingots/gold"}, "C": {"item": "computercraft:computer_normal"}}, "pattern": ["###", "#C#", "# #"], "result": {"count": 1, "id": "computercraft:computer_advanced"} diff --git a/projects/common/src/generated/resources/data/computercraft/recipes/pocket_computer_advanced_upgrade.json b/projects/common/src/generated/resources/data/computercraft/recipes/pocket_computer_advanced_upgrade.json index 10c8aeeb9..52649787f 100644 --- a/projects/common/src/generated/resources/data/computercraft/recipes/pocket_computer_advanced_upgrade.json +++ b/projects/common/src/generated/resources/data/computercraft/recipes/pocket_computer_advanced_upgrade.json @@ -1,6 +1,9 @@ { - "type": "computercraft:computer_convert", + "type": "computercraft:transform_shaped", "category": "redstone", + "function": [ + {"type": "computercraft:copy_components", "from": {"item": "computercraft:pocket_computer_normal"}} + ], "key": {"#": {"tag": "c:ingots/gold"}, "C": {"item": "computercraft:pocket_computer_normal"}}, "pattern": ["###", "#C#", "# #"], "result": {"count": 1, "id": "computercraft:pocket_computer_advanced"} diff --git a/projects/common/src/generated/resources/data/computercraft/recipes/turtle_advanced.json b/projects/common/src/generated/resources/data/computercraft/recipes/turtle_advanced.json index 02e450f72..4e86b0316 100644 --- a/projects/common/src/generated/resources/data/computercraft/recipes/turtle_advanced.json +++ b/projects/common/src/generated/resources/data/computercraft/recipes/turtle_advanced.json @@ -1,6 +1,7 @@ { - "type": "computercraft:computer_convert", + "type": "computercraft:transform_shaped", "category": "redstone", + "function": [{"type": "computercraft:copy_components", "from": {"item": "computercraft:computer_advanced"}}], "key": { "#": {"tag": "c:ingots/gold"}, "C": {"item": "computercraft:computer_advanced"}, diff --git a/projects/common/src/generated/resources/data/computercraft/recipes/turtle_advanced_overlays/turtle_rainbow_overlay.json b/projects/common/src/generated/resources/data/computercraft/recipes/turtle_advanced_overlays/turtle_rainbow_overlay.json index 8e652cd1b..b08d4c7c4 100644 --- a/projects/common/src/generated/resources/data/computercraft/recipes/turtle_advanced_overlays/turtle_rainbow_overlay.json +++ b/projects/common/src/generated/resources/data/computercraft/recipes/turtle_advanced_overlays/turtle_rainbow_overlay.json @@ -1,6 +1,13 @@ { - "type": "computercraft:turtle_overlay", + "type": "computercraft:transform_shapeless", "category": "redstone", + "function": [ + { + "type": "computercraft:copy_components", + "exclude": ["computercraft:overlay"], + "from": {"item": "computercraft:turtle_advanced"} + } + ], "group": "computercraft:turtle_advanced_overlay", "ingredients": [ {"tag": "c:dyes/red"}, @@ -12,6 +19,9 @@ {"item": "minecraft:stick"}, {"item": "computercraft:turtle_advanced"} ], - "overlay": "computercraft:block/turtle_rainbow_overlay", - "result": {"count": 1, "id": "computercraft:turtle_advanced"} + "result": { + "components": {"computercraft:overlay": "computercraft:block/turtle_rainbow_overlay"}, + "count": 1, + "id": "computercraft:turtle_advanced" + } } diff --git a/projects/common/src/generated/resources/data/computercraft/recipes/turtle_advanced_overlays/turtle_trans_overlay.json b/projects/common/src/generated/resources/data/computercraft/recipes/turtle_advanced_overlays/turtle_trans_overlay.json index b1349d76e..03f8cdea1 100644 --- a/projects/common/src/generated/resources/data/computercraft/recipes/turtle_advanced_overlays/turtle_trans_overlay.json +++ b/projects/common/src/generated/resources/data/computercraft/recipes/turtle_advanced_overlays/turtle_trans_overlay.json @@ -1,6 +1,13 @@ { - "type": "computercraft:turtle_overlay", + "type": "computercraft:transform_shapeless", "category": "redstone", + "function": [ + { + "type": "computercraft:copy_components", + "exclude": ["computercraft:overlay"], + "from": {"item": "computercraft:turtle_advanced"} + } + ], "group": "computercraft:turtle_advanced_overlay", "ingredients": [ {"tag": "c:dyes/light_blue"}, @@ -9,6 +16,9 @@ {"item": "minecraft:stick"}, {"item": "computercraft:turtle_advanced"} ], - "overlay": "computercraft:block/turtle_trans_overlay", - "result": {"count": 1, "id": "computercraft:turtle_advanced"} + "result": { + "components": {"computercraft:overlay": "computercraft:block/turtle_trans_overlay"}, + "count": 1, + "id": "computercraft:turtle_advanced" + } } diff --git a/projects/common/src/generated/resources/data/computercraft/recipes/turtle_advanced_upgrade.json b/projects/common/src/generated/resources/data/computercraft/recipes/turtle_advanced_upgrade.json index 7405e84f1..34e794127 100644 --- a/projects/common/src/generated/resources/data/computercraft/recipes/turtle_advanced_upgrade.json +++ b/projects/common/src/generated/resources/data/computercraft/recipes/turtle_advanced_upgrade.json @@ -1,6 +1,7 @@ { - "type": "computercraft:computer_convert", + "type": "computercraft:transform_shaped", "category": "redstone", + "function": [{"type": "computercraft:copy_components", "from": {"item": "computercraft:turtle_normal"}}], "key": { "#": {"tag": "c:ingots/gold"}, "B": {"tag": "c:storage_blocks/gold"}, diff --git a/projects/common/src/generated/resources/data/computercraft/recipes/turtle_normal.json b/projects/common/src/generated/resources/data/computercraft/recipes/turtle_normal.json index 14b764b3d..8c8312083 100644 --- a/projects/common/src/generated/resources/data/computercraft/recipes/turtle_normal.json +++ b/projects/common/src/generated/resources/data/computercraft/recipes/turtle_normal.json @@ -1,6 +1,7 @@ { - "type": "computercraft:computer_convert", + "type": "computercraft:transform_shaped", "category": "redstone", + "function": [{"type": "computercraft:copy_components", "from": {"item": "computercraft:computer_normal"}}], "key": { "#": {"tag": "c:ingots/iron"}, "C": {"item": "computercraft:computer_normal"}, diff --git a/projects/common/src/generated/resources/data/computercraft/recipes/turtle_normal_overlays/turtle_rainbow_overlay.json b/projects/common/src/generated/resources/data/computercraft/recipes/turtle_normal_overlays/turtle_rainbow_overlay.json index 12673b797..64cd0675f 100644 --- a/projects/common/src/generated/resources/data/computercraft/recipes/turtle_normal_overlays/turtle_rainbow_overlay.json +++ b/projects/common/src/generated/resources/data/computercraft/recipes/turtle_normal_overlays/turtle_rainbow_overlay.json @@ -1,6 +1,13 @@ { - "type": "computercraft:turtle_overlay", + "type": "computercraft:transform_shapeless", "category": "redstone", + "function": [ + { + "type": "computercraft:copy_components", + "exclude": ["computercraft:overlay"], + "from": {"item": "computercraft:turtle_normal"} + } + ], "group": "computercraft:turtle_normal_overlay", "ingredients": [ {"tag": "c:dyes/red"}, @@ -12,6 +19,9 @@ {"item": "minecraft:stick"}, {"item": "computercraft:turtle_normal"} ], - "overlay": "computercraft:block/turtle_rainbow_overlay", - "result": {"count": 1, "id": "computercraft:turtle_normal"} + "result": { + "components": {"computercraft:overlay": "computercraft:block/turtle_rainbow_overlay"}, + "count": 1, + "id": "computercraft:turtle_normal" + } } diff --git a/projects/common/src/generated/resources/data/computercraft/recipes/turtle_normal_overlays/turtle_trans_overlay.json b/projects/common/src/generated/resources/data/computercraft/recipes/turtle_normal_overlays/turtle_trans_overlay.json index e91c5a456..63df05d9d 100644 --- a/projects/common/src/generated/resources/data/computercraft/recipes/turtle_normal_overlays/turtle_trans_overlay.json +++ b/projects/common/src/generated/resources/data/computercraft/recipes/turtle_normal_overlays/turtle_trans_overlay.json @@ -1,6 +1,13 @@ { - "type": "computercraft:turtle_overlay", + "type": "computercraft:transform_shapeless", "category": "redstone", + "function": [ + { + "type": "computercraft:copy_components", + "exclude": ["computercraft:overlay"], + "from": {"item": "computercraft:turtle_normal"} + } + ], "group": "computercraft:turtle_normal_overlay", "ingredients": [ {"tag": "c:dyes/light_blue"}, @@ -9,6 +16,9 @@ {"item": "minecraft:stick"}, {"item": "computercraft:turtle_normal"} ], - "overlay": "computercraft:block/turtle_trans_overlay", - "result": {"count": 1, "id": "computercraft:turtle_normal"} + "result": { + "components": {"computercraft:overlay": "computercraft:block/turtle_trans_overlay"}, + "count": 1, + "id": "computercraft:turtle_normal" + } } diff --git a/projects/common/src/main/java/dan200/computercraft/data/RecipeProvider.java b/projects/common/src/main/java/dan200/computercraft/data/RecipeProvider.java index b67ecb1b5..152cdd97b 100644 --- a/projects/common/src/main/java/dan200/computercraft/data/RecipeProvider.java +++ b/projects/common/src/main/java/dan200/computercraft/data/RecipeProvider.java @@ -18,7 +18,6 @@ import dan200.computercraft.shared.ModRegistry; import dan200.computercraft.shared.common.ClearColourRecipe; import dan200.computercraft.shared.common.ColourableRecipe; -import dan200.computercraft.shared.computer.recipe.ComputerConvertRecipe; import dan200.computercraft.shared.media.recipes.DiskRecipe; import dan200.computercraft.shared.media.recipes.PrintoutRecipe; import dan200.computercraft.shared.platform.PlatformHelper; @@ -27,8 +26,10 @@ import dan200.computercraft.shared.pocket.recipes.PocketComputerUpgradeRecipe; import dan200.computercraft.shared.recipe.ImpostorShapedRecipe; import dan200.computercraft.shared.recipe.ImpostorShapelessRecipe; +import dan200.computercraft.shared.recipe.TransformShapedRecipe; +import dan200.computercraft.shared.recipe.TransformShapelessRecipe; +import dan200.computercraft.shared.recipe.function.CopyComponents; import dan200.computercraft.shared.turtle.items.TurtleItem; -import dan200.computercraft.shared.turtle.recipes.TurtleOverlayRecipe; import dan200.computercraft.shared.turtle.recipes.TurtleUpgradeRecipe; import dan200.computercraft.shared.util.ColourUtils; import dan200.computercraft.shared.util.DataComponentUtil; @@ -194,16 +195,21 @@ private void turtleOverlays(RecipeOutput add) { } private void turtleOverlay(RecipeOutput add, String overlay, Consumer build) { + var overlayId = new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/" + overlay); + for (var turtleItem : turtleItems()) { var name = RegistryHelper.getKeyOrThrow(BuiltInRegistries.ITEM, turtleItem); - var builder = ShapelessSpecBuilder.shapeless(RecipeCategory.REDSTONE, new ItemStack(turtleItem)) + var builder = ShapelessSpecBuilder + .shapeless(RecipeCategory.REDSTONE, DataComponentUtil.createStack(turtleItem, ModRegistry.DataComponents.OVERLAY.get(), overlayId)) .group(name.withSuffix("_overlay").toString()) .unlockedBy("has_turtle", inventoryChange(turtleItem)); build.accept(builder); builder .requires(turtleItem) - .build(s -> new TurtleOverlayRecipe(s, new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/" + overlay))) + .build(s -> new TransformShapelessRecipe(s, List.of( + CopyComponents.builder(turtleItem).exclude(ModRegistry.DataComponents.OVERLAY.get()).build() + ))) .save(add, name.withSuffix("_overlays/" + overlay)); } } @@ -251,7 +257,7 @@ private void basicRecipes(RecipeOutput add) { .define('#', ingredients.goldIngot()) .define('C', ModRegistry.Items.COMPUTER_NORMAL.get()) .unlockedBy("has_components", inventoryChange(itemPredicate(ModRegistry.Items.COMPUTER_NORMAL.get()), itemPredicate(ingredients.goldIngot()))) - .build(ComputerConvertRecipe::new) + .build(x -> new TransformShapedRecipe(x, List.of(new CopyComponents(ModRegistry.Items.COMPUTER_NORMAL.get())))) .save(add, new ResourceLocation(ComputerCraftAPI.MOD_ID, "computer_advanced_upgrade")); ShapedRecipeBuilder @@ -274,7 +280,7 @@ private void basicRecipes(RecipeOutput add) { .define('C', ModRegistry.Items.COMPUTER_NORMAL.get()) .define('I', ingredients.woodenChest()) .unlockedBy("has_computer", inventoryChange(ModRegistry.Items.COMPUTER_NORMAL.get())) - .build(ComputerConvertRecipe::new) + .build(x -> new TransformShapedRecipe(x, List.of(new CopyComponents(ModRegistry.Items.COMPUTER_NORMAL.get())))) .save(add); ShapedSpecBuilder @@ -286,7 +292,7 @@ private void basicRecipes(RecipeOutput add) { .define('C', ModRegistry.Items.COMPUTER_ADVANCED.get()) .define('I', ingredients.woodenChest()) .unlockedBy("has_computer", inventoryChange(ModRegistry.Items.COMPUTER_NORMAL.get())) - .build(ComputerConvertRecipe::new) + .build(x -> new TransformShapedRecipe(x, List.of(new CopyComponents(ModRegistry.Items.COMPUTER_ADVANCED.get())))) .save(add); ShapedSpecBuilder @@ -298,7 +304,7 @@ private void basicRecipes(RecipeOutput add) { .define('C', ModRegistry.Items.TURTLE_NORMAL.get()) .define('B', ingredients.goldBlock()) .unlockedBy("has_components", inventoryChange(itemPredicate(ModRegistry.Items.TURTLE_NORMAL.get()), itemPredicate(ingredients.goldIngot()))) - .build(ComputerConvertRecipe::new) + .build(x -> new TransformShapedRecipe(x, List.of(new CopyComponents(ModRegistry.Items.TURTLE_NORMAL.get())))) .save(add, new ResourceLocation(ComputerCraftAPI.MOD_ID, "turtle_advanced_upgrade")); ShapedRecipeBuilder @@ -363,7 +369,7 @@ private void basicRecipes(RecipeOutput add) { .define('#', ingredients.goldIngot()) .define('C', ModRegistry.Items.POCKET_COMPUTER_NORMAL.get()) .unlockedBy("has_components", inventoryChange(itemPredicate(ModRegistry.Items.POCKET_COMPUTER_NORMAL.get()), itemPredicate(ingredients.goldIngot()))) - .build(ComputerConvertRecipe::new) + .build(x -> new TransformShapedRecipe(x, List.of(new CopyComponents(ModRegistry.Items.POCKET_COMPUTER_NORMAL.get())))) .save(add, new ResourceLocation(ComputerCraftAPI.MOD_ID, "pocket_computer_advanced_upgrade")); ShapedRecipeBuilder diff --git a/projects/common/src/main/java/dan200/computercraft/shared/ModRegistry.java b/projects/common/src/main/java/dan200/computercraft/shared/ModRegistry.java index 0a99662d4..538382a60 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/ModRegistry.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/ModRegistry.java @@ -37,7 +37,6 @@ import dan200.computercraft.shared.computer.items.CommandComputerItem; import dan200.computercraft.shared.computer.items.ComputerItem; import dan200.computercraft.shared.computer.items.ServerComputerReference; -import dan200.computercraft.shared.computer.recipe.ComputerConvertRecipe; import dan200.computercraft.shared.config.Config; import dan200.computercraft.shared.data.BlockNamedEntityLootCondition; import dan200.computercraft.shared.data.HasComputerIdLootCondition; @@ -71,16 +70,14 @@ import dan200.computercraft.shared.pocket.peripherals.PocketModem; import dan200.computercraft.shared.pocket.peripherals.PocketSpeaker; import dan200.computercraft.shared.pocket.recipes.PocketComputerUpgradeRecipe; -import dan200.computercraft.shared.recipe.CustomShapedRecipe; -import dan200.computercraft.shared.recipe.CustomShapelessRecipe; -import dan200.computercraft.shared.recipe.ImpostorShapedRecipe; -import dan200.computercraft.shared.recipe.ImpostorShapelessRecipe; +import dan200.computercraft.shared.recipe.*; +import dan200.computercraft.shared.recipe.function.CopyComponents; +import dan200.computercraft.shared.recipe.function.RecipeFunction; import dan200.computercraft.shared.turtle.FurnaceRefuelHandler; import dan200.computercraft.shared.turtle.blocks.TurtleBlock; import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity; import dan200.computercraft.shared.turtle.inventory.TurtleMenu; import dan200.computercraft.shared.turtle.items.TurtleItem; -import dan200.computercraft.shared.turtle.recipes.TurtleOverlayRecipe; import dan200.computercraft.shared.turtle.recipes.TurtleUpgradeRecipe; import dan200.computercraft.shared.turtle.upgrades.*; import dan200.computercraft.shared.util.DataComponentUtil; @@ -91,14 +88,17 @@ import net.minecraft.core.cauldron.CauldronInteraction; import net.minecraft.core.component.DataComponentType; import net.minecraft.core.registries.Registries; +import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.chat.Component; import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.flag.FeatureFlags; import net.minecraft.world.inventory.MenuType; import net.minecraft.world.item.*; import net.minecraft.world.item.component.DyedItemColor; import net.minecraft.world.item.crafting.CustomRecipe; +import net.minecraft.world.item.crafting.Recipe; import net.minecraft.world.item.crafting.RecipeSerializer; import net.minecraft.world.item.crafting.SimpleCraftingRecipeSerializer; import net.minecraft.world.level.block.Block; @@ -474,17 +474,32 @@ private static RegistryEntry new SimpleCraftingRecipeSerializer<>(factory)); } + private static > RegistryEntry> register(String name, MapCodec codec, StreamCodec streamCodec) { + return REGISTRY.register(name, () -> new BasicRecipeSerialiser<>(codec, streamCodec)); + } + public static final RegistryEntry> IMPOSTOR_SHAPED = REGISTRY.register("impostor_shaped", () -> CustomShapedRecipe.serialiser(ImpostorShapedRecipe::new)); public static final RegistryEntry> IMPOSTOR_SHAPELESS = REGISTRY.register("impostor_shapeless", () -> CustomShapelessRecipe.serialiser(ImpostorShapelessRecipe::new)); + public static final RegistryEntry> TRANSFORM_SHAPED = register("transform_shaped", TransformShapedRecipe.CODEC, TransformShapedRecipe.STREAM_CODEC); + public static final RegistryEntry> TRANSFORM_SHAPELESS = register("transform_shapeless", TransformShapelessRecipe.CODEC, TransformShapelessRecipe.STREAM_CODEC); + public static final RegistryEntry> DYEABLE_ITEM = simple("colour", ColourableRecipe::new); public static final RegistryEntry> DYEABLE_ITEM_CLEAR = simple("clear_colour", ClearColourRecipe::new); public static final RegistryEntry> TURTLE_UPGRADE = simple("turtle_upgrade", TurtleUpgradeRecipe::new); - public static final RegistryEntry> TURTLE_OVERLAY = REGISTRY.register("turtle_overlay", TurtleOverlayRecipe::serialiser); public static final RegistryEntry> POCKET_COMPUTER_UPGRADE = simple("pocket_computer_upgrade", PocketComputerUpgradeRecipe::new); public static final RegistryEntry> PRINTOUT = simple("printout", PrintoutRecipe::new); public static final RegistryEntry> DISK = simple("disk", DiskRecipe::new); - public static final RegistryEntry> COMPUTER_CONVERT = REGISTRY.register("computer_convert", () -> CustomShapedRecipe.serialiser(ComputerConvertRecipe::new)); + } + + public static class RecipeFunctions { + static final RegistrationHelper> REGISTRY = PlatformHelper.get().createRegistrationHelper(RecipeFunction.REGISTRY); + + private static RegistryEntry> register(String name, MapCodec codec, StreamCodec streamCodec) { + return REGISTRY.register(name, () -> new RecipeFunction.Type<>(codec, streamCodec)); + } + + public static final RegistryEntry> COPY_COMPONENTS = register("copy_components", CopyComponents.CODEC, CopyComponents.STREAM_CODEC); } public static class Permissions { @@ -553,6 +568,7 @@ public static void register() { ArgumentTypes.REGISTRY.register(); LootItemConditionTypes.REGISTRY.register(); RecipeSerializers.REGISTRY.register(); + RecipeFunctions.REGISTRY.register(); Permissions.REGISTRY.register(); CreativeTabs.REGISTRY.register(); diff --git a/projects/common/src/main/java/dan200/computercraft/shared/computer/recipe/ComputerConvertRecipe.java b/projects/common/src/main/java/dan200/computercraft/shared/computer/recipe/ComputerConvertRecipe.java deleted file mode 100644 index 15cc56e67..000000000 --- a/projects/common/src/main/java/dan200/computercraft/shared/computer/recipe/ComputerConvertRecipe.java +++ /dev/null @@ -1,53 +0,0 @@ -// SPDX-FileCopyrightText: 2018 The CC: Tweaked Developers -// -// SPDX-License-Identifier: MPL-2.0 - -package dan200.computercraft.shared.computer.recipe; - -import dan200.computercraft.shared.ModRegistry; -import dan200.computercraft.shared.computer.items.AbstractComputerItem; -import dan200.computercraft.shared.pocket.items.PocketComputerItem; -import dan200.computercraft.shared.recipe.CustomShapedRecipe; -import dan200.computercraft.shared.recipe.ShapedRecipeSpec; -import net.minecraft.core.HolderLookup; -import net.minecraft.world.inventory.CraftingContainer; -import net.minecraft.world.item.Item; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.crafting.RecipeSerializer; - -/** - * A recipe which converts a computer from one form into another. - */ -public final class ComputerConvertRecipe extends CustomShapedRecipe { - private final Item result; - - public ComputerConvertRecipe(ShapedRecipeSpec recipe) { - super(recipe); - this.result = recipe.result().getItem(); - } - - @Override - public ItemStack assemble(CraftingContainer inventory, HolderLookup.Provider registryAccess) { - // Find our computer item and copy the components across. - for (var i = 0; i < inventory.getContainerSize(); i++) { - var stack = inventory.getItem(i); - if (isComputerItem(stack.getItem())) { - var newStack = new ItemStack(result); - newStack.applyComponents(stack.getComponentsPatch()); - return newStack; - } - } - - return ItemStack.EMPTY; - } - - @Override - public RecipeSerializer getSerializer() { - return ModRegistry.RecipeSerializers.COMPUTER_CONVERT.get(); - } - - private static boolean isComputerItem(Item item) { - // TODO: Make this a little more general. Either with a tag, or a predicate on the recipe itself? - return item instanceof AbstractComputerItem || item instanceof PocketComputerItem; - } -} diff --git a/projects/common/src/main/java/dan200/computercraft/shared/recipe/CustomShapedRecipe.java b/projects/common/src/main/java/dan200/computercraft/shared/recipe/CustomShapedRecipe.java index 4c99a8c3c..fb8d54316 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/recipe/CustomShapedRecipe.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/recipe/CustomShapedRecipe.java @@ -32,7 +32,7 @@ public final ShapedRecipeSpec toSpec() { public abstract RecipeSerializer getSerializer(); public static RecipeSerializer serialiser(Function factory) { - return new BasicRecipeSerialiser( + return new BasicRecipeSerialiser<>( ShapedRecipeSpec.CODEC.xmap(factory, CustomShapedRecipe::toSpec), ShapedRecipeSpec.STREAM_CODEC.map(factory, CustomShapedRecipe::toSpec) ); diff --git a/projects/common/src/main/java/dan200/computercraft/shared/recipe/TransformShapedRecipe.java b/projects/common/src/main/java/dan200/computercraft/shared/recipe/TransformShapedRecipe.java new file mode 100644 index 000000000..358bee2ab --- /dev/null +++ b/projects/common/src/main/java/dan200/computercraft/shared/recipe/TransformShapedRecipe.java @@ -0,0 +1,55 @@ +// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.shared.recipe; + +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import dan200.computercraft.shared.ModRegistry; +import dan200.computercraft.shared.recipe.function.RecipeFunction; +import net.minecraft.core.HolderLookup; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.world.inventory.CraftingContainer; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.RecipeSerializer; +import net.minecraft.world.item.crafting.ShapedRecipe; + +import java.util.List; + +/** + * A {@link ShapedRecipe} that applies a list of {@linkplain RecipeFunction recipe functions}. + */ +public final class TransformShapedRecipe extends CustomShapedRecipe { + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group( + ShapedRecipeSpec.CODEC.forGetter(TransformShapedRecipe::toSpec), + RecipeFunction.LIST_CODEC.fieldOf("function").forGetter(x -> x.functions) + ).apply(instance, TransformShapedRecipe::new) + ); + + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + ShapedRecipeSpec.STREAM_CODEC, TransformShapedRecipe::toSpec, + RecipeFunction.LIST_STREAM_CODEC, x -> x.functions, + TransformShapedRecipe::new + ); + + private final List functions; + + public TransformShapedRecipe(ShapedRecipeSpec recipe, List functions) { + super(recipe); + this.functions = functions; + } + + @Override + public ItemStack assemble(CraftingContainer inventory, HolderLookup.Provider registryAccess) { + var result = super.assemble(inventory, registryAccess); + for (var function : functions) result = function.apply(inventory, result); + return result; + } + + @Override + public RecipeSerializer getSerializer() { + return ModRegistry.RecipeSerializers.TRANSFORM_SHAPED.get(); + } +} diff --git a/projects/common/src/main/java/dan200/computercraft/shared/recipe/TransformShapelessRecipe.java b/projects/common/src/main/java/dan200/computercraft/shared/recipe/TransformShapelessRecipe.java new file mode 100644 index 000000000..f3a8b8f2b --- /dev/null +++ b/projects/common/src/main/java/dan200/computercraft/shared/recipe/TransformShapelessRecipe.java @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.shared.recipe; + +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import dan200.computercraft.shared.ModRegistry; +import dan200.computercraft.shared.recipe.function.RecipeFunction; +import net.minecraft.core.HolderLookup; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.world.inventory.CraftingContainer; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.RecipeSerializer; +import net.minecraft.world.item.crafting.ShapelessRecipe; + +import java.util.List; + +/** + * A {@link ShapelessRecipe} that applies a list of {@linkplain RecipeFunction recipe functions}. + */ +public class TransformShapelessRecipe extends CustomShapelessRecipe { + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group( + ShapelessRecipeSpec.CODEC.forGetter(TransformShapelessRecipe::toSpec), + RecipeFunction.LIST_CODEC.fieldOf("function").forGetter(x -> x.functions) + ).apply(instance, TransformShapelessRecipe::new)); + + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + ShapelessRecipeSpec.STREAM_CODEC, CustomShapelessRecipe::toSpec, + RecipeFunction.LIST_STREAM_CODEC, x -> x.functions, + TransformShapelessRecipe::new + ); + + private final List functions; + + public TransformShapelessRecipe(ShapelessRecipeSpec spec, List functions) { + super(spec); + this.functions = functions; + } + + @Override + public ItemStack assemble(CraftingContainer inventory, HolderLookup.Provider registryAccess) { + var result = super.assemble(inventory, registryAccess); + for (var function : functions) result = function.apply(inventory, result); + return result; + } + + @Override + public RecipeSerializer getSerializer() { + return ModRegistry.RecipeSerializers.TRANSFORM_SHAPELESS.get(); + } +} diff --git a/projects/common/src/main/java/dan200/computercraft/shared/recipe/function/CopyComponents.java b/projects/common/src/main/java/dan200/computercraft/shared/recipe/function/CopyComponents.java new file mode 100644 index 000000000..09f9653e6 --- /dev/null +++ b/projects/common/src/main/java/dan200/computercraft/shared/recipe/function/CopyComponents.java @@ -0,0 +1,179 @@ +// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.shared.recipe.function; + +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import dan200.computercraft.shared.ModRegistry; +import net.minecraft.core.component.DataComponentPatch; +import net.minecraft.core.component.DataComponentType; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.world.inventory.CraftingContainer; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.Ingredient; +import net.minecraft.world.level.ItemLike; +import net.minecraft.world.level.storage.loot.functions.CopyComponentsFunction; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +/** + * A {@link RecipeFunction} that copies components from one of the ingredients to the result. + *

+ * This has the same behaviour as {@linkplain CopyComponentsFunction}, but operating within the scope of recipes. + */ +public final class CopyComponents implements RecipeFunction { + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group( + Ingredient.CODEC_NONEMPTY.fieldOf("from").forGetter(x -> x.from), + DataComponentType.CODEC.listOf().optionalFieldOf("include").forGetter(x -> x.include), + DataComponentType.CODEC.listOf().optionalFieldOf("exclude").forGetter(x -> x.exclude) + ).apply(instance, CopyComponents::new)); + + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + Ingredient.CONTENTS_STREAM_CODEC, x -> x.from, + ByteBufCodecs.optional(DataComponentType.STREAM_CODEC.apply(ByteBufCodecs.list())), x -> x.include, + ByteBufCodecs.optional(DataComponentType.STREAM_CODEC.apply(ByteBufCodecs.list())), x -> x.exclude, + CopyComponents::new + ); + + private final Ingredient from; + private final Optional>> include; + private final Optional>> exclude; + + private final @Nullable Set> includeSet; + private final @Nullable Set> excludeSet; + + /** + * Create a new {@link CopyComponents} that copies all components from an ingredient. + * + * @param from The ingredient to copy from. + */ + public CopyComponents(Ingredient from) { + this(from, Optional.empty(), Optional.empty()); + } + + /** + * Create a new {@link CopyComponents} that copies all components from an ingredient. + * + * @param from The ingredient to copy from. + */ + public CopyComponents(ItemLike from) { + this(Ingredient.of(from)); + } + + private CopyComponents(Ingredient from, Optional>> include, Optional>> exclude) { + this.from = from; + this.include = include.map(List::copyOf); + this.exclude = exclude.map(List::copyOf); + + includeSet = include.map(Set::copyOf).orElse(null); + excludeSet = exclude.map(Set::copyOf).orElse(null); + } + + @Override + public Type getType() { + return ModRegistry.RecipeFunctions.COPY_COMPONENTS.get(); + } + + @Override + public ItemStack apply(CraftingContainer container, ItemStack result) { + for (var item : container.getItems()) { + if (from.test(item)) { + applyPatch(item.getComponentsPatch(), result); + break; + } + } + + return result; + } + + private void applyPatch(DataComponentPatch patch, ItemStack result) { + if (includeSet == null && excludeSet == null) { + result.applyComponents(patch); + return; + } + + // Only apply components in the include set (if present) and not in the exclude set (if present). + for (var component : patch.entrySet()) { + var type = component.getKey(); + if ((includeSet == null || includeSet.contains(type)) && (excludeSet == null || !excludeSet.contains(type))) { + unsafeSetComponent(result, type, component.getValue().orElse(null)); + } + } + } + + @SuppressWarnings("unchecked") + private static void unsafeSetComponent(ItemStack stack, DataComponentType type, @Nullable T value) { + stack.set((DataComponentType) type, value); + } + + /** + * Create a new {@link CopyComponents} builder, that copies components from an ingredient. + * + * @param ingredient The ingredient to copy from. + * @return The builder. + */ + public static Builder builder(Ingredient ingredient) { + return new Builder(ingredient); + } + + /** + * Create a new {@link CopyComponents} builder, that copies components from an ingredient. + * + * @param ingredient The ingredient to copy from. + * @return The builder. + */ + public static Builder builder(ItemLike ingredient) { + return new Builder(Ingredient.of(ingredient)); + } + + public static final class Builder { + private final Ingredient from; + private @Nullable List> include; + private @Nullable List> exclude; + + private Builder(Ingredient from) { + this.from = from; + } + + /** + * Only copy the specified component. + * + * @param type The component to include. + * @return {@code this}, for chaining. + */ + public Builder include(DataComponentType type) { + if (this.include == null) include = new ArrayList<>(); + include.add(type); + return this; + } + + /** + * Exclude a component from being copied. + * + * @param type The component to exclude. + * @return {@code this}, for chaining. + */ + public Builder exclude(DataComponentType type) { + if (exclude == null) exclude = new ArrayList<>(); + exclude.add(type); + return this; + } + + /** + * Build the resulting {@link CopyComponents} instance. + * + * @return The constructed {@link CopyComponents} recipe function. + */ + public CopyComponents build() { + return new CopyComponents(from, Optional.ofNullable(include), Optional.ofNullable(exclude)); + } + } +} diff --git a/projects/common/src/main/java/dan200/computercraft/shared/recipe/function/RecipeFunction.java b/projects/common/src/main/java/dan200/computercraft/shared/recipe/function/RecipeFunction.java new file mode 100644 index 000000000..003c3b255 --- /dev/null +++ b/projects/common/src/main/java/dan200/computercraft/shared/recipe/function/RecipeFunction.java @@ -0,0 +1,90 @@ +// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.shared.recipe.function; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.MapCodec; +import dan200.computercraft.api.ComputerCraftAPI; +import dan200.computercraft.impl.RegistryHelper; +import dan200.computercraft.shared.recipe.TransformShapedRecipe; +import dan200.computercraft.shared.recipe.TransformShapelessRecipe; +import net.minecraft.core.Registry; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.inventory.CraftingContainer; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.storage.loot.functions.LootItemFunction; + +import java.util.List; + +/** + * A function that is applied to the result of a recipe, mutating it in some way. These can be used from within a recipe + * JSON file to define basic dynamic recipes, rather than having to fall back to Java. + *

+ * For instance, the recipe to convert a normal computer to a turtle, is defined as a basic shaped recipes plus an + * additional {@link CopyComponents} function, that copies the id and label from the computer to the turtle. + *

+ * The design and implementation of these are very similar to Minecraft's existing {@linkplain LootItemFunction loot + * functions}. + * + * @see TransformShapedRecipe + * @see TransformShapelessRecipe + */ +public interface RecipeFunction { + /** + * The registry where {@link RecipeFunction}s are registered. + */ + ResourceKey>> REGISTRY = ResourceKey.createRegistryKey(new ResourceLocation(ComputerCraftAPI.MOD_ID, "recipe_function")); + + /** + * The codec to read and write {@link RecipeFunction}s with. + */ + Codec CODEC = Codec.lazyInitialized(() -> RegistryHelper.getRegistry(REGISTRY).byNameCodec().dispatch(RecipeFunction::getType, Type::codec)); + + /** + * A codec for a list of functions. + */ + Codec> LIST_CODEC = CODEC.listOf(1, Integer.MAX_VALUE); + + /** + * The {@link StreamCodec} equivalent of {@link #CODEC}. + */ + StreamCodec STREAM_CODEC = ByteBufCodecs.registry(REGISTRY).dispatch(RecipeFunction::getType, Type::streamCodec); + + /** + * The {@link StreamCodec} equivalent of {@link #LIST_CODEC}. + */ + StreamCodec> LIST_STREAM_CODEC = STREAM_CODEC.apply(ByteBufCodecs.list()); + + /** + * Get the type of this recipe function. + * + * @return The type of this recipe function. + */ + Type getType(); + + /** + * Apply this recipe function, modifying the result item. + * + * @param container The current crafting container. + * @param result The result item to modify. This may be mutated in place. + * @return The new result item. This may be {@code result}. + */ + ItemStack apply(CraftingContainer container, ItemStack result); + + /** + * Properties about a type of {@link RecipeFunction}. These are stored in {@linkplain #REGISTRY a Minecraft + * registry}, and returned by {@link #getType()}. + * + * @param codec The codec to read and write this class of recipe functions with. + * @param streamCodec The network codec to read and write this class of recipe functions with. + * @param The type of recipe function. + */ + record Type(MapCodec codec, StreamCodec streamCodec) { + } +} diff --git a/projects/common/src/main/java/dan200/computercraft/shared/turtle/recipes/TurtleOverlayRecipe.java b/projects/common/src/main/java/dan200/computercraft/shared/turtle/recipes/TurtleOverlayRecipe.java deleted file mode 100644 index 1c2ad765f..000000000 --- a/projects/common/src/main/java/dan200/computercraft/shared/turtle/recipes/TurtleOverlayRecipe.java +++ /dev/null @@ -1,68 +0,0 @@ -// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers -// -// SPDX-License-Identifier: MPL-2.0 - -package dan200.computercraft.shared.turtle.recipes; - -import com.mojang.serialization.MapCodec; -import com.mojang.serialization.codecs.RecordCodecBuilder; -import dan200.computercraft.shared.ModRegistry; -import dan200.computercraft.shared.recipe.BasicRecipeSerialiser; -import dan200.computercraft.shared.recipe.CustomShapelessRecipe; -import dan200.computercraft.shared.recipe.ShapelessRecipeSpec; -import dan200.computercraft.shared.turtle.items.TurtleItem; -import dan200.computercraft.shared.util.DataComponentUtil; -import net.minecraft.core.HolderLookup; -import net.minecraft.network.RegistryFriendlyByteBuf; -import net.minecraft.network.codec.StreamCodec; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.inventory.CraftingContainer; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.crafting.RecipeSerializer; -import net.minecraft.world.item.crafting.ShapelessRecipe; - -/** - * A {@link ShapelessRecipe} which sets the {@linkplain TurtleItem#getOverlay(ItemStack)} turtle's overlay} instead. - */ -public class TurtleOverlayRecipe extends CustomShapelessRecipe { - private static final MapCodec CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group( - ShapelessRecipeSpec.CODEC.forGetter(CustomShapelessRecipe::toSpec), - ResourceLocation.CODEC.fieldOf("overlay").forGetter(x -> x.overlay) - ).apply(instance, TurtleOverlayRecipe::new)); - - private static final StreamCodec STREAM_CODEC = StreamCodec.composite( - ShapelessRecipeSpec.STREAM_CODEC, CustomShapelessRecipe::toSpec, - ResourceLocation.STREAM_CODEC, x -> x.overlay, - TurtleOverlayRecipe::new - ); - - private final ResourceLocation overlay; - - public TurtleOverlayRecipe(ShapelessRecipeSpec spec, ResourceLocation overlay) { - super(spec); - this.overlay = overlay; - } - - private static ItemStack make(ItemStack stack, ResourceLocation overlay) { - return DataComponentUtil.createResult(stack, ModRegistry.DataComponents.OVERLAY.get(), overlay); - } - - @Override - public ItemStack assemble(CraftingContainer inventory, HolderLookup.Provider registryAccess) { - for (var i = 0; i < inventory.getContainerSize(); i++) { - var stack = inventory.getItem(i); - if (stack.getItem() instanceof TurtleItem) return make(stack, overlay); - } - - return ItemStack.EMPTY; - } - - @Override - public RecipeSerializer getSerializer() { - return ModRegistry.RecipeSerializers.TURTLE_OVERLAY.get(); - } - - public static RecipeSerializer serialiser() { - return new BasicRecipeSerialiser<>(CODEC, STREAM_CODEC); - } -} diff --git a/projects/fabric/src/main/java/dan200/computercraft/shared/ComputerCraft.java b/projects/fabric/src/main/java/dan200/computercraft/shared/ComputerCraft.java index eeeabf821..526d68bac 100644 --- a/projects/fabric/src/main/java/dan200/computercraft/shared/ComputerCraft.java +++ b/projects/fabric/src/main/java/dan200/computercraft/shared/ComputerCraft.java @@ -22,12 +22,15 @@ import dan200.computercraft.shared.peripheral.modem.wired.WiredModemFullBlockEntity; import dan200.computercraft.shared.peripheral.modem.wireless.WirelessModemBlockEntity; import dan200.computercraft.shared.platform.FabricConfigFile; +import dan200.computercraft.shared.recipe.function.RecipeFunction; import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerChunkEvents; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; import net.fabricmc.fabric.api.event.player.PlayerBlockBreakEvents; import net.fabricmc.fabric.api.event.player.UseBlockCallback; +import net.fabricmc.fabric.api.event.registry.FabricRegistryBuilder; +import net.fabricmc.fabric.api.event.registry.RegistryAttribute; import net.fabricmc.fabric.api.itemgroup.v1.ItemGroupEvents; import net.fabricmc.fabric.api.loot.v2.LootTableEvents; import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry; @@ -60,6 +63,8 @@ public static void init() { ServerPlayNetworking.registerGlobalReceiver(type.type(), (packet, player) -> packet.handle(player::player)); } + FabricRegistryBuilder.createSimple(RecipeFunction.REGISTRY).attribute(RegistryAttribute.SYNCED).buildAndRegister(); + ModRegistry.register(); ModRegistry.registerMainThread(); diff --git a/projects/forge/src/main/java/dan200/computercraft/ComputerCraft.java b/projects/forge/src/main/java/dan200/computercraft/ComputerCraft.java index a4d1e30b6..f2006cc75 100644 --- a/projects/forge/src/main/java/dan200/computercraft/ComputerCraft.java +++ b/projects/forge/src/main/java/dan200/computercraft/ComputerCraft.java @@ -30,6 +30,7 @@ import dan200.computercraft.shared.peripheral.modem.wired.WiredModemFullBlockEntity; import dan200.computercraft.shared.peripheral.modem.wireless.WirelessModemBlockEntity; import dan200.computercraft.shared.platform.ForgeConfigFile; +import dan200.computercraft.shared.recipe.function.RecipeFunction; import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; import net.minecraft.server.level.ServerPlayer; @@ -81,6 +82,7 @@ public static IEventBus getEventBus() { public static void registerRegistries(NewRegistryEvent event) { event.create(new RegistryBuilder<>(ITurtleUpgrade.serialiserRegistryKey())); event.create(new RegistryBuilder<>(IPocketUpgrade.serialiserRegistryKey())); + event.create(new RegistryBuilder<>(RecipeFunction.REGISTRY).sync(true)); } @SubscribeEvent