From 7a972fc22fc8284aae740d4009167b521e92e5c0 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 18 Dec 2023 15:23:13 +0100 Subject: [PATCH] [Change] Recipes use Codecs for Serialization now --- .../betterx/bclib/recipes/AlloyingRecipe.java | 68 ++++--- .../betterx/bclib/recipes/AnvilRecipe.java | 56 +++--- .../java/org/betterx/bclib/util/ItemUtil.java | 189 +++++++++--------- src/main/resources/bclib.accesswidener | 9 +- 4 files changed, 162 insertions(+), 160 deletions(-) diff --git a/src/main/java/org/betterx/bclib/recipes/AlloyingRecipe.java b/src/main/java/org/betterx/bclib/recipes/AlloyingRecipe.java index 621970b7..c71a3c77 100644 --- a/src/main/java/org/betterx/bclib/recipes/AlloyingRecipe.java +++ b/src/main/java/org/betterx/bclib/recipes/AlloyingRecipe.java @@ -5,13 +5,14 @@ import org.betterx.bclib.interfaces.AlloyingRecipeWorkstation; import org.betterx.bclib.interfaces.UnknownReceipBookCategory; import org.betterx.bclib.util.ItemUtil; +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; import net.minecraft.core.NonNullList; import net.minecraft.core.RegistryAccess; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.resources.ResourceLocation; import net.minecraft.tags.TagKey; -import net.minecraft.util.GsonHelper; import net.minecraft.world.Container; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; @@ -25,9 +26,10 @@ import net.minecraft.world.level.Level; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; -import com.google.gson.JsonArray; import com.google.gson.JsonObject; +import java.util.List; + public class AlloyingRecipe implements Recipe, UnknownReceipBookCategory { public final static String GROUP = "alloying"; public final static RecipeType TYPE = BCLRecipeManager.registerType(BCLib.MOD_ID, GROUP); @@ -38,7 +40,6 @@ public class AlloyingRecipe implements Recipe, UnknownReceipBookCateg ); protected final RecipeType type; - protected final ResourceLocation id; protected final Ingredient primaryInput; protected final Ingredient secondaryInput; protected final ItemStack output; @@ -47,7 +48,23 @@ public class AlloyingRecipe implements Recipe, UnknownReceipBookCateg protected final int smeltTime; public AlloyingRecipe( - ResourceLocation id, + List inputs, + String group, + ItemStack output, + float experience, + int smeltTime + ) { + this( + group, + !inputs.isEmpty() ? inputs.get(0) : null, + inputs.size() > 1 ? inputs.get(1) : null, + output, + experience, + smeltTime + ); + } + + public AlloyingRecipe( String group, Ingredient primaryInput, Ingredient secondaryInput, @@ -56,7 +73,6 @@ public class AlloyingRecipe implements Recipe, UnknownReceipBookCateg int smeltTime ) { this.group = group; - this.id = id; this.primaryInput = primaryInput; this.secondaryInput = secondaryInput; this.output = output; @@ -103,11 +119,6 @@ public class AlloyingRecipe implements Recipe, UnknownReceipBookCateg return this.output; } - @Override - public ResourceLocation getId() { - return this.id; - } - @Override public RecipeSerializer getSerializer() { return SERIALIZER; @@ -209,27 +220,19 @@ public class AlloyingRecipe implements Recipe, UnknownReceipBookCateg } public static class Serializer implements RecipeSerializer { - @Override - public AlloyingRecipe fromJson(ResourceLocation id, JsonObject json) { - JsonArray ingredients = GsonHelper.getAsJsonArray(json, "ingredients"); - Ingredient primaryInput = Ingredient.fromJson(ingredients.get(0)); - Ingredient secondaryInput = Ingredient.fromJson(ingredients.get(1)); - - String group = GsonHelper.getAsString(json, "group", ""); - - JsonObject result = GsonHelper.getAsJsonObject(json, "result"); - ItemStack output = ItemUtil.fromJsonRecipeWithNBT(result); - if (output == null) { - throw new IllegalStateException("Output item does not exists!"); - } - float experience = GsonHelper.getAsFloat(json, "experience", 0.0F); - int smeltTime = GsonHelper.getAsInt(json, "smelttime", 350); - - return new AlloyingRecipe(id, group, primaryInput, secondaryInput, output, experience, smeltTime); - } + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + Codec.list(Ingredient.CODEC_NONEMPTY) + .fieldOf("ingredients") + .forGetter(recipe -> List.of(recipe.primaryInput, recipe.secondaryInput)), + Codec.STRING.optionalFieldOf("group", "") + .forGetter(recipe -> recipe.group), + ItemUtil.CODEC_ITEM_STACK_WITH_NBT.fieldOf("result").forGetter(recipe -> recipe.output), + Codec.FLOAT.optionalFieldOf("experience", 0f).forGetter(recipe -> recipe.experience), + Codec.INT.optionalFieldOf("smelttime", 350).forGetter(recipe -> recipe.smeltTime) + ).apply(instance, AlloyingRecipe::new)); @Override - public AlloyingRecipe fromNetwork(ResourceLocation id, FriendlyByteBuf packetBuffer) { + public AlloyingRecipe fromNetwork(FriendlyByteBuf packetBuffer) { String group = packetBuffer.readUtf(32767); Ingredient primary = Ingredient.fromNetwork(packetBuffer); Ingredient secondary = Ingredient.fromNetwork(packetBuffer); @@ -237,7 +240,12 @@ public class AlloyingRecipe implements Recipe, UnknownReceipBookCateg float experience = packetBuffer.readFloat(); int smeltTime = packetBuffer.readVarInt(); - return new AlloyingRecipe(id, group, primary, secondary, output, experience, smeltTime); + return new AlloyingRecipe(group, primary, secondary, output, experience, smeltTime); + } + + @Override + public Codec codec() { + return null; } @Override diff --git a/src/main/java/org/betterx/bclib/recipes/AnvilRecipe.java b/src/main/java/org/betterx/bclib/recipes/AnvilRecipe.java index f35ae7be..e555ef94 100644 --- a/src/main/java/org/betterx/bclib/recipes/AnvilRecipe.java +++ b/src/main/java/org/betterx/bclib/recipes/AnvilRecipe.java @@ -6,6 +6,8 @@ import org.betterx.bclib.util.ItemUtil; import org.betterx.worlds.together.tag.v3.CommonItemTags; import org.betterx.worlds.together.world.event.WorldBootstrap; +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; import net.minecraft.client.Minecraft; import net.minecraft.core.Holder; import net.minecraft.core.NonNullList; @@ -16,7 +18,6 @@ import net.minecraft.nbt.CompoundTag; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.resources.ResourceLocation; import net.minecraft.tags.TagKey; -import net.minecraft.util.GsonHelper; import net.minecraft.world.Container; import net.minecraft.world.InteractionHand; import net.minecraft.world.entity.player.Player; @@ -53,7 +54,6 @@ public class AnvilRecipe implements Recipe, UnknownReceipBookCategory //we call this to make sure that TYPE is initialized } - private final ResourceLocation id; private final Ingredient input; private final ItemStack output; private final int damage; @@ -62,7 +62,6 @@ public class AnvilRecipe implements Recipe, UnknownReceipBookCategory private final int inputCount; public AnvilRecipe( - ResourceLocation identifier, Ingredient input, ItemStack output, int inputCount, @@ -70,7 +69,6 @@ public class AnvilRecipe implements Recipe, UnknownReceipBookCategory int anvilLevel, int damage ) { - this.id = identifier; this.input = input; this.output = output; this.toolLevel = toolLevel; @@ -214,11 +212,6 @@ public class AnvilRecipe implements Recipe, UnknownReceipBookCategory return true; } - @Override - public ResourceLocation getId() { - return this.id; - } - @Override public RecipeType getType() { return TYPE; @@ -234,18 +227,25 @@ public class AnvilRecipe implements Recipe, UnknownReceipBookCategory if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; AnvilRecipe that = (AnvilRecipe) o; - return damage == that.damage && toolLevel == that.toolLevel && id.equals(that.id) && input.equals(that.input) && output.equals( - that.output); + return damage == that.damage && toolLevel == that.toolLevel && input.equals(that.input) && output.equals(that.output); } @Override public int hashCode() { - return Objects.hash(id, input, output, damage, toolLevel); + return Objects.hash(input, output, damage, toolLevel); } @Override public String toString() { - return "AnvilRecipe [" + id + "]"; + final StringBuffer sb = new StringBuffer("AnvilRecipe{"); + sb.append("input=").append(input); + sb.append(", output=").append(output); + sb.append(", damage=").append(damage); + sb.append(", toolLevel=").append(toolLevel); + sb.append(", anvilLevel=").append(anvilLevel); + sb.append(", inputCount=").append(inputCount); + sb.append('}'); + return sb.toString(); } public static class Builder extends AbstractSingleInputRecipeBuilder { @@ -357,25 +357,23 @@ public class AnvilRecipe implements Recipe, UnknownReceipBookCategory } public static class Serializer implements RecipeSerializer { + public static Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + Ingredient.CODEC_NONEMPTY.fieldOf("input").forGetter(recipe -> recipe.input), + ItemUtil.CODEC_ITEM_STACK_WITH_NBT.fieldOf("result").forGetter(recipe -> recipe.output), + Codec.INT.fieldOf("inputCount").forGetter(recipe -> recipe.inputCount), + Codec.INT.fieldOf("toolLevel").forGetter(recipe -> recipe.toolLevel), + Codec.INT.fieldOf("anvilLevel").forGetter(recipe -> recipe.anvilLevel), + Codec.INT.fieldOf("damage").forGetter(recipe -> recipe.damage) + ).apply(instance, AnvilRecipe::new)); + @Override - public AnvilRecipe fromJson(ResourceLocation id, JsonObject json) { - Ingredient input = Ingredient.fromJson(json.get("input")); - JsonObject result = GsonHelper.getAsJsonObject(json, "result"); - ItemStack output = ItemUtil.fromJsonRecipeWithNBT(result); - if (output == null) { - throw new IllegalStateException("Output item does not exists!"); - } - - int inputCount = GsonHelper.getAsInt(json, "inputCount", 1); - int toolLevel = GsonHelper.getAsInt(json, "toolLevel", 1); - int anvilLevel = GsonHelper.getAsInt(json, "anvilLevel", 1); - int damage = GsonHelper.getAsInt(json, "damage", 1); - - return new AnvilRecipe(id, input, output, inputCount, toolLevel, anvilLevel, damage); + public Codec codec() { + return CODEC; } + @Override - public AnvilRecipe fromNetwork(ResourceLocation id, FriendlyByteBuf packetBuffer) { + public AnvilRecipe fromNetwork(FriendlyByteBuf packetBuffer) { Ingredient input = Ingredient.fromNetwork(packetBuffer); ItemStack output = packetBuffer.readItem(); int inputCount = packetBuffer.readVarInt(); @@ -383,7 +381,7 @@ public class AnvilRecipe implements Recipe, UnknownReceipBookCategory int anvilLevel = packetBuffer.readVarInt(); int damage = packetBuffer.readVarInt(); - return new AnvilRecipe(id, input, output, inputCount, toolLevel, anvilLevel, damage); + return new AnvilRecipe(input, output, inputCount, toolLevel, anvilLevel, damage); } @Override diff --git a/src/main/java/org/betterx/bclib/util/ItemUtil.java b/src/main/java/org/betterx/bclib/util/ItemUtil.java index bc7a9496..763f6f65 100644 --- a/src/main/java/org/betterx/bclib/util/ItemUtil.java +++ b/src/main/java/org/betterx/bclib/util/ItemUtil.java @@ -2,26 +2,28 @@ package org.betterx.bclib.util; import org.betterx.bclib.BCLib; -import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.datafixers.util.Either; +import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.codecs.RecordCodecBuilder; import net.minecraft.core.registries.BuiltInRegistries; -import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.TagParser; import net.minecraft.resources.ResourceLocation; -import net.minecraft.util.GsonHelper; +import net.minecraft.util.ExtraCodecs; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.crafting.Ingredient; -import net.minecraft.world.level.ItemLike; - -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Optional; import org.jetbrains.annotations.Nullable; public class ItemUtil { @Nullable public static ItemStack fromStackString(String stackString) { - if (stackString == null || stackString.equals("")) { + if (stackString == null || stackString.isEmpty()) { return null; } try { @@ -29,112 +31,99 @@ public class ItemUtil { if (parts.length < 2) return null; if (parts.length == 2) { ResourceLocation itemId = new ResourceLocation(stackString); - Item item = BuiltInRegistries.ITEM.getOptional(itemId).orElseThrow(() -> { - return new IllegalStateException("Output item " + itemId + " does not exists!"); - }); + Item item = BuiltInRegistries + .ITEM + .getOptional(itemId) + .orElseThrow(() -> new IllegalStateException("Output item " + itemId + " does not exists!")); return new ItemStack(item); } ResourceLocation itemId = new ResourceLocation(parts[0], parts[1]); - Item item = BuiltInRegistries.ITEM.getOptional(itemId) - .orElseThrow(() -> new IllegalStateException("Output item " + itemId + " does not exists!")); - return new ItemStack(item, Integer.valueOf(parts[2])); + Item item = BuiltInRegistries + .ITEM + .getOptional(itemId) + .orElseThrow(() -> new IllegalStateException("Output item " + itemId + " does not exists!")); + return new ItemStack(item, Integer.parseInt(parts[2])); } catch (Exception ex) { BCLib.LOGGER.error("ItemStack deserialization error!", ex); } return null; } - public static CompoundTag readNBT(JsonObject recipe) { - if (recipe.has("nbt")) { - try { - String nbtData = GsonHelper.getAsString(recipe, "nbt"); - CompoundTag nbt = TagParser.parseTag(nbtData); - return nbt; - } catch (CommandSyntaxException ex) { - BCLib.LOGGER.warning("Error parsing nbt data for output.", ex); + public static Codec CODEC_ITEM_STACK_WITH_NBT = RecordCodecBuilder.create((instance) -> instance.group( + BuiltInRegistries.ITEM.holderByNameCodec() + .fieldOf("item") + .forGetter(ItemStack::getItemHolder), + Codec.INT.optionalFieldOf("count", 1) + .forGetter(ItemStack::getCount), + ExtraCodecs.strictOptionalField(TagParser.AS_CODEC, "nbt") + .forGetter((itemStack) -> Optional.ofNullable(itemStack.getTag())) + ).apply(instance, ItemStack::new)); + + public static Codec CODEC_INGREDIENT_WITH_NBT = ingredientCodec(true); + public static Codec CODEC_INGREDIENT_WITH_NBT_NOT_EMPTY = ingredientCodec(false); + + + private static Codec ingredientCodec(boolean allowEmpty) { + record NbtItemValue(ItemStack item) implements Ingredient.Value { + static final Codec CODEC = RecordCodecBuilder.create((instance) -> instance + .group(CODEC_ITEM_STACK_WITH_NBT.fieldOf("item").forGetter((itemValue) -> itemValue.item)) + .apply(instance, NbtItemValue::new)); + + public boolean equals(Object object) { + if (object instanceof NbtItemValue itemValue) { + return ItemStack.isSameItemSameTags(itemValue.item, this.item) + && itemValue.item.getCount() == this.item.getCount(); + } else if (object instanceof Ingredient.ItemValue itemValue) { + return ItemStack.isSameItemSameTags(itemValue.item(), this.item) + && itemValue.item().getCount() == this.item.getCount(); + } else { + return false; + } + } + + public Collection getItems() { + return Collections.singleton(this.item); + } + + public ItemStack item() { + return this.item; } } - return null; - } - public static void writeNBT(JsonObject root, CompoundTag nbt) { - if (nbt != null) { - final String nbtData = nbt.toString(); - root.addProperty("nbt", nbtData); - } - } - - public static Ingredient fromJsonIngredientWithNBT(JsonObject ingredient) { - Ingredient ing = Ingredient.fromJson(ingredient); - CompoundTag nbt = readNBT(ingredient); - if (nbt != null && !ing.isEmpty()) { - ing.getItems()[0].setTag(nbt); - } - return ing; - } - - public static ItemStack fromJsonRecipeWithNBT(JsonObject recipe) { - ItemStack output = ItemUtil.fromJsonRecipe(recipe); - CompoundTag nbt = ItemUtil.readNBT(recipe); - if (output != null && nbt != null) { - output.setTag(nbt); - } - return output; - } - - @Nullable - public static ItemStack fromJsonRecipe(JsonObject recipe) { - try { - if (!recipe.has("item")) { - throw new IllegalStateException("Invalid JsonObject. Entry 'item' does not exists!"); - } - ResourceLocation itemId = new ResourceLocation(GsonHelper.getAsString(recipe, "item")); - Item item = BuiltInRegistries.ITEM.getOptional(itemId).orElseThrow(() -> { - return new IllegalStateException("Output item " + itemId + " does not exists!"); - }); - int count = GsonHelper.getAsInt(recipe, "count", 1); - return new ItemStack(item, count); - } catch (Exception ex) { - BCLib.LOGGER.error("ItemStack deserialization error!", ex); - } - return null; - } - - public static JsonElement toJsonIngredientWithNBT(Ingredient ing) { - JsonElement el = ing.toJson(); - if (el.isJsonObject() && !ing.isEmpty() && ing.getItems()[0].hasTag()) { - JsonObject obj = el.getAsJsonObject(); - writeNBT(obj, ing.getItems()[0].getTag()); - } - return el; - } + Codec VALUE_CODEC = ExtraCodecs + .xor(NbtItemValue.CODEC, Ingredient.TagValue.CODEC) + .xmap( + (either) -> either.map((itemValue) -> itemValue, (tagValue) -> tagValue), + (value) -> { + if (value instanceof Ingredient.TagValue tagValue) { + return Either.right(tagValue); + } else if (value instanceof NbtItemValue itemValue) { + return Either.left(itemValue); + } else { + throw new UnsupportedOperationException( + "This is neither an nbt-item value nor a tag value."); + } + } + ); - public static JsonObject toJsonRecipeWithNBT(ItemStack stack) { - return toJsonRecipeWithNBT(stack.getItem(), stack.getCount(), stack.getTag()); - } + Codec codec = Codec.list(VALUE_CODEC).comapFlatMap((list) -> + !allowEmpty && list.size() < 1 + ? DataResult.error(() -> "Item array cannot be empty, at least one item must be defined") + : DataResult.success(list.toArray(new Ingredient.Value[0])) + , List::of); - public static JsonObject toJsonRecipeWithNBT(ItemLike item, int count, CompoundTag nbt) { - JsonObject root = toJsonRecipe(item, count); - writeNBT(root, nbt); - return root; - } - - public static JsonObject toJsonRecipe(ItemStack stack) { - return toJsonRecipe(stack.getItem(), stack.getCount()); - } - - public static JsonObject toJsonRecipe(ItemLike item, int count) { - final ResourceLocation id = BuiltInRegistries.ITEM.getKey(item.asItem()); - if (id == null) { - throw new IllegalStateException("Unknown Item " + item); - } - - final JsonObject root = new JsonObject(); - root.addProperty("item", BuiltInRegistries.ITEM.getKey(item.asItem()).toString()); - if (count > 1) { - root.addProperty("count", count); - } - return root; + return ExtraCodecs.either(codec, VALUE_CODEC).flatComapMap( + (either) -> either.map(Ingredient::new, (value) -> new Ingredient(new Ingredient.Value[]{value})), + (ingredient) -> { + if (ingredient.values.length == 1) { + return DataResult.success(Either.right(ingredient.values[0])); + } else { + return ingredient.values.length == 0 && !allowEmpty + ? DataResult.error(() -> "Item array cannot be empty, at least one item must be defined") + : DataResult.success(Either.left(ingredient.values)); + } + } + ); } } diff --git a/src/main/resources/bclib.accesswidener b/src/main/resources/bclib.accesswidener index 509f87b3..e9fa1658 100644 --- a/src/main/resources/bclib.accesswidener +++ b/src/main/resources/bclib.accesswidener @@ -17,6 +17,9 @@ accessible class net/minecraft/client/resources/model/AtlasSet$AtlasEntry extendable class net/minecraft/world/level/block/state/properties/WoodType accessible class net/minecraft/world/level/levelgen/SurfaceRules$BiomeConditionSource accessible class net/minecraft/world/level/levelgen/SurfaceRules$TestRuleSource +accessible class net/minecraft/world/item/crafting/Ingredient$Value +accessible class net/minecraft/world/item/crafting/Ingredient$TagValue +accessible class net/minecraft/world/item/crafting/Ingredient$ItemValue #Methods accessible method net/minecraft/world/level/storage/loot/LootPool (Ljava/util/List;Ljava/util/List;Ljava/util/List;Lnet/minecraft/world/level/storage/loot/providers/number/NumberProvider;Lnet/minecraft/world/level/storage/loot/providers/number/NumberProvider;)V @@ -36,7 +39,11 @@ accessible method net/minecraft/world/level/block/Blocks never (Lnet/minecraft/w accessible method net/minecraft/world/level/block/Blocks never (Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/BlockGetter;Lnet/minecraft/core/BlockPos;)Z accessible method net/minecraft/world/level/levelgen/structure/pools/SinglePoolElement (Lcom/mojang/datafixers/util/Either;Lnet/minecraft/core/Holder;Lnet/minecraft/world/level/levelgen/structure/pools/StructureTemplatePool$Projection;)V accessible method net/minecraft/world/level/levelgen/structure/pools/LegacySinglePoolElement (Lcom/mojang/datafixers/util/Either;Lnet/minecraft/core/Holder;Lnet/minecraft/world/level/levelgen/structure/pools/StructureTemplatePool$Projection;)V +accessible method net/minecraft/world/item/crafting/Ingredient ([Lnet/minecraft/world/item/crafting/Ingredient$Value;)V +accessible method net/minecraft/world/item/crafting/Ingredient$TagValue (Lnet/minecraft/tags/TagKey;)V #Fields accessible field net/minecraft/world/entity/ai/village/poi/PoiTypes TYPE_BY_STATE Ljava/util/Map; -accessible field net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity lootTable Lnet/minecraft/resources/ResourceLocation; \ No newline at end of file +accessible field net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity lootTable Lnet/minecraft/resources/ResourceLocation; +accessible field net/minecraft/world/item/crafting/Ingredient values [Lnet/minecraft/world/item/crafting/Ingredient$Value; +accessible field net/minecraft/world/item/crafting/Ingredient$TagValue CODEC Lcom/mojang/serialization/Codec; \ No newline at end of file