package wayoftime.bloodmagic.api.event.recipes; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Objects; import javax.annotation.Nonnull; import javax.annotation.Nullable; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; import com.google.gson.JsonSyntaxException; import net.minecraft.fluid.Fluid; import net.minecraft.network.PacketBuffer; import net.minecraft.tags.FluidTags; import net.minecraft.tags.ITag; import net.minecraft.tags.TagCollectionManager; import net.minecraft.util.JSONUtils; import net.minecraft.util.ResourceLocation; import net.minecraftforge.fluids.FluidStack; import wayoftime.bloodmagic.api.SerializerHelper; import wayoftime.bloodmagic.util.Constants; /** * Created by Thiakil on 12/07/2019. */ public abstract class FluidStackIngredient implements InputIngredient { public static FluidStackIngredient from(@Nonnull Fluid instance, int amount) { return from(new FluidStack(instance, amount)); } public static FluidStackIngredient from(@Nonnull FluidStack instance) { return new Single(instance); } public static FluidStackIngredient from(@Nonnull ITag fluidTag, int minAmount) { return new Tagged(fluidTag, minAmount); } public static FluidStackIngredient read(PacketBuffer buffer) { // TODO: Allow supporting serialization of different types than just the ones we // implement? IngredientType type = buffer.readEnumValue(IngredientType.class); if (type == IngredientType.SINGLE) { return Single.read(buffer); } else if (type == IngredientType.TAGGED) { return Tagged.read(buffer); } return Multi.read(buffer); } public static FluidStackIngredient deserialize(@Nullable JsonElement json) { if (json == null || json.isJsonNull()) { throw new JsonSyntaxException("Ingredient cannot be null"); } if (json.isJsonArray()) { JsonArray jsonArray = json.getAsJsonArray(); int size = jsonArray.size(); if (size == 0) { throw new JsonSyntaxException("Ingredient array cannot be empty, at least one ingredient must be defined"); } else if (size > 1) { FluidStackIngredient[] ingredients = new FluidStackIngredient[size]; for (int i = 0; i < size; i++) { // Read all the ingredients ingredients[i] = deserialize(jsonArray.get(i)); } return createMulti(ingredients); } // If we only have a single element, just set our json as that so that we don't // have to use Multi for efficiency reasons json = jsonArray.get(0); } if (!json.isJsonObject()) { throw new JsonSyntaxException("Expected fluid to be object or array of objects"); } JsonObject jsonObject = json.getAsJsonObject(); if (jsonObject.has(Constants.JSON.FLUID) && jsonObject.has(Constants.JSON.TAG)) { throw new JsonParseException("An ingredient entry is either a tag or an fluid, not both"); } else if (jsonObject.has(Constants.JSON.FLUID)) { return from(SerializerHelper.deserializeFluid(jsonObject)); } else if (jsonObject.has(Constants.JSON.TAG)) { if (!jsonObject.has(Constants.JSON.AMOUNT)) { throw new JsonSyntaxException("Expected to receive a amount that is greater than zero"); } JsonElement count = jsonObject.get(Constants.JSON.AMOUNT); if (!JSONUtils.isNumber(count)) { throw new JsonSyntaxException("Expected amount to be a number greater than zero."); } int amount = count.getAsJsonPrimitive().getAsInt(); if (amount < 1) { throw new JsonSyntaxException("Expected amount to be greater than zero."); } ResourceLocation resourceLocation = new ResourceLocation(JSONUtils.getString(jsonObject, Constants.JSON.TAG)); ITag tag = TagCollectionManager.getManager().getFluidTags().get(resourceLocation); if (tag == null) { throw new JsonSyntaxException("Unknown fluid tag '" + resourceLocation + "'"); } return from(tag, amount); } throw new JsonSyntaxException("Expected to receive a resource location representing either a tag or a fluid."); } public static FluidStackIngredient createMulti(FluidStackIngredient... ingredients) { if (ingredients.length == 0) { // TODO: Throw error } else if (ingredients.length == 1) { return ingredients[0]; } List cleanedIngredients = new ArrayList<>(); for (FluidStackIngredient ingredient : ingredients) { if (ingredient instanceof Multi) { // Don't worry about if our inner ingredients are multi as well, as if this is // the only external method for // creating a multi ingredient, then we are certified they won't be of a higher // depth cleanedIngredients.addAll(Arrays.asList(((Multi) ingredient).ingredients)); } else { cleanedIngredients.add(ingredient); } } // There should be more than a single fluid or we would have split out earlier return new Multi(cleanedIngredients.toArray(new FluidStackIngredient[0])); } public static class Single extends FluidStackIngredient { @Nonnull private final FluidStack fluidInstance; public Single(@Nonnull FluidStack fluidInstance) { this.fluidInstance = Objects.requireNonNull(fluidInstance); } @Override public boolean test(@Nonnull FluidStack fluidStack) { return testType(fluidStack) && fluidStack.getAmount() >= fluidInstance.getAmount(); } @Override public boolean testType(@Nonnull FluidStack fluidStack) { return Objects.requireNonNull(fluidStack).isFluidEqual(fluidInstance); } @Nonnull @Override public FluidStack getMatchingInstance(@Nonnull FluidStack fluidStack) { return test(fluidStack) ? fluidInstance : FluidStack.EMPTY; } @Nonnull @Override public List getRepresentations() { return Collections.singletonList(fluidInstance); } @Override public void write(PacketBuffer buffer) { buffer.writeEnumValue(IngredientType.SINGLE); fluidInstance.writeToPacket(buffer); } @Nonnull @Override public JsonElement serialize() { JsonObject json = new JsonObject(); json.addProperty(Constants.JSON.AMOUNT, fluidInstance.getAmount()); json.addProperty(Constants.JSON.FLUID, fluidInstance.getFluid().getRegistryName().toString()); if (fluidInstance.hasTag()) { json.addProperty(Constants.JSON.NBT, fluidInstance.getTag().toString()); } return json; } public static Single read(PacketBuffer buffer) { return new Single(FluidStack.readFromPacket(buffer)); } } public static class Tagged extends FluidStackIngredient { @Nonnull private final ITag tag; private final int amount; public Tagged(@Nonnull ITag tag, int amount) { this.tag = tag; this.amount = amount; } @Override public boolean test(@Nonnull FluidStack fluidStack) { return testType(fluidStack) && fluidStack.getAmount() >= amount; } @Override public boolean testType(@Nonnull FluidStack fluidStack) { return Objects.requireNonNull(fluidStack).getFluid().isIn(tag); } @Nonnull @Override public FluidStack getMatchingInstance(@Nonnull FluidStack fluidStack) { if (test(fluidStack)) { // Our fluid is in the tag so we make a new stack with the given amount return new FluidStack(fluidStack, amount); } return FluidStack.EMPTY; } @Nonnull @Override public List getRepresentations() { // TODO: Can this be cached some how List representations = new ArrayList<>(); for (Fluid fluid : TagResolverHelper.getRepresentations(tag)) { representations.add(new FluidStack(fluid, amount)); } return representations; } @Override public void write(PacketBuffer buffer) { buffer.writeEnumValue(IngredientType.TAGGED); buffer.writeResourceLocation(TagCollectionManager.getManager().getFluidTags().getValidatedIdFromTag(tag)); buffer.writeVarInt(amount); } @Nonnull @Override public JsonElement serialize() { JsonObject json = new JsonObject(); json.addProperty(Constants.JSON.AMOUNT, amount); json.addProperty(Constants.JSON.TAG, TagCollectionManager.getManager().getFluidTags().getValidatedIdFromTag(tag).toString()); return json; } public static Tagged read(PacketBuffer buffer) { return new Tagged(FluidTags.makeWrapperTag(buffer.readResourceLocation().toString()), buffer.readVarInt()); } } public static class Multi extends FluidStackIngredient { private final FluidStackIngredient[] ingredients; protected Multi(@Nonnull FluidStackIngredient... ingredients) { this.ingredients = ingredients; } @Override public boolean test(@Nonnull FluidStack stack) { return Arrays.stream(ingredients).anyMatch(ingredient -> ingredient.test(stack)); } @Override public boolean testType(@Nonnull FluidStack stack) { return Arrays.stream(ingredients).anyMatch(ingredient -> ingredient.testType(stack)); } @Nonnull @Override public FluidStack getMatchingInstance(@Nonnull FluidStack stack) { for (FluidStackIngredient ingredient : ingredients) { FluidStack matchingInstance = ingredient.getMatchingInstance(stack); if (!matchingInstance.isEmpty()) { return matchingInstance; } } return FluidStack.EMPTY; } @Nonnull @Override public List getRepresentations() { List representations = new ArrayList<>(); for (FluidStackIngredient ingredient : ingredients) { representations.addAll(ingredient.getRepresentations()); } return representations; } @Override public void write(PacketBuffer buffer) { buffer.writeEnumValue(IngredientType.MULTI); buffer.writeVarInt(ingredients.length); for (FluidStackIngredient ingredient : ingredients) { ingredient.write(buffer); } } @Nonnull @Override public JsonElement serialize() { JsonArray json = new JsonArray(); for (FluidStackIngredient ingredient : ingredients) { json.add(ingredient.serialize()); } return json; } public static FluidStackIngredient read(PacketBuffer buffer) { FluidStackIngredient[] ingredients = new FluidStackIngredient[buffer.readVarInt()]; for (int i = 0; i < ingredients.length; i++) { ingredients[i] = FluidStackIngredient.read(buffer); } return createMulti(ingredients); } } private enum IngredientType { SINGLE, TAGGED, MULTI } }