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 f23d6711c..719f9a08b 100644 --- a/projects/common/src/main/java/dan200/computercraft/data/RecipeProvider.java +++ b/projects/common/src/main/java/dan200/computercraft/data/RecipeProvider.java @@ -5,6 +5,7 @@ package dan200.computercraft.data; import com.google.gson.JsonObject; +import com.mojang.authlib.GameProfile; import dan200.computercraft.api.ComputerCraftAPI; import dan200.computercraft.api.pocket.PocketUpgradeDataProvider; import dan200.computercraft.api.turtle.TurtleUpgradeDataProvider; @@ -25,15 +26,12 @@ import net.minecraft.core.registries.Registries; import net.minecraft.data.PackOutput; import net.minecraft.data.recipes.*; import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtUtils; import net.minecraft.resources.ResourceLocation; import net.minecraft.tags.TagKey; import net.minecraft.util.GsonHelper; -import net.minecraft.world.item.DyeColor; -import net.minecraft.world.item.DyeItem; -import net.minecraft.world.item.Item; -import net.minecraft.world.item.Items; +import net.minecraft.world.item.*; import net.minecraft.world.item.crafting.Ingredient; -import net.minecraft.world.item.crafting.RecipeSerializer; import net.minecraft.world.item.crafting.ShapedRecipe; import net.minecraft.world.item.crafting.SimpleCraftingRecipeSerializer; import net.minecraft.world.level.ItemLike; @@ -41,6 +39,7 @@ import net.minecraft.world.level.block.Blocks; import java.util.List; import java.util.Locale; +import java.util.UUID; import java.util.function.Consumer; import static dan200.computercraft.api.ComputerCraftTags.Items.COMPUTER; @@ -443,7 +442,7 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider { .requires(ModRegistry.Items.MONITOR_NORMAL.get()) .unlockedBy("has_monitor", inventoryChange(ModRegistry.Items.MONITOR_NORMAL.get())) .save( - RecipeWrapper.wrap(RecipeSerializer.SHAPELESS_RECIPE, add) + RecipeWrapper.wrap(ModRegistry.RecipeSerializers.SHAPELESS.get(), add) .withResultTag(playerHead("Cloudhunter", "6d074736-b1e9-4378-a99b-bd8777821c9c")), new ResourceLocation(ComputerCraftAPI.MOD_ID, "skull_cloudy") ); @@ -454,7 +453,7 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider { .requires(ModRegistry.Items.COMPUTER_ADVANCED.get()) .unlockedBy("has_computer", inventoryChange(ModRegistry.Items.COMPUTER_ADVANCED.get())) .save( - RecipeWrapper.wrap(RecipeSerializer.SHAPELESS_RECIPE, add) + RecipeWrapper.wrap(ModRegistry.RecipeSerializers.SHAPELESS.get(), add) .withResultTag(playerHead("dan200", "f3c8d69b-0776-4512-8434-d1b2165909eb")), new ResourceLocation(ComputerCraftAPI.MOD_ID, "skull_dan200") ); @@ -513,17 +512,15 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider { } private static CompoundTag playerHead(String name, String uuid) { - var owner = new CompoundTag(); - owner.putString("Name", name); - owner.putString("Id", uuid); + var owner = NbtUtils.writeGameProfile(new CompoundTag(), new GameProfile(UUID.fromString(uuid), name)); var tag = new CompoundTag(); - tag.put("SkullOwner", owner); + tag.put(PlayerHeadItem.TAG_SKULL_OWNER, owner); return tag; } private static Consumer family(ComputerFamily family) { - return json -> json.addProperty("family", family.toString()); + return json -> json.addProperty("family", family.getSerializedName()); } private static void addSpecial(Consumer add, SimpleCraftingRecipeSerializer special) { 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 98ba9e8a9..1af983037 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/ModRegistry.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/ModRegistry.java @@ -70,6 +70,10 @@ import dan200.computercraft.shared.pocket.items.PocketComputerItem; 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.turtle.FurnaceRefuelHandler; import dan200.computercraft.shared.turtle.blocks.TurtleBlock; import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity; @@ -79,8 +83,6 @@ import dan200.computercraft.shared.turtle.recipes.TurtleOverlayRecipe; import dan200.computercraft.shared.turtle.recipes.TurtleRecipe; import dan200.computercraft.shared.turtle.recipes.TurtleUpgradeRecipe; import dan200.computercraft.shared.turtle.upgrades.*; -import dan200.computercraft.shared.util.ImpostorRecipe; -import dan200.computercraft.shared.util.ImpostorShapelessRecipe; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.synchronization.ArgumentTypeInfo; import net.minecraft.commands.synchronization.SingletonArgumentInfo; @@ -359,17 +361,21 @@ public final class ModRegistry { return REGISTRY.register(name, () -> new SimpleCraftingRecipeSerializer<>(factory)); } + public static final RegistryEntry> SHAPED = REGISTRY.register("shaped", () -> CustomShapedRecipe.serialiser(CustomShapedRecipe::new)); + public static final RegistryEntry> SHAPELESS = REGISTRY.register("shapeless", () -> CustomShapelessRecipe.serialiser(CustomShapelessRecipe::new)); + + 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> DYEABLE_ITEM = simple("colour", ColourableRecipe::new); public static final RegistryEntry> DYEABLE_ITEM_CLEAR = simple("clear_colour", ClearColourRecipe::new); - public static final RegistryEntry TURTLE = REGISTRY.register("turtle", TurtleRecipe.Serializer::new); + public static final RegistryEntry> TURTLE = REGISTRY.register("turtle", () -> TurtleRecipe.validatingSerialiser(TurtleRecipe::of)); public static final RegistryEntry> TURTLE_UPGRADE = simple("turtle_upgrade", TurtleUpgradeRecipe::new); - public static final RegistryEntry TURTLE_OVERLAY = REGISTRY.register("turtle_overlay", TurtleOverlayRecipe.Serializer::new); + public static final RegistryEntry> TURTLE_OVERLAY = REGISTRY.register("turtle_overlay", TurtleOverlayRecipe.Serializer::new); 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_UPGRADE = REGISTRY.register("computer_upgrade", ComputerUpgradeRecipe.Serializer::new); - public static final RegistryEntry IMPOSTOR_SHAPED = REGISTRY.register("impostor_shaped", ImpostorRecipe.Serializer::new); - public static final RegistryEntry IMPOSTOR_SHAPELESS = REGISTRY.register("impostor_shapeless", ImpostorShapelessRecipe.Serializer::new); + public static final RegistryEntry> COMPUTER_UPGRADE = REGISTRY.register("computer_upgrade", ComputerUpgradeRecipe.Serializer::new); } public static class Permissions { diff --git a/projects/common/src/main/java/dan200/computercraft/shared/computer/core/ComputerFamily.java b/projects/common/src/main/java/dan200/computercraft/shared/computer/core/ComputerFamily.java index 1944df94e..da1b912c2 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/computer/core/ComputerFamily.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/computer/core/ComputerFamily.java @@ -4,8 +4,33 @@ package dan200.computercraft.shared.computer.core; -public enum ComputerFamily { - NORMAL, - ADVANCED, - COMMAND +import com.google.gson.JsonObject; +import com.google.gson.JsonSyntaxException; +import net.minecraft.util.GsonHelper; +import net.minecraft.util.StringRepresentable; + +public enum ComputerFamily implements StringRepresentable { + NORMAL("normal"), + ADVANCED("advanced"), + COMMAND("command"); + + private final String name; + + ComputerFamily(String name) { + this.name = name; + } + + public static ComputerFamily getFamily(JsonObject json, String name) { + var familyName = GsonHelper.getAsString(json, name); + for (var family : values()) { + if (family.getSerializedName().equalsIgnoreCase(familyName)) return family; + } + + throw new JsonSyntaxException("Unknown computer family '" + familyName + "' for field " + name); + } + + @Override + public String getSerializedName() { + return name; + } } 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 index 6ea7096a4..ab8fa2a20 100644 --- 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 @@ -5,31 +5,20 @@ package dan200.computercraft.shared.computer.recipe; import dan200.computercraft.shared.computer.items.IComputerItem; -import net.minecraft.core.NonNullList; +import dan200.computercraft.shared.recipe.CustomShapedRecipe; +import dan200.computercraft.shared.recipe.ShapedRecipeSpec; import net.minecraft.core.RegistryAccess; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.inventory.CraftingContainer; 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.ShapedRecipe; import net.minecraft.world.level.Level; /** - * Represents a recipe which converts a computer from one form into another. + * A recipe which converts a computer from one form into another. */ -public abstract class ComputerConvertRecipe extends ShapedRecipe { - private final String group; - private final ItemStack result; - - public ComputerConvertRecipe(ResourceLocation identifier, String group, CraftingBookCategory category, int width, int height, NonNullList ingredients, ItemStack result) { - super(identifier, group, category, width, height, ingredients, result); - this.group = group; - this.result = result; - } - - public ItemStack getResultItem() { - return result; +public abstract class ComputerConvertRecipe extends CustomShapedRecipe { + public ComputerConvertRecipe(ResourceLocation identifier, ShapedRecipeSpec recipe) { + super(identifier, recipe); } protected abstract ItemStack convert(IComputerItem item, ItemStack stack); @@ -55,9 +44,4 @@ public abstract class ComputerConvertRecipe extends ShapedRecipe { return ItemStack.EMPTY; } - - @Override - public String getGroup() { - return group; - } } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/computer/recipe/ComputerFamilyRecipe.java b/projects/common/src/main/java/dan200/computercraft/shared/computer/recipe/ComputerFamilyRecipe.java deleted file mode 100644 index a87a4f987..000000000 --- a/projects/common/src/main/java/dan200/computercraft/shared/computer/recipe/ComputerFamilyRecipe.java +++ /dev/null @@ -1,72 +0,0 @@ -// SPDX-FileCopyrightText: 2018 The CC: Tweaked Developers -// -// SPDX-License-Identifier: MPL-2.0 - -package dan200.computercraft.shared.computer.recipe; - -import com.google.gson.JsonObject; -import dan200.computercraft.shared.computer.core.ComputerFamily; -import dan200.computercraft.shared.util.RecipeUtil; -import net.minecraft.core.NonNullList; -import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.util.GsonHelper; -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; - -public abstract class ComputerFamilyRecipe extends ComputerConvertRecipe { - private final ComputerFamily family; - - public ComputerFamilyRecipe(ResourceLocation identifier, String group, CraftingBookCategory category, int width, int height, NonNullList ingredients, ItemStack result, ComputerFamily family) { - super(identifier, group, category, width, height, ingredients, result); - this.family = family; - } - - public ComputerFamily getFamily() { - return family; - } - - public abstract static class Serializer implements RecipeSerializer { - protected abstract T create(ResourceLocation identifier, String group, CraftingBookCategory category, int width, int height, NonNullList ingredients, ItemStack result, ComputerFamily family); - - @Override - public T fromJson(ResourceLocation identifier, JsonObject json) { - var group = GsonHelper.getAsString(json, "group", ""); - var category = CraftingBookCategory.CODEC.byName(GsonHelper.getAsString(json, "category", null), CraftingBookCategory.MISC); - var family = RecipeUtil.getFamily(json, "family"); - - var template = RecipeUtil.getTemplate(json); - var result = itemStackFromJson(GsonHelper.getAsJsonObject(json, "result")); - - return create(identifier, group, category, template.width(), template.height(), template.ingredients(), result, family); - } - - @Override - public T fromNetwork(ResourceLocation identifier, FriendlyByteBuf buf) { - var width = buf.readVarInt(); - var height = buf.readVarInt(); - var group = buf.readUtf(); - var category = buf.readEnum(CraftingBookCategory.class); - - var ingredients = NonNullList.withSize(width * height, Ingredient.EMPTY); - for (var i = 0; i < ingredients.size(); i++) ingredients.set(i, Ingredient.fromNetwork(buf)); - - var result = buf.readItem(); - var family = buf.readEnum(ComputerFamily.class); - return create(identifier, group, category, width, height, ingredients, result, family); - } - - @Override - public void toNetwork(FriendlyByteBuf buf, T recipe) { - buf.writeVarInt(recipe.getWidth()); - buf.writeVarInt(recipe.getHeight()); - buf.writeUtf(recipe.getGroup()); - buf.writeEnum(recipe.category()); - for (var ingredient : recipe.getIngredients()) ingredient.toNetwork(buf); - buf.writeItem(recipe.getResultItem()); - buf.writeEnum(recipe.getFamily()); - } - } -} diff --git a/projects/common/src/main/java/dan200/computercraft/shared/computer/recipe/ComputerUpgradeRecipe.java b/projects/common/src/main/java/dan200/computercraft/shared/computer/recipe/ComputerUpgradeRecipe.java index 8a5e530fa..075695752 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/computer/recipe/ComputerUpgradeRecipe.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/computer/recipe/ComputerUpgradeRecipe.java @@ -4,35 +4,57 @@ package dan200.computercraft.shared.computer.recipe; +import com.google.gson.JsonObject; import dan200.computercraft.shared.ModRegistry; import dan200.computercraft.shared.computer.core.ComputerFamily; import dan200.computercraft.shared.computer.items.IComputerItem; -import net.minecraft.core.NonNullList; +import dan200.computercraft.shared.recipe.ShapedRecipeSpec; +import net.minecraft.network.FriendlyByteBuf; import net.minecraft.resources.ResourceLocation; 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; -public final class ComputerUpgradeRecipe extends ComputerFamilyRecipe { - private ComputerUpgradeRecipe(ResourceLocation identifier, String group, CraftingBookCategory category, int width, int height, NonNullList ingredients, ItemStack result, ComputerFamily family) { - super(identifier, group, category, width, height, ingredients, result, family); +/** + * A recipe which "upgrades" a {@linkplain IComputerItem computer}, converting it from one {@linkplain ComputerFamily + * family} to another. + */ +public final class ComputerUpgradeRecipe extends ComputerConvertRecipe { + private final ComputerFamily family; + + private ComputerUpgradeRecipe(ResourceLocation identifier, ShapedRecipeSpec recipe, ComputerFamily family) { + super(identifier, recipe); + this.family = family; } @Override protected ItemStack convert(IComputerItem item, ItemStack stack) { - return item.withFamily(stack, getFamily()); + return item.withFamily(stack, family); } @Override - public RecipeSerializer getSerializer() { + public RecipeSerializer getSerializer() { return ModRegistry.RecipeSerializers.COMPUTER_UPGRADE.get(); } - public static class Serializer extends ComputerFamilyRecipe.Serializer { + public static class Serializer implements RecipeSerializer { @Override - protected ComputerUpgradeRecipe create(ResourceLocation identifier, String group, CraftingBookCategory category, int width, int height, NonNullList ingredients, ItemStack result, ComputerFamily family) { - return new ComputerUpgradeRecipe(identifier, group, category, width, height, ingredients, result, family); + public ComputerUpgradeRecipe fromJson(ResourceLocation identifier, JsonObject json) { + var recipe = ShapedRecipeSpec.fromJson(json); + var family = ComputerFamily.getFamily(json, "family"); + return new ComputerUpgradeRecipe(identifier, recipe, family); + } + + @Override + public ComputerUpgradeRecipe fromNetwork(ResourceLocation identifier, FriendlyByteBuf buf) { + var recipe = ShapedRecipeSpec.fromNetwork(buf); + var family = buf.readEnum(ComputerFamily.class); + return new ComputerUpgradeRecipe(identifier, recipe, family); + } + + @Override + public void toNetwork(FriendlyByteBuf buf, ComputerUpgradeRecipe recipe) { + recipe.toSpec().toNetwork(buf); + buf.writeEnum(recipe.family); } } } 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 new file mode 100644 index 000000000..c1a7b27a0 --- /dev/null +++ b/projects/common/src/main/java/dan200/computercraft/shared/recipe/CustomShapedRecipe.java @@ -0,0 +1,86 @@ +// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.shared.recipe; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.mojang.serialization.DataResult; +import dan200.computercraft.shared.ModRegistry; +import net.minecraft.Util; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.RecipeSerializer; +import net.minecraft.world.item.crafting.ShapedRecipe; + +/** + * A custom version of {@link ShapedRecipe}, which can be converted to and from a {@link ShapedRecipeSpec}. + *

+ * This recipe may both be used as a normal recipe (behaving mostly the same as {@link ShapedRecipe}, with + * {@linkplain RecipeUtil#itemStackFromJson(JsonObject) support for putting nbt on the result}), or subclassed to + * customise the crafting behaviour. + */ +public class CustomShapedRecipe extends ShapedRecipe { + private final ItemStack result; + + public CustomShapedRecipe(ResourceLocation id, ShapedRecipeSpec recipe) { + super( + id, + recipe.properties().group(), recipe.properties().category(), + recipe.template().width(), recipe.template().height(), recipe.template().ingredients(), + recipe.result() + ); + this.result = recipe.result(); + } + + public final ShapedRecipeSpec toSpec() { + return new ShapedRecipeSpec(RecipeProperties.of(this), ShapedTemplate.of(this), result); + } + + @Override + public RecipeSerializer getSerializer() { + return ModRegistry.RecipeSerializers.SHAPED.get(); + } + + public interface Factory { + R create(ResourceLocation id, ShapedRecipeSpec recipe); + } + + public static RecipeSerializer serialiser(CustomShapedRecipe.Factory factory) { + return new Serialiser<>((id, r) -> DataResult.success(factory.create(id, r))); + } + + public static RecipeSerializer validatingSerialiser(CustomShapedRecipe.Factory> factory) { + return new Serialiser<>(factory); + } + + private record Serialiser( + Factory> factory + ) implements RecipeSerializer { + private Serialiser(Factory> factory) { + this.factory = (id, r) -> factory.create(id, r).flatMap(x -> { + if (x.getSerializer() != this) { + return DataResult.error(() -> "Expected serialiser to be " + this + ", but was " + x.getSerializer()); + } + return DataResult.success(x); + }); + } + + @Override + public T fromJson(ResourceLocation id, JsonObject json) { + return Util.getOrThrow(factory.create(id, ShapedRecipeSpec.fromJson(json)), JsonParseException::new); + } + + @Override + public T fromNetwork(ResourceLocation id, FriendlyByteBuf buffer) { + return Util.getOrThrow(factory.create(id, ShapedRecipeSpec.fromNetwork(buffer)), IllegalStateException::new); + } + + @Override + public void toNetwork(FriendlyByteBuf buffer, T recipe) { + recipe.toSpec().toNetwork(buffer); + } + } +} diff --git a/projects/common/src/main/java/dan200/computercraft/shared/recipe/CustomShapelessRecipe.java b/projects/common/src/main/java/dan200/computercraft/shared/recipe/CustomShapelessRecipe.java new file mode 100644 index 000000000..e948d411d --- /dev/null +++ b/projects/common/src/main/java/dan200/computercraft/shared/recipe/CustomShapelessRecipe.java @@ -0,0 +1,81 @@ +// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.shared.recipe; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.mojang.serialization.DataResult; +import dan200.computercraft.shared.ModRegistry; +import net.minecraft.Util; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.RecipeSerializer; +import net.minecraft.world.item.crafting.ShapelessRecipe; + +/** + * A custom version of {@link ShapelessRecipe}, which can be converted to and from a {@link ShapelessRecipeSpec}. + *

+ * This recipe may both be used as a normal recipe (behaving mostly the same as {@link ShapelessRecipe}, with + * {@linkplain RecipeUtil#itemStackFromJson(JsonObject) support for putting nbt on the result}), or subclassed to + * customise the crafting behaviour. + */ +public class CustomShapelessRecipe extends ShapelessRecipe { + private final ItemStack result; + + public CustomShapelessRecipe(ResourceLocation id, ShapelessRecipeSpec recipe) { + super(id, recipe.properties().group(), recipe.properties().category(), recipe.result(), recipe.ingredients()); + this.result = recipe.result(); + } + + public final ShapelessRecipeSpec toSpec() { + return new ShapelessRecipeSpec(RecipeProperties.of(this), getIngredients(), result); + } + + @Override + public RecipeSerializer getSerializer() { + return ModRegistry.RecipeSerializers.SHAPELESS.get(); + } + + public interface Factory { + R create(ResourceLocation id, ShapelessRecipeSpec recipe); + } + + public static RecipeSerializer serialiser(Factory factory) { + return new CustomShapelessRecipe.Serialiser<>((id, r) -> DataResult.success(factory.create(id, r))); + } + + public static RecipeSerializer validatingSerialiser(Factory> factory) { + return new CustomShapelessRecipe.Serialiser<>(factory); + } + + private record Serialiser( + Factory> factory + ) implements RecipeSerializer { + private Serialiser(Factory> factory) { + this.factory = (id, r) -> factory.create(id, r).flatMap(x -> { + if (x.getSerializer() != this) { + return DataResult.error(() -> "Expected serialiser to be " + this + ", but was " + x.getSerializer()); + } + return DataResult.success(x); + }); + } + + @Override + public T fromJson(ResourceLocation id, JsonObject json) { + return Util.getOrThrow(factory.create(id, ShapelessRecipeSpec.fromJson(json)), JsonParseException::new); + } + + @Override + public T fromNetwork(ResourceLocation id, FriendlyByteBuf buffer) { + return Util.getOrThrow(factory.create(id, ShapelessRecipeSpec.fromNetwork(buffer)), IllegalStateException::new); + } + + @Override + public void toNetwork(FriendlyByteBuf buffer, T recipe) { + recipe.toSpec().toNetwork(buffer); + } + } +} diff --git a/projects/common/src/main/java/dan200/computercraft/shared/recipe/ImpostorShapedRecipe.java b/projects/common/src/main/java/dan200/computercraft/shared/recipe/ImpostorShapedRecipe.java new file mode 100644 index 000000000..f598cbee8 --- /dev/null +++ b/projects/common/src/main/java/dan200/computercraft/shared/recipe/ImpostorShapedRecipe.java @@ -0,0 +1,41 @@ +// Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. +// +// SPDX-License-Identifier: LicenseRef-CCPL + +package dan200.computercraft.shared.recipe; + +import dan200.computercraft.shared.ModRegistry; +import net.minecraft.core.RegistryAccess; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.inventory.CraftingContainer; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.CustomRecipe; +import net.minecraft.world.item.crafting.RecipeSerializer; +import net.minecraft.world.item.crafting.ShapedRecipe; +import net.minecraft.world.level.Level; + +/** + * A fake {@link ShapedRecipe}, which appears in the recipe book (and other recipe mods), but cannot be crafted. + *

+ * This is used to represent examples for our {@link CustomRecipe}s. + */ +public final class ImpostorShapedRecipe extends CustomShapedRecipe { + public ImpostorShapedRecipe(ResourceLocation id, ShapedRecipeSpec recipe) { + super(id, recipe); + } + + @Override + public boolean matches(CraftingContainer inv, Level world) { + return false; + } + + @Override + public ItemStack assemble(CraftingContainer inventory, RegistryAccess registryAccess) { + return ItemStack.EMPTY; + } + + @Override + public RecipeSerializer getSerializer() { + return ModRegistry.RecipeSerializers.IMPOSTOR_SHAPED.get(); + } +} diff --git a/projects/common/src/main/java/dan200/computercraft/shared/recipe/ImpostorShapelessRecipe.java b/projects/common/src/main/java/dan200/computercraft/shared/recipe/ImpostorShapelessRecipe.java new file mode 100644 index 000000000..25974cb3d --- /dev/null +++ b/projects/common/src/main/java/dan200/computercraft/shared/recipe/ImpostorShapelessRecipe.java @@ -0,0 +1,41 @@ +// Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. +// +// SPDX-License-Identifier: LicenseRef-CCPL + +package dan200.computercraft.shared.recipe; + +import dan200.computercraft.shared.ModRegistry; +import net.minecraft.core.RegistryAccess; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.inventory.CraftingContainer; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.CustomRecipe; +import net.minecraft.world.item.crafting.RecipeSerializer; +import net.minecraft.world.item.crafting.ShapelessRecipe; +import net.minecraft.world.level.Level; + +/** + * A fake {@link ShapelessRecipe}, which appears in the recipe book (and other recipe mods), but cannot be crafted. + *

+ * This is used to represent examples for our {@link CustomRecipe}s. + */ +public final class ImpostorShapelessRecipe extends CustomShapelessRecipe { + public ImpostorShapelessRecipe(ResourceLocation id, ShapelessRecipeSpec recipe) { + super(id, recipe); + } + + @Override + public boolean matches(CraftingContainer inv, Level world) { + return false; + } + + @Override + public ItemStack assemble(CraftingContainer inventory, RegistryAccess access) { + return ItemStack.EMPTY; + } + + @Override + public RecipeSerializer getSerializer() { + return ModRegistry.RecipeSerializers.IMPOSTOR_SHAPELESS.get(); + } +} diff --git a/projects/common/src/main/java/dan200/computercraft/shared/recipe/MoreCodecs.java b/projects/common/src/main/java/dan200/computercraft/shared/recipe/MoreCodecs.java new file mode 100644 index 000000000..9de9c1eab --- /dev/null +++ b/projects/common/src/main/java/dan200/computercraft/shared/recipe/MoreCodecs.java @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.shared.recipe; + +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.datafixers.util.Either; +import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.TagParser; + +/** + * Additional codecs for working with recipes. + */ +public class MoreCodecs { + /** + * A codec for {@link CompoundTag}s, which either accepts a NBT-string or a JSON object. + */ + public static final Codec TAG = Codec.either(Codec.STRING, CompoundTag.CODEC).flatXmap( + either -> either.map(MoreCodecs::parseTag, DataResult::success), + nbtCompound -> DataResult.success(Either.left(nbtCompound.getAsString())) + ); + + private static DataResult parseTag(String contents) { + try { + return DataResult.success(TagParser.parseTag(contents)); + } catch (CommandSyntaxException e) { + return DataResult.error(e::getMessage); + } + } +} diff --git a/projects/common/src/main/java/dan200/computercraft/shared/recipe/RecipeProperties.java b/projects/common/src/main/java/dan200/computercraft/shared/recipe/RecipeProperties.java new file mode 100644 index 000000000..9048bc219 --- /dev/null +++ b/projects/common/src/main/java/dan200/computercraft/shared/recipe/RecipeProperties.java @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.shared.recipe; + +import com.google.gson.JsonObject; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.util.GsonHelper; +import net.minecraft.world.item.crafting.CraftingBookCategory; +import net.minecraft.world.item.crafting.CraftingRecipe; + +/** + * Common properties that appear in all {@link CraftingRecipe}s. + * + * @param group The (optional) group of the recipe, see {@link CraftingRecipe#getGroup()}. + * @param category The category the recipe appears in, see {@link CraftingRecipe#category()}. + */ +public record RecipeProperties(String group, CraftingBookCategory category) { + public static RecipeProperties of(CraftingRecipe recipe) { + return new RecipeProperties(recipe.getGroup(), recipe.category()); + } + + public static RecipeProperties fromJson(JsonObject json) { + var group = GsonHelper.getAsString(json, "group", ""); + var category = CraftingBookCategory.CODEC.byName(GsonHelper.getAsString(json, "category", null), CraftingBookCategory.MISC); + return new RecipeProperties(group, category); + } + + public static RecipeProperties fromNetwork(FriendlyByteBuf buffer) { + var group = buffer.readUtf(); + var category = buffer.readEnum(CraftingBookCategory.class); + return new RecipeProperties(group, category); + } + + public void toNetwork(FriendlyByteBuf buffer) { + buffer.writeUtf(group()); + buffer.writeEnum(category()); + } +} diff --git a/projects/common/src/main/java/dan200/computercraft/shared/recipe/RecipeUtil.java b/projects/common/src/main/java/dan200/computercraft/shared/recipe/RecipeUtil.java new file mode 100644 index 000000000..482d1ea16 --- /dev/null +++ b/projects/common/src/main/java/dan200/computercraft/shared/recipe/RecipeUtil.java @@ -0,0 +1,70 @@ +// SPDX-FileCopyrightText: 2017 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.shared.recipe; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonSyntaxException; +import com.mojang.serialization.JsonOps; +import net.minecraft.Util; +import net.minecraft.core.NonNullList; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.util.GsonHelper; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.Ingredient; +import net.minecraft.world.item.crafting.ShapedRecipe; + +public final class RecipeUtil { + private RecipeUtil() { + } + + public static NonNullList readIngredients(FriendlyByteBuf buffer) { + return buffer.readCollection(x -> NonNullList.withSize(x, Ingredient.EMPTY), Ingredient::fromNetwork); + } + + public static void writeIngredients(FriendlyByteBuf buffer, NonNullList ingredients) { + buffer.writeCollection(ingredients, (a, b) -> b.toNetwork(a)); + } + + public static NonNullList readShapelessIngredients(JsonObject json) { + NonNullList ingredients = NonNullList.create(); + + var ingredientsList = GsonHelper.getAsJsonArray(json, "ingredients"); + for (var i = 0; i < ingredientsList.size(); ++i) { + var ingredient = Ingredient.fromJson(ingredientsList.get(i)); + if (!ingredient.isEmpty()) ingredients.add(ingredient); + } + + if (ingredients.isEmpty()) throw new JsonParseException("No ingredients for shapeless recipe"); + if (ingredients.size() > 9) { + throw new JsonParseException("Too many ingredients for shapeless recipe the max is 9"); + } + + return ingredients; + } + + /** + * Extends {@link ShapedRecipe#itemStackFromJson(JsonObject)} with support for the {@code nbt} field. + * + * @param json The json to extract the item from. + * @return The parsed item stack. + */ + public static ItemStack itemStackFromJson(JsonObject json) { + var item = ShapedRecipe.itemFromJson(json); + if (json.has("data")) throw new JsonParseException("Disallowed data tag found"); + + var count = GsonHelper.getAsInt(json, "count", 1); + if (count < 1) throw new JsonSyntaxException("Invalid output count: " + count); + + var stack = new ItemStack(item, count); + + var nbt = json.get("nbt"); + if (nbt != null) { + stack.setTag(Util.getOrThrow(MoreCodecs.TAG.parse(JsonOps.INSTANCE, nbt), JsonParseException::new)); + } + return stack; + } + +} diff --git a/projects/common/src/main/java/dan200/computercraft/shared/recipe/ShapedRecipeSpec.java b/projects/common/src/main/java/dan200/computercraft/shared/recipe/ShapedRecipeSpec.java new file mode 100644 index 000000000..a93115ba0 --- /dev/null +++ b/projects/common/src/main/java/dan200/computercraft/shared/recipe/ShapedRecipeSpec.java @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.shared.recipe; + +import com.google.gson.JsonObject; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.util.GsonHelper; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.ShapedRecipe; + +/** + * A description of a {@link ShapedRecipe}. + *

+ * This is meant to be used in conjunction with {@link CustomShapedRecipe} for more reusable serialisation and + * deserialisation of {@link ShapedRecipe}-like recipes. + * + * @param properties The common properties of this recipe. + * @param template The shaped template of the recipe. + * @param result The result of the recipe. + */ +public record ShapedRecipeSpec(RecipeProperties properties, ShapedTemplate template, ItemStack result) { + public static ShapedRecipeSpec fromJson(JsonObject json) { + var properties = RecipeProperties.fromJson(json); + var template = ShapedTemplate.fromJson(json); + var result = RecipeUtil.itemStackFromJson(GsonHelper.getAsJsonObject(json, "result")); + return new ShapedRecipeSpec(properties, template, result); + } + + public static ShapedRecipeSpec fromNetwork(FriendlyByteBuf buffer) { + var properties = RecipeProperties.fromNetwork(buffer); + var template = ShapedTemplate.fromNetwork(buffer); + var result = buffer.readItem(); + return new ShapedRecipeSpec(properties, template, result); + } + + public void toNetwork(FriendlyByteBuf buffer) { + properties().toNetwork(buffer); + template().toNetwork(buffer); + buffer.writeItem(result()); + } +} diff --git a/projects/common/src/main/java/dan200/computercraft/shared/recipe/ShapedTemplate.java b/projects/common/src/main/java/dan200/computercraft/shared/recipe/ShapedTemplate.java new file mode 100644 index 000000000..ef84079f9 --- /dev/null +++ b/projects/common/src/main/java/dan200/computercraft/shared/recipe/ShapedTemplate.java @@ -0,0 +1,99 @@ +// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.shared.recipe; + +import com.google.gson.JsonObject; +import com.google.gson.JsonSyntaxException; +import net.minecraft.core.NonNullList; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.util.GsonHelper; +import net.minecraft.world.item.crafting.Ingredient; +import net.minecraft.world.item.crafting.ShapedRecipe; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * The template for {@linkplain ShapedRecipe shaped recipes}. This largely exists for parsing shaped recipes from JSON. + * + * @param width The width of the recipe, see {@link ShapedRecipe#getWidth()}. + * @param height The height of the recipe, see {@link ShapedRecipe#getHeight()}. + * @param ingredients The ingredients in the recipe, see {@link ShapedRecipe#getIngredients()} + */ +public record ShapedTemplate(int width, int height, NonNullList ingredients) { + public static ShapedTemplate of(ShapedRecipe recipe) { + return new ShapedTemplate(recipe.getWidth(), recipe.getHeight(), recipe.getIngredients()); + } + + public static ShapedTemplate fromJson(JsonObject json) { + Map key = new HashMap<>(); + for (var entry : GsonHelper.getAsJsonObject(json, "key").entrySet()) { + if (entry.getKey().length() != 1) { + throw new JsonSyntaxException("Invalid key entry: '" + entry.getKey() + "' is an invalid symbol (must be 1 character only)."); + } + if (" ".equals(entry.getKey())) { + throw new JsonSyntaxException("Invalid key entry: ' ' is a reserved symbol."); + } + + key.put(entry.getKey().charAt(0), Ingredient.fromJson(entry.getValue())); + } + + var patternList = GsonHelper.getAsJsonArray(json, "pattern"); + if (patternList.size() == 0) { + throw new JsonSyntaxException("Invalid pattern: empty pattern not allowed"); + } + + var pattern = new String[patternList.size()]; + for (var x = 0; x < pattern.length; x++) { + var line = GsonHelper.convertToString(patternList.get(x), "pattern[" + x + "]"); + if (x > 0 && pattern[0].length() != line.length()) { + throw new JsonSyntaxException("Invalid pattern: each row must be the same width"); + } + pattern[x] = line; + } + + var width = pattern[0].length(); + var height = pattern.length; + var ingredients = NonNullList.withSize(width * height, Ingredient.EMPTY); + + Set missingKeys = new HashSet<>(key.keySet()); + + var ingredientIdx = 0; + for (var line : pattern) { + for (var x = 0; x < line.length(); x++) { + var chr = line.charAt(x); + var ing = chr == ' ' ? Ingredient.EMPTY : key.get(chr); + if (ing == null) { + throw new JsonSyntaxException("Pattern references symbol '" + chr + "' but it's not defined in the key"); + } + ingredients.set(ingredientIdx++, ing); + missingKeys.remove(chr); + } + } + + if (!missingKeys.isEmpty()) { + throw new JsonSyntaxException("Key defines symbols that aren't used in pattern: " + missingKeys); + } + + return new ShapedTemplate(width, height, ingredients); + } + + public static ShapedTemplate fromNetwork(FriendlyByteBuf buffer) { + var width = buffer.readVarInt(); + var height = buffer.readVarInt(); + var ingredients = NonNullList.withSize(width * height, Ingredient.EMPTY); + for (var i = 0; i < ingredients.size(); ++i) ingredients.set(i, Ingredient.fromNetwork(buffer)); + return new ShapedTemplate(width, height, ingredients); + } + + public void toNetwork(FriendlyByteBuf buffer) { + buffer.writeVarInt(width()); + buffer.writeVarInt(height()); + for (var ingredient : ingredients) ingredient.toNetwork(buffer); + } + +} diff --git a/projects/common/src/main/java/dan200/computercraft/shared/recipe/ShapelessRecipeSpec.java b/projects/common/src/main/java/dan200/computercraft/shared/recipe/ShapelessRecipeSpec.java new file mode 100644 index 000000000..7a3011f5c --- /dev/null +++ b/projects/common/src/main/java/dan200/computercraft/shared/recipe/ShapelessRecipeSpec.java @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.shared.recipe; + +import com.google.gson.JsonObject; +import net.minecraft.core.NonNullList; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.util.GsonHelper; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.Ingredient; +import net.minecraft.world.item.crafting.ShapelessRecipe; + +/** + * A description of a {@link ShapelessRecipe}. + *

+ * This is meant to be used in conjunction with {@link CustomShapelessRecipe} for more reusable serialisation and + * deserialisation of {@link ShapelessRecipe}-like recipes. + * + * @param properties The common properties of this recipe. + * @param ingredients The ingredients of the recipe. + * @param result The result of the recipe. + */ +public record ShapelessRecipeSpec(RecipeProperties properties, NonNullList ingredients, ItemStack result) { + public static ShapelessRecipeSpec fromJson(JsonObject json) { + var properties = RecipeProperties.fromJson(json); + var ingredients = RecipeUtil.readShapelessIngredients(json); + var result = RecipeUtil.itemStackFromJson(GsonHelper.getAsJsonObject(json, "result")); + return new ShapelessRecipeSpec(properties, ingredients, result); + } + + public static ShapelessRecipeSpec fromNetwork(FriendlyByteBuf buffer) { + var properties = RecipeProperties.fromNetwork(buffer); + var ingredients = RecipeUtil.readIngredients(buffer); + var result = buffer.readItem(); + + return new ShapelessRecipeSpec(properties, ingredients, result); + } + + public void toNetwork(FriendlyByteBuf buffer) { + properties().toNetwork(buffer); + RecipeUtil.writeIngredients(buffer, ingredients()); + buffer.writeItem(result()); + } +} 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 index 7f31bca95..57609c1ce 100644 --- 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 @@ -4,32 +4,30 @@ package dan200.computercraft.shared.turtle.recipes; -import com.google.gson.JsonArray; import com.google.gson.JsonObject; -import com.google.gson.JsonParseException; import dan200.computercraft.api.turtle.TurtleSide; import dan200.computercraft.shared.ModRegistry; +import dan200.computercraft.shared.recipe.CustomShapelessRecipe; +import dan200.computercraft.shared.recipe.ShapelessRecipeSpec; import dan200.computercraft.shared.turtle.items.TurtleItem; -import net.minecraft.core.NonNullList; import net.minecraft.core.RegistryAccess; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.resources.ResourceLocation; import net.minecraft.util.GsonHelper; import net.minecraft.world.inventory.CraftingContainer; import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.crafting.*; +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 ShapelessRecipe { +public class TurtleOverlayRecipe extends CustomShapelessRecipe { private final ResourceLocation overlay; - private final ItemStack result; - public TurtleOverlayRecipe(ResourceLocation id, String group, CraftingBookCategory category, ItemStack result, NonNullList ingredients, ResourceLocation overlay) { - super(id, group, category, result, ingredients); + public TurtleOverlayRecipe(ResourceLocation id, ShapelessRecipeSpec spec, ResourceLocation overlay) { + super(id, spec); this.overlay = overlay; - this.result = result; } private static ItemStack make(ItemStack stack, ResourceLocation overlay) { @@ -56,63 +54,29 @@ public class TurtleOverlayRecipe extends ShapelessRecipe { } @Override - public RecipeSerializer getSerializer() { + public RecipeSerializer getSerializer() { return ModRegistry.RecipeSerializers.TURTLE_OVERLAY.get(); } public static class Serializer implements RecipeSerializer { @Override public TurtleOverlayRecipe fromJson(ResourceLocation id, JsonObject json) { - var group = GsonHelper.getAsString(json, "group", ""); - var category = CraftingBookCategory.CODEC.byName(GsonHelper.getAsString(json, "category", null), CraftingBookCategory.MISC); - var ingredients = readIngredients(GsonHelper.getAsJsonArray(json, "ingredients")); - - if (ingredients.isEmpty()) throw new JsonParseException("No ingredients for shapeless recipe"); - if (ingredients.size() > 9) { - throw new JsonParseException("Too many ingredients for shapeless recipe the max is 9"); - } - + var recipe = ShapelessRecipeSpec.fromJson(json); var overlay = new ResourceLocation(GsonHelper.getAsString(json, "overlay")); - // We could derive this from the ingredients, but we want to avoid evaluating the ingredients too early, so - // it's easier to do this. - var result = make(ShapedRecipe.itemStackFromJson(GsonHelper.getAsJsonObject(json, "result")), overlay); - - return new TurtleOverlayRecipe(id, group, category, result, ingredients, overlay); - } - - private NonNullList readIngredients(JsonArray arrays) { - NonNullList items = NonNullList.create(); - for (var i = 0; i < arrays.size(); ++i) { - var ingredient = Ingredient.fromJson(arrays.get(i)); - if (!ingredient.isEmpty()) items.add(ingredient); - } - - return items; + return new TurtleOverlayRecipe(id, recipe, overlay); } @Override public TurtleOverlayRecipe fromNetwork(ResourceLocation id, FriendlyByteBuf buffer) { - var group = buffer.readUtf(); - var category = buffer.readEnum(CraftingBookCategory.class); - var count = buffer.readVarInt(); - var items = NonNullList.withSize(count, Ingredient.EMPTY); - - for (var j = 0; j < items.size(); j++) items.set(j, Ingredient.fromNetwork(buffer)); - var result = buffer.readItem(); + var recipe = ShapelessRecipeSpec.fromNetwork(buffer); var overlay = buffer.readResourceLocation(); - - return new TurtleOverlayRecipe(id, group, category, result, items, overlay); + return new TurtleOverlayRecipe(id, recipe, overlay); } @Override public void toNetwork(FriendlyByteBuf buffer, TurtleOverlayRecipe recipe) { - buffer.writeUtf(recipe.getGroup()); - buffer.writeEnum(recipe.category()); - buffer.writeVarInt(recipe.getIngredients().size()); - - for (var ingredient : recipe.getIngredients()) ingredient.toNetwork(buffer); - buffer.writeItem(recipe.result); + recipe.toSpec().toNetwork(buffer); buffer.writeResourceLocation(recipe.overlay); } } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/turtle/recipes/TurtleRecipe.java b/projects/common/src/main/java/dan200/computercraft/shared/turtle/recipes/TurtleRecipe.java index 2b786a5db..d73d29aca 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/turtle/recipes/TurtleRecipe.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/turtle/recipes/TurtleRecipe.java @@ -4,26 +4,33 @@ package dan200.computercraft.shared.turtle.recipes; +import com.mojang.serialization.DataResult; import dan200.computercraft.shared.ModRegistry; -import dan200.computercraft.shared.computer.core.ComputerFamily; import dan200.computercraft.shared.computer.items.IComputerItem; -import dan200.computercraft.shared.computer.recipe.ComputerFamilyRecipe; +import dan200.computercraft.shared.computer.recipe.ComputerConvertRecipe; +import dan200.computercraft.shared.recipe.ShapedRecipeSpec; import dan200.computercraft.shared.turtle.items.TurtleItem; -import net.minecraft.core.NonNullList; import net.minecraft.resources.ResourceLocation; 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; -public final class TurtleRecipe extends ComputerFamilyRecipe { - public TurtleRecipe(ResourceLocation identifier, String group, CraftingBookCategory category, int width, int height, NonNullList ingredients, ItemStack result, ComputerFamily family) { - super(identifier, group, category, width, height, ingredients, result, family); +/** + * The recipe which crafts a turtle from an existing computer item. + */ +public final class TurtleRecipe extends ComputerConvertRecipe { + private final TurtleItem turtle; + + private TurtleRecipe(ResourceLocation id, ShapedRecipeSpec recipe, TurtleItem turtle) { + super(id, recipe); + this.turtle = turtle; } - @Override - public RecipeSerializer getSerializer() { - return ModRegistry.RecipeSerializers.TURTLE.get(); + public static DataResult of(ResourceLocation id, ShapedRecipeSpec recipe) { + if (!(recipe.result().getItem() instanceof TurtleItem turtle)) { + return DataResult.error(() -> recipe.result().getItem() + " is not a turtle item"); + } + + return DataResult.success(new TurtleRecipe(id, recipe, turtle)); } @Override @@ -31,13 +38,11 @@ public final class TurtleRecipe extends ComputerFamilyRecipe { var computerID = item.getComputerID(stack); var label = item.getLabel(stack); - return TurtleItem.create(computerID, label, -1, getFamily(), null, null, 0, null); + return turtle.create(computerID, label, -1, null, null, 0, null); } - public static class Serializer extends ComputerFamilyRecipe.Serializer { - @Override - protected TurtleRecipe create(ResourceLocation identifier, String group, CraftingBookCategory category, int width, int height, NonNullList ingredients, ItemStack result, ComputerFamily family) { - return new TurtleRecipe(identifier, group, category, width, height, ingredients, result, family); - } + @Override + public RecipeSerializer getSerializer() { + return ModRegistry.RecipeSerializers.TURTLE.get(); } } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/util/ImpostorRecipe.java b/projects/common/src/main/java/dan200/computercraft/shared/util/ImpostorRecipe.java deleted file mode 100644 index 8354be236..000000000 --- a/projects/common/src/main/java/dan200/computercraft/shared/util/ImpostorRecipe.java +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. -// -// SPDX-License-Identifier: LicenseRef-CCPL - -package dan200.computercraft.shared.util; - -import com.google.gson.JsonObject; -import dan200.computercraft.shared.ModRegistry; -import net.minecraft.core.NonNullList; -import net.minecraft.core.RegistryAccess; -import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.util.GsonHelper; -import net.minecraft.world.inventory.CraftingContainer; -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.item.crafting.ShapedRecipe; -import net.minecraft.world.level.Level; - -public final class ImpostorRecipe extends ShapedRecipe { - private final String group; - private final ItemStack result; - - private ImpostorRecipe(ResourceLocation id, String group, CraftingBookCategory category, int width, int height, NonNullList ingredients, ItemStack result) { - super(id, group, category, width, height, ingredients, result); - this.group = group; - this.result = result; - } - - @Override - public String getGroup() { - return group; - } - - ItemStack getResultItem() { - return result; - } - - @Override - public boolean matches(CraftingContainer inv, Level world) { - return false; - } - - @Override - public ItemStack assemble(CraftingContainer inventory, RegistryAccess registryAccess) { - return ItemStack.EMPTY; - } - - @Override - public RecipeSerializer getSerializer() { - return ModRegistry.RecipeSerializers.IMPOSTOR_SHAPED.get(); - } - - public static class Serializer implements RecipeSerializer { - @Override - public ImpostorRecipe fromJson(ResourceLocation identifier, JsonObject json) { - var group = GsonHelper.getAsString(json, "group", ""); - var category = CraftingBookCategory.CODEC.byName(GsonHelper.getAsString(json, "category", null), CraftingBookCategory.MISC); - var recipe = RecipeSerializer.SHAPED_RECIPE.fromJson(identifier, json); - var result = ShapedRecipe.itemStackFromJson(GsonHelper.getAsJsonObject(json, "result")); - return new ImpostorRecipe(identifier, group, category, recipe.getWidth(), recipe.getHeight(), recipe.getIngredients(), result); - } - - @Override - public ImpostorRecipe fromNetwork(ResourceLocation identifier, FriendlyByteBuf buf) { - var width = buf.readVarInt(); - var height = buf.readVarInt(); - var group = buf.readUtf(Short.MAX_VALUE); - var category = buf.readEnum(CraftingBookCategory.class); - var items = NonNullList.withSize(width * height, Ingredient.EMPTY); - for (var k = 0; k < items.size(); k++) items.set(k, Ingredient.fromNetwork(buf)); - var result = buf.readItem(); - return new ImpostorRecipe(identifier, group, category, width, height, items, result); - } - - @Override - public void toNetwork(FriendlyByteBuf buf, ImpostorRecipe recipe) { - buf.writeVarInt(recipe.getWidth()); - buf.writeVarInt(recipe.getHeight()); - buf.writeUtf(recipe.getGroup()); - buf.writeEnum(recipe.category()); - for (var ingredient : recipe.getIngredients()) ingredient.toNetwork(buf); - buf.writeItem(recipe.getResultItem()); - } - } -} diff --git a/projects/common/src/main/java/dan200/computercraft/shared/util/ImpostorShapelessRecipe.java b/projects/common/src/main/java/dan200/computercraft/shared/util/ImpostorShapelessRecipe.java deleted file mode 100644 index 3e3add914..000000000 --- a/projects/common/src/main/java/dan200/computercraft/shared/util/ImpostorShapelessRecipe.java +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. -// -// SPDX-License-Identifier: LicenseRef-CCPL - -package dan200.computercraft.shared.util; - -import com.google.gson.JsonArray; -import com.google.gson.JsonObject; -import com.google.gson.JsonParseException; -import dan200.computercraft.shared.ModRegistry; -import net.minecraft.core.NonNullList; -import net.minecraft.core.RegistryAccess; -import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.util.GsonHelper; -import net.minecraft.world.inventory.CraftingContainer; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.crafting.*; -import net.minecraft.world.level.Level; - -public final class ImpostorShapelessRecipe extends ShapelessRecipe { - private final String group; - private final ItemStack result; - - private ImpostorShapelessRecipe(ResourceLocation id, String group, CraftingBookCategory category, ItemStack result, NonNullList ingredients) { - super(id, group, category, result, ingredients); - this.group = group; - this.result = result; - } - - @Override - public String getGroup() { - return group; - } - - ItemStack getResultItem() { - return result; - } - - @Override - public boolean matches(CraftingContainer inv, Level world) { - return false; - } - - @Override - public ItemStack assemble(CraftingContainer inventory, RegistryAccess access) { - return ItemStack.EMPTY; - } - - @Override - public RecipeSerializer getSerializer() { - return ModRegistry.RecipeSerializers.IMPOSTOR_SHAPELESS.get(); - } - - public static final class Serializer implements RecipeSerializer { - @Override - public ImpostorShapelessRecipe fromJson(ResourceLocation id, JsonObject json) { - var group = GsonHelper.getAsString(json, "group", ""); - var category = CraftingBookCategory.CODEC.byName(GsonHelper.getAsString(json, "category", null), CraftingBookCategory.MISC); - var ingredients = readIngredients(GsonHelper.getAsJsonArray(json, "ingredients")); - - if (ingredients.isEmpty()) throw new JsonParseException("No ingredients for shapeless recipe"); - if (ingredients.size() > 9) { - throw new JsonParseException("Too many ingredients for shapeless recipe the max is 9"); - } - - var result = ShapedRecipe.itemStackFromJson(GsonHelper.getAsJsonObject(json, "result")); - return new ImpostorShapelessRecipe(id, group, category, result, ingredients); - } - - private NonNullList readIngredients(JsonArray arrays) { - NonNullList items = NonNullList.create(); - for (var i = 0; i < arrays.size(); ++i) { - var ingredient = Ingredient.fromJson(arrays.get(i)); - if (!ingredient.isEmpty()) items.add(ingredient); - } - - return items; - } - - @Override - public ImpostorShapelessRecipe fromNetwork(ResourceLocation id, FriendlyByteBuf buffer) { - var group = buffer.readUtf(); - var category = buffer.readEnum(CraftingBookCategory.class); - var count = buffer.readVarInt(); - var items = NonNullList.withSize(count, Ingredient.EMPTY); - - for (var j = 0; j < items.size(); j++) items.set(j, Ingredient.fromNetwork(buffer)); - var result = buffer.readItem(); - - return new ImpostorShapelessRecipe(id, group, category, result, items); - } - - @Override - public void toNetwork(FriendlyByteBuf buffer, ImpostorShapelessRecipe recipe) { - buffer.writeUtf(recipe.getGroup()); - buffer.writeEnum(recipe.category()); - buffer.writeVarInt(recipe.getIngredients().size()); - - for (var ingredient : recipe.getIngredients()) ingredient.toNetwork(buffer); - buffer.writeItem(recipe.getResultItem()); - } - } -} diff --git a/projects/common/src/main/java/dan200/computercraft/shared/util/RecipeUtil.java b/projects/common/src/main/java/dan200/computercraft/shared/util/RecipeUtil.java deleted file mode 100644 index 9919ec425..000000000 --- a/projects/common/src/main/java/dan200/computercraft/shared/util/RecipeUtil.java +++ /dev/null @@ -1,94 +0,0 @@ -// SPDX-FileCopyrightText: 2017 The CC: Tweaked Developers -// -// SPDX-License-Identifier: MPL-2.0 - -package dan200.computercraft.shared.util; - -import com.google.common.collect.Maps; -import com.google.common.collect.Sets; -import com.google.gson.JsonObject; -import com.google.gson.JsonSyntaxException; -import dan200.computercraft.shared.computer.core.ComputerFamily; -import net.minecraft.core.NonNullList; -import net.minecraft.util.GsonHelper; -import net.minecraft.world.item.crafting.Ingredient; - -import java.util.Map; -import java.util.Set; - -// TODO: Replace some things with Forge?? - -public final class RecipeUtil { - private RecipeUtil() { - } - - public record ShapedTemplate(int width, int height, NonNullList ingredients) { - } - - public static ShapedTemplate getTemplate(JsonObject json) { - Map ingMap = Maps.newHashMap(); - for (var entry : GsonHelper.getAsJsonObject(json, "key").entrySet()) { - if (entry.getKey().length() != 1) { - throw new JsonSyntaxException("Invalid key entry: '" + entry.getKey() + "' is an invalid symbol (must be 1 character only)."); - } - if (" ".equals(entry.getKey())) { - throw new JsonSyntaxException("Invalid key entry: ' ' is a reserved symbol."); - } - - ingMap.put(entry.getKey().charAt(0), Ingredient.fromJson(entry.getValue())); - } - - ingMap.put(' ', Ingredient.EMPTY); - - var patternJ = GsonHelper.getAsJsonArray(json, "pattern"); - - if (patternJ.size() == 0) { - throw new JsonSyntaxException("Invalid pattern: empty pattern not allowed"); - } - - var pattern = new String[patternJ.size()]; - for (var x = 0; x < pattern.length; x++) { - var line = GsonHelper.convertToString(patternJ.get(x), "pattern[" + x + "]"); - if (x > 0 && pattern[0].length() != line.length()) { - throw new JsonSyntaxException("Invalid pattern: each row must be the same width"); - } - pattern[x] = line; - } - - var width = pattern[0].length(); - var height = pattern.length; - var ingredients = NonNullList.withSize(width * height, Ingredient.EMPTY); - - Set missingKeys = Sets.newHashSet(ingMap.keySet()); - missingKeys.remove(' '); - - var ingredientIdx = 0; - for (var line : pattern) { - for (var i = 0; i < line.length(); i++) { - var chr = line.charAt(i); - - var ing = ingMap.get(chr); - if (ing == null) { - throw new JsonSyntaxException("Pattern references symbol '" + chr + "' but it's not defined in the key"); - } - ingredients.set(ingredientIdx++, ing); - missingKeys.remove(chr); - } - } - - if (!missingKeys.isEmpty()) { - throw new JsonSyntaxException("Key defines symbols that aren't used in pattern: " + missingKeys); - } - - return new ShapedTemplate(width, height, ingredients); - } - - public static ComputerFamily getFamily(JsonObject json, String name) { - var familyName = GsonHelper.getAsString(json, name); - for (var family : ComputerFamily.values()) { - if (family.name().equalsIgnoreCase(familyName)) return family; - } - - throw new JsonSyntaxException("Unknown computer family '" + familyName + "' for field " + name); - } -} diff --git a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Recipe_Test.kt b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Recipe_Test.kt index 4113263ca..fb186d1cb 100644 --- a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Recipe_Test.kt +++ b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Recipe_Test.kt @@ -4,6 +4,7 @@ package dan200.computercraft.gametest +import com.mojang.authlib.GameProfile import dan200.computercraft.gametest.api.Structures import dan200.computercraft.gametest.api.sequence import dan200.computercraft.shared.ModRegistry @@ -11,6 +12,7 @@ import net.minecraft.gametest.framework.GameTest import net.minecraft.gametest.framework.GameTestAssertException import net.minecraft.gametest.framework.GameTestHelper import net.minecraft.nbt.CompoundTag +import net.minecraft.nbt.NbtUtils import net.minecraft.world.entity.player.Player import net.minecraft.world.inventory.AbstractContainerMenu import net.minecraft.world.inventory.MenuType @@ -41,11 +43,10 @@ class Recipe_Test { val result = recipe.get().assemble(container, context.level.registryAccess()) - val owner = CompoundTag() - owner.putString("Name", "dan200") - owner.putString("Id", "f3c8d69b-0776-4512-8434-d1b2165909eb") + val profile = GameProfile(UUID.fromString("f3c8d69b-0776-4512-8434-d1b2165909eb"), "dan200") + val tag = CompoundTag() - tag.put("SkullOwner", owner) + tag.put("SkullOwner", NbtUtils.writeGameProfile(CompoundTag(), profile)) assertEquals(tag, result.tag, "Expected NBT tags to be the same") } diff --git a/projects/fabric/src/generated/resources/data/computercraft/recipes/computer_advanced_upgrade.json b/projects/fabric/src/generated/resources/data/computercraft/recipes/computer_advanced_upgrade.json index e4c194f71..ccbc87c2c 100644 --- a/projects/fabric/src/generated/resources/data/computercraft/recipes/computer_advanced_upgrade.json +++ b/projects/fabric/src/generated/resources/data/computercraft/recipes/computer_advanced_upgrade.json @@ -1,7 +1,7 @@ { "type": "computercraft:computer_upgrade", "category": "redstone", - "family": "ADVANCED", + "family": "advanced", "key": {"#": {"tag": "c:gold_ingots"}, "C": {"item": "computercraft:computer_normal"}}, "pattern": ["###", "#C#", "# #"], "result": {"item": "computercraft:computer_advanced"}, diff --git a/projects/fabric/src/generated/resources/data/computercraft/recipes/pocket_computer_advanced_upgrade.json b/projects/fabric/src/generated/resources/data/computercraft/recipes/pocket_computer_advanced_upgrade.json index e269f37b6..ac2f5a9b7 100644 --- a/projects/fabric/src/generated/resources/data/computercraft/recipes/pocket_computer_advanced_upgrade.json +++ b/projects/fabric/src/generated/resources/data/computercraft/recipes/pocket_computer_advanced_upgrade.json @@ -1,7 +1,7 @@ { "type": "computercraft:computer_upgrade", "category": "redstone", - "family": "ADVANCED", + "family": "advanced", "key": {"#": {"tag": "c:gold_ingots"}, "C": {"item": "computercraft:pocket_computer_normal"}}, "pattern": ["###", "#C#", "# #"], "result": {"item": "computercraft:pocket_computer_advanced"}, diff --git a/projects/fabric/src/generated/resources/data/computercraft/recipes/skull_cloudy.json b/projects/fabric/src/generated/resources/data/computercraft/recipes/skull_cloudy.json index e5d84e4f2..d69e4e195 100644 --- a/projects/fabric/src/generated/resources/data/computercraft/recipes/skull_cloudy.json +++ b/projects/fabric/src/generated/resources/data/computercraft/recipes/skull_cloudy.json @@ -1,9 +1,9 @@ { - "type": "minecraft:crafting_shapeless", + "type": "computercraft:shapeless", "category": "misc", "ingredients": [{"tag": "c:skulls"}, {"item": "computercraft:monitor_normal"}], "result": { "item": "minecraft:player_head", - "nbt": "{SkullOwner:{Id:\"6d074736-b1e9-4378-a99b-bd8777821c9c\",Name:\"Cloudhunter\"}}" + "nbt": "{SkullOwner:{Id:[I;1829193526,-1310112904,-1449411193,2005015708],Name:\"Cloudhunter\"}}" } } diff --git a/projects/fabric/src/generated/resources/data/computercraft/recipes/skull_dan200.json b/projects/fabric/src/generated/resources/data/computercraft/recipes/skull_dan200.json index bd8a1beec..8c21bd49d 100644 --- a/projects/fabric/src/generated/resources/data/computercraft/recipes/skull_dan200.json +++ b/projects/fabric/src/generated/resources/data/computercraft/recipes/skull_dan200.json @@ -1,9 +1,9 @@ { - "type": "minecraft:crafting_shapeless", + "type": "computercraft:shapeless", "category": "misc", "ingredients": [{"tag": "c:skulls"}, {"item": "computercraft:computer_advanced"}], "result": { "item": "minecraft:player_head", - "nbt": "{SkullOwner:{Id:\"f3c8d69b-0776-4512-8434-d1b2165909eb\",Name:\"dan200\"}}" + "nbt": "{SkullOwner:{Id:[I;-204941669,125191442,-2076913230,374933995],Name:\"dan200\"}}" } } diff --git a/projects/fabric/src/generated/resources/data/computercraft/recipes/turtle_advanced.json b/projects/fabric/src/generated/resources/data/computercraft/recipes/turtle_advanced.json index cea8529fa..367b93bc2 100644 --- a/projects/fabric/src/generated/resources/data/computercraft/recipes/turtle_advanced.json +++ b/projects/fabric/src/generated/resources/data/computercraft/recipes/turtle_advanced.json @@ -1,7 +1,7 @@ { "type": "computercraft:turtle", "category": "redstone", - "family": "ADVANCED", + "family": "advanced", "key": { "#": {"tag": "c:gold_ingots"}, "C": {"item": "computercraft:computer_advanced"}, diff --git a/projects/fabric/src/generated/resources/data/computercraft/recipes/turtle_advanced_upgrade.json b/projects/fabric/src/generated/resources/data/computercraft/recipes/turtle_advanced_upgrade.json index e99caef38..155297a1f 100644 --- a/projects/fabric/src/generated/resources/data/computercraft/recipes/turtle_advanced_upgrade.json +++ b/projects/fabric/src/generated/resources/data/computercraft/recipes/turtle_advanced_upgrade.json @@ -1,7 +1,7 @@ { "type": "computercraft:computer_upgrade", "category": "redstone", - "family": "ADVANCED", + "family": "advanced", "key": { "#": {"tag": "c:gold_ingots"}, "B": {"item": "minecraft:gold_block"}, diff --git a/projects/fabric/src/generated/resources/data/computercraft/recipes/turtle_normal.json b/projects/fabric/src/generated/resources/data/computercraft/recipes/turtle_normal.json index e9e7d4719..eb35293ca 100644 --- a/projects/fabric/src/generated/resources/data/computercraft/recipes/turtle_normal.json +++ b/projects/fabric/src/generated/resources/data/computercraft/recipes/turtle_normal.json @@ -1,7 +1,7 @@ { "type": "computercraft:turtle", "category": "redstone", - "family": "NORMAL", + "family": "normal", "key": { "#": {"tag": "c:iron_ingots"}, "C": {"item": "computercraft:computer_normal"}, diff --git a/projects/fabric/src/main/java/dan200/computercraft/mixin/ShapedRecipeMixin.java b/projects/fabric/src/main/java/dan200/computercraft/mixin/ShapedRecipeMixin.java deleted file mode 100644 index e1ef5d24d..000000000 --- a/projects/fabric/src/main/java/dan200/computercraft/mixin/ShapedRecipeMixin.java +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers -// -// SPDX-License-Identifier: MPL-2.0 - -package dan200.computercraft.mixin; - -import com.google.gson.JsonObject; -import dan200.computercraft.shared.FabricCommonHooks; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.crafting.ShapedRecipe; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; - -@Mixin(ShapedRecipe.class) -class ShapedRecipeMixin { - @Inject(method = "itemStackFromJson", at = @At("RETURN")) - @SuppressWarnings("UnusedMethod") - private static void itemStackFromJson(JsonObject json, CallbackInfoReturnable cir) { - // This is a fairly invasive mixin in the sense that every mod goes through this code path. We might want to - // remove this and use custom recipes types in the future. - FabricCommonHooks.addRecipeResultTag(cir.getReturnValue(), json); - } -} diff --git a/projects/fabric/src/main/java/dan200/computercraft/shared/FabricCommonHooks.java b/projects/fabric/src/main/java/dan200/computercraft/shared/FabricCommonHooks.java index cb1e17b74..73a1ab689 100644 --- a/projects/fabric/src/main/java/dan200/computercraft/shared/FabricCommonHooks.java +++ b/projects/fabric/src/main/java/dan200/computercraft/shared/FabricCommonHooks.java @@ -4,18 +4,12 @@ package dan200.computercraft.shared; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonObject; -import com.mojang.brigadier.exceptions.CommandSyntaxException; import dan200.computercraft.shared.media.items.DiskItem; import dan200.computercraft.shared.media.items.TreasureDiskItem; import dan200.computercraft.shared.peripheral.modem.wired.CableBlock; import net.minecraft.core.BlockPos; -import net.minecraft.nbt.TagParser; import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.ServerPlayerGameMode; -import net.minecraft.util.GsonHelper; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.entity.player.Player; @@ -24,15 +18,10 @@ import net.minecraft.world.level.Level; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.BlockHitResult; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import javax.annotation.Nullable; public class FabricCommonHooks { - private static final Gson GSON = new GsonBuilder().create(); - private static final Logger LOGGER = LoggerFactory.getLogger(FabricCommonHooks.class); - public static boolean onBlockDestroy(Level level, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity) { return !(state.getBlock() instanceof CableBlock cable) || !cable.onCustomDestroyBlock(state, level, pos, player); } @@ -64,23 +53,4 @@ public class FabricCommonHooks { private static boolean doesSneakBypassUse(ItemStack stack) { return stack.isEmpty() || stack.getItem() instanceof DiskItem || stack.getItem() instanceof TreasureDiskItem; } - - /** - * Add the {@code "nbt"} field to the resulting item stack. - * - * @param stack The stack to add the tag to. - * @param json The result JSON object to parse. - */ - public static void addRecipeResultTag(ItemStack stack, JsonObject json) { - var nbt = json.get("nbt"); - if (nbt == null || stack.hasTag()) return; - - try { - stack.setTag(nbt.isJsonObject() - ? TagParser.parseTag(GSON.toJson(nbt)) - : TagParser.parseTag(GsonHelper.convertToString(nbt, "nbt"))); - } catch (CommandSyntaxException e) { - LOGGER.error("Invalid NBT entry {}, skipping.", nbt); - } - } } diff --git a/projects/fabric/src/main/resources/computercraft.fabric.mixins.json b/projects/fabric/src/main/resources/computercraft.fabric.mixins.json index 0b6d01a0c..d52054c29 100644 --- a/projects/fabric/src/main/resources/computercraft.fabric.mixins.json +++ b/projects/fabric/src/main/resources/computercraft.fabric.mixins.json @@ -14,7 +14,6 @@ "ItemEntityMixin", "ItemMixin", "ServerLevelMixin", - "ShapedRecipeMixin", "TagEntryAccessor", "TagsProviderMixin" ] diff --git a/projects/forge/src/generated/resources/data/computercraft/recipes/computer_advanced_upgrade.json b/projects/forge/src/generated/resources/data/computercraft/recipes/computer_advanced_upgrade.json index 94d3b9a5f..05bf8dc26 100644 --- a/projects/forge/src/generated/resources/data/computercraft/recipes/computer_advanced_upgrade.json +++ b/projects/forge/src/generated/resources/data/computercraft/recipes/computer_advanced_upgrade.json @@ -1,7 +1,7 @@ { "type": "computercraft:computer_upgrade", "category": "redstone", - "family": "ADVANCED", + "family": "advanced", "key": {"#": {"tag": "forge:ingots/gold"}, "C": {"item": "computercraft:computer_normal"}}, "pattern": ["###", "#C#", "# #"], "result": {"item": "computercraft:computer_advanced"}, diff --git a/projects/forge/src/generated/resources/data/computercraft/recipes/pocket_computer_advanced_upgrade.json b/projects/forge/src/generated/resources/data/computercraft/recipes/pocket_computer_advanced_upgrade.json index 27a0c7d66..a139e02dd 100644 --- a/projects/forge/src/generated/resources/data/computercraft/recipes/pocket_computer_advanced_upgrade.json +++ b/projects/forge/src/generated/resources/data/computercraft/recipes/pocket_computer_advanced_upgrade.json @@ -1,7 +1,7 @@ { "type": "computercraft:computer_upgrade", "category": "redstone", - "family": "ADVANCED", + "family": "advanced", "key": {"#": {"tag": "forge:ingots/gold"}, "C": {"item": "computercraft:pocket_computer_normal"}}, "pattern": ["###", "#C#", "# #"], "result": {"item": "computercraft:pocket_computer_advanced"}, diff --git a/projects/forge/src/generated/resources/data/computercraft/recipes/skull_cloudy.json b/projects/forge/src/generated/resources/data/computercraft/recipes/skull_cloudy.json index 009e639e1..d12a80157 100644 --- a/projects/forge/src/generated/resources/data/computercraft/recipes/skull_cloudy.json +++ b/projects/forge/src/generated/resources/data/computercraft/recipes/skull_cloudy.json @@ -1,9 +1,9 @@ { - "type": "minecraft:crafting_shapeless", + "type": "computercraft:shapeless", "category": "misc", "ingredients": [{"tag": "forge:heads"}, {"item": "computercraft:monitor_normal"}], "result": { "item": "minecraft:player_head", - "nbt": "{SkullOwner:{Id:\"6d074736-b1e9-4378-a99b-bd8777821c9c\",Name:\"Cloudhunter\"}}" + "nbt": "{SkullOwner:{Id:[I;1829193526,-1310112904,-1449411193,2005015708],Name:\"Cloudhunter\"}}" } } diff --git a/projects/forge/src/generated/resources/data/computercraft/recipes/skull_dan200.json b/projects/forge/src/generated/resources/data/computercraft/recipes/skull_dan200.json index 2657717f6..b3fb9eaef 100644 --- a/projects/forge/src/generated/resources/data/computercraft/recipes/skull_dan200.json +++ b/projects/forge/src/generated/resources/data/computercraft/recipes/skull_dan200.json @@ -1,9 +1,9 @@ { - "type": "minecraft:crafting_shapeless", + "type": "computercraft:shapeless", "category": "misc", "ingredients": [{"tag": "forge:heads"}, {"item": "computercraft:computer_advanced"}], "result": { "item": "minecraft:player_head", - "nbt": "{SkullOwner:{Id:\"f3c8d69b-0776-4512-8434-d1b2165909eb\",Name:\"dan200\"}}" + "nbt": "{SkullOwner:{Id:[I;-204941669,125191442,-2076913230,374933995],Name:\"dan200\"}}" } } diff --git a/projects/forge/src/generated/resources/data/computercraft/recipes/turtle_advanced.json b/projects/forge/src/generated/resources/data/computercraft/recipes/turtle_advanced.json index 28978455c..c0485e66a 100644 --- a/projects/forge/src/generated/resources/data/computercraft/recipes/turtle_advanced.json +++ b/projects/forge/src/generated/resources/data/computercraft/recipes/turtle_advanced.json @@ -1,7 +1,7 @@ { "type": "computercraft:turtle", "category": "redstone", - "family": "ADVANCED", + "family": "advanced", "key": { "#": {"tag": "forge:ingots/gold"}, "C": {"item": "computercraft:computer_advanced"}, diff --git a/projects/forge/src/generated/resources/data/computercraft/recipes/turtle_advanced_upgrade.json b/projects/forge/src/generated/resources/data/computercraft/recipes/turtle_advanced_upgrade.json index f1b3574e6..a1c522fb8 100644 --- a/projects/forge/src/generated/resources/data/computercraft/recipes/turtle_advanced_upgrade.json +++ b/projects/forge/src/generated/resources/data/computercraft/recipes/turtle_advanced_upgrade.json @@ -1,7 +1,7 @@ { "type": "computercraft:computer_upgrade", "category": "redstone", - "family": "ADVANCED", + "family": "advanced", "key": { "#": {"tag": "forge:ingots/gold"}, "B": {"tag": "forge:storage_blocks/gold"}, diff --git a/projects/forge/src/generated/resources/data/computercraft/recipes/turtle_normal.json b/projects/forge/src/generated/resources/data/computercraft/recipes/turtle_normal.json index c2da094a1..49c36fb43 100644 --- a/projects/forge/src/generated/resources/data/computercraft/recipes/turtle_normal.json +++ b/projects/forge/src/generated/resources/data/computercraft/recipes/turtle_normal.json @@ -1,7 +1,7 @@ { "type": "computercraft:turtle", "category": "redstone", - "family": "NORMAL", + "family": "normal", "key": { "#": {"tag": "forge:ingots/iron"}, "C": {"item": "computercraft:computer_normal"},