[Change] Recipes use Codecs for Serialization now

This commit is contained in:
Frank 2023-12-18 15:23:13 +01:00
parent 7e06e9ce3f
commit 7a972fc22f
4 changed files with 162 additions and 160 deletions

View file

@ -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<Container>, UnknownReceipBookCategory {
public final static String GROUP = "alloying";
public final static RecipeType<AlloyingRecipe> TYPE = BCLRecipeManager.registerType(BCLib.MOD_ID, GROUP);
@ -38,7 +40,6 @@ public class AlloyingRecipe implements Recipe<Container>, 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<Container>, UnknownReceipBookCateg
protected final int smeltTime;
public AlloyingRecipe(
ResourceLocation id,
List<Ingredient> 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<Container>, 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<Container>, 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<Container>, UnknownReceipBookCateg
}
public static class Serializer implements RecipeSerializer<AlloyingRecipe> {
@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<AlloyingRecipe> 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<Container>, 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<AlloyingRecipe> codec() {
return null;
}
@Override

View file

@ -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<Container>, 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<Container>, UnknownReceipBookCategory
private final int inputCount;
public AnvilRecipe(
ResourceLocation identifier,
Ingredient input,
ItemStack output,
int inputCount,
@ -70,7 +69,6 @@ public class AnvilRecipe implements Recipe<Container>, 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<Container>, 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<Container>, 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<Builder, AnvilRecipe> {
@ -357,25 +357,23 @@ public class AnvilRecipe implements Recipe<Container>, UnknownReceipBookCategory
}
public static class Serializer implements RecipeSerializer<AnvilRecipe> {
@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 static Codec<AnvilRecipe> 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 fromNetwork(ResourceLocation id, FriendlyByteBuf packetBuffer) {
public Codec<AnvilRecipe> codec() {
return CODEC;
}
@Override
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<Container>, 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

View file

@ -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)
Item item = BuiltInRegistries
.ITEM
.getOptional(itemId)
.orElseThrow(() -> new IllegalStateException("Output item " + itemId + " does not exists!"));
return new ItemStack(item, Integer.valueOf(parts[2]));
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);
}
}
return null;
}
public static Codec<ItemStack> 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 void writeNBT(JsonObject root, CompoundTag nbt) {
if (nbt != null) {
final String nbtData = nbt.toString();
root.addProperty("nbt", nbtData);
public static Codec<Ingredient> CODEC_INGREDIENT_WITH_NBT = ingredientCodec(true);
public static Codec<Ingredient> CODEC_INGREDIENT_WITH_NBT_NOT_EMPTY = ingredientCodec(false);
private static Codec<Ingredient> ingredientCodec(boolean allowEmpty) {
record NbtItemValue(ItemStack item) implements Ingredient.Value {
static final Codec<NbtItemValue> 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 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 Collection<ItemStack> getItems() {
return Collections.singleton(this.item);
}
public static ItemStack fromJsonRecipeWithNBT(JsonObject recipe) {
ItemStack output = ItemUtil.fromJsonRecipe(recipe);
CompoundTag nbt = ItemUtil.readNBT(recipe);
if (output != null && nbt != null) {
output.setTag(nbt);
public ItemStack item() {
return this.item;
}
return output;
}
@Nullable
public static ItemStack fromJsonRecipe(JsonObject recipe) {
try {
if (!recipe.has("item")) {
throw new IllegalStateException("Invalid JsonObject. Entry 'item' does not exists!");
Codec<Ingredient.Value> 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.");
}
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;
}
);
public static JsonObject toJsonRecipeWithNBT(ItemStack stack) {
return toJsonRecipeWithNBT(stack.getItem(), stack.getCount(), stack.getTag());
}
Codec<Ingredient.Value[]> 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;
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));
}
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;
);
}
}

View file

@ -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 <init> (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 <init> (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 <init> (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 <init> ([Lnet/minecraft/world/item/crafting/Ingredient$Value;)V
accessible method net/minecraft/world/item/crafting/Ingredient$TagValue <init> (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;
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;