From dc7613dd4669393a313b270b55cfaaa3ff8c94a3 Mon Sep 17 00:00:00 2001 From: Uko Kokņevičs Date: Sat, 13 Jan 2024 01:12:12 +0100 Subject: Toposort recipes so it's actually usably fast --- src/main/java/lv/enes/mc/eris_alchemy/Emc.java | 171 ++++++++++++--------- .../java/lv/enes/mc/eris_alchemy/EmcLoader.java | 15 +- .../java/lv/enes/mc/eris_alchemy/ErisAlchemy.java | 6 + .../lv/enes/mc/eris_alchemy/SimplifiedRecipe.java | 89 ----------- .../enes/mc/eris_alchemy/recipe/BannedRecipe.java | 40 +++++ .../mc/eris_alchemy/recipe/SimplifiedRecipe.java | 123 +++++++++++++++ .../lv/enes/mc/eris_alchemy/utils/ConsList.java | 103 +++++++++++++ .../mc/eris_alchemy/utils/IngredientProvider.java | 44 ++++++ .../lv/enes/mc/eris_alchemy/utils/ItemUtils.java | 5 + .../eris_alchemy/utils/OptionalDoubleSummer.java | 47 ++++++ .../java/lv/enes/mc/eris_alchemy/utils/Ref.java | 14 ++ .../eris_alchemy/cycle_cut/minecraft.json | 64 ++++++++ .../eris_alchemy/item_emcs/minecraft.json | 62 ++++---- 13 files changed, 589 insertions(+), 194 deletions(-) delete mode 100644 src/main/java/lv/enes/mc/eris_alchemy/SimplifiedRecipe.java create mode 100644 src/main/java/lv/enes/mc/eris_alchemy/recipe/BannedRecipe.java create mode 100644 src/main/java/lv/enes/mc/eris_alchemy/recipe/SimplifiedRecipe.java create mode 100644 src/main/java/lv/enes/mc/eris_alchemy/utils/ConsList.java create mode 100644 src/main/java/lv/enes/mc/eris_alchemy/utils/IngredientProvider.java create mode 100644 src/main/java/lv/enes/mc/eris_alchemy/utils/OptionalDoubleSummer.java create mode 100644 src/main/java/lv/enes/mc/eris_alchemy/utils/Ref.java create mode 100644 src/main/resources/data/eris_alchemy/eris_alchemy/cycle_cut/minecraft.json diff --git a/src/main/java/lv/enes/mc/eris_alchemy/Emc.java b/src/main/java/lv/enes/mc/eris_alchemy/Emc.java index 2e7e2d7..d058b94 100644 --- a/src/main/java/lv/enes/mc/eris_alchemy/Emc.java +++ b/src/main/java/lv/enes/mc/eris_alchemy/Emc.java @@ -2,9 +2,9 @@ package lv.enes.mc.eris_alchemy; import jakarta.annotation.Nullable; import lv.enes.mc.eris_alchemy.ErisAlchemyRegistry.NetworkingConstants; -import lv.enes.mc.eris_alchemy.utils.BufUtils; -import lv.enes.mc.eris_alchemy.utils.ItemUtils; -import lv.enes.mc.eris_alchemy.utils.PlayerUtils; +import lv.enes.mc.eris_alchemy.recipe.BannedRecipe; +import lv.enes.mc.eris_alchemy.recipe.SimplifiedRecipe; +import lv.enes.mc.eris_alchemy.utils.*; import net.minecraft.client.Minecraft; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.core.registries.Registries; @@ -27,6 +27,7 @@ import org.quiltmc.qsl.networking.api.client.ClientPlayNetworking; import java.text.DecimalFormat; import java.util.*; +import java.util.function.Supplier; import java.util.stream.Stream; public final class Emc { @@ -36,6 +37,7 @@ public final class Emc { private static final Map, OptionalDouble> ITEM_TAG_VALUES = new HashMap<>(); private static final Map, OptionalDouble> BLOCK_TAG_VALUES = new HashMap<>(); private static final List FAKE_RECIPES = new ArrayList<>(); + private static final List BANNED_RECIPES = new ArrayList<>(); private static final Map VALUES = Collections.synchronizedMap(new HashMap<>()); @@ -102,7 +104,8 @@ public final class Emc { Map itemValues, Map itemTagValues, Map blockTagValues, - List fakeRecipes + List fakeRecipes, + List bannedRecipes ) { ITEM_VALUES.clear(); ITEM_VALUES.putAll(itemValues); @@ -116,86 +119,69 @@ public final class Emc { FAKE_RECIPES.clear(); FAKE_RECIPES.addAll(fakeRecipes); + BANNED_RECIPES.clear(); + BANNED_RECIPES.addAll(bannedRecipes); + reinit(); warnOfMissingValues(); } - private static OptionalDouble calculateEmcForIngredient( - List recipes, - Set configured, - Ingredient ingredient + private static OptionalDouble calcEmc( + ResourceLocation item, + Map> allRecipes ) { + return allRecipes.getOrDefault(item, List.of()) + .stream() + .map(Emc::calcEmcForRecipe) + .flatMapToDouble(OptionalDouble::stream) + .average(); + } + + private static OptionalDouble calcEmcForIngredient(Ingredient ingredient) { return Arrays.stream(ingredient.getItems()) - .map(stack -> configEmc(recipes, configured, ItemUtils.getId(stack.getItem())).stream() - .map(x -> x * stack.getCount()) - .findFirst()) - .filter(OptionalDouble::isPresent) - .mapToDouble(OptionalDouble::getAsDouble) - .filter(x -> x > 0) + .map(Emc::get) + .flatMapToDouble(OptionalDouble::stream) .average(); } - private static OptionalDouble calculateEmcForRecipe( - List recipes, - Set configured, - SimplifiedRecipe recipe - ) { - try { - if (recipe.input().isEmpty()) { - return OptionalDouble.empty(); - } - - var inputEmc = recipe.input() - .stream() - .map(ingredient -> calculateEmcForIngredient(recipes, configured, ingredient)) - .mapToDouble(OptionalDouble::orElseThrow) - .sum(); - - var remainderEmc = recipe.remainder() - .stream() - .map(remainder -> configEmc(recipes, configured, ItemUtils.getId(remainder))) - .mapToDouble(OptionalDouble::orElseThrow) - .sum(); - - if (remainderEmc > inputEmc) { - ErisAlchemy.LOGGER.warn("Recipe generating {} creates too much EMC out of thin air!", recipe.output()); - return OptionalDouble.empty(); - } - - var outputDivisor = (double) recipe.output().getCount(); - - return OptionalDouble.of((inputEmc - remainderEmc) / outputDivisor); - } catch (NoSuchElementException e) { + private static OptionalDouble calcEmcForRecipe(SimplifiedRecipe recipe) { + if (recipe.input().isEmpty()) { return OptionalDouble.empty(); } - } - private static OptionalDouble configEmc( - List recipes, - Set configured, - ResourceLocation itemId - ) { - var res = get(itemId); - if (res.isPresent() || configured.contains(itemId)) { - return res; + var inputEmcOpt = recipe.input() + .stream() + .map(Supplier::get) + .map(Emc::calcEmcForIngredient) + .collect(new OptionalDoubleSummer()); + + if (inputEmcOpt.isEmpty()) { + return OptionalDouble.empty(); } - configured.add(itemId); - var item = BuiltInRegistries.ITEM.get(itemId); - res = recipes.stream() - .filter(recipe -> recipe.output().is(item)) - .map(recipe -> calculateEmcForRecipe(recipes, configured, recipe)) - .flatMapToDouble(OptionalDouble::stream) - .average(); - res.ifPresentOrElse( - emc -> VALUES.put(itemId, OptionalDouble.of(emc)), - () -> configured.remove(itemId) - ); - return res; + var remainderEmcOpt = recipe.remainder() + .stream() + .map(Emc::get) + .collect(new OptionalDoubleSummer()); + + if (remainderEmcOpt.isEmpty()) { + return OptionalDouble.empty(); + } + + var inputEmc = inputEmcOpt.getAsDouble(); + var remainderEmc = remainderEmcOpt.getAsDouble(); + if (remainderEmc > inputEmc) { + ErisAlchemy.LOGGER.warn("Recipe generating {} creates too much EMC out of thin air!", recipe.output()); + return OptionalDouble.empty(); + } + + var outputDivisor = (double) recipe.output().getCount(); + + return OptionalDouble.of((inputEmc - remainderEmc) / outputDivisor); } - private static List getRecipes(@Nullable Level world) { - Stream recipes = FAKE_RECIPES.stream(); + private static Stream getRecipes(@Nullable Level world) { + var recipes = FAKE_RECIPES.stream(); if (world != null) { recipes = Stream.concat( @@ -207,7 +193,7 @@ public final class Emc { ); } - return recipes.toList(); + return recipes; } private static void reinit() { @@ -225,14 +211,55 @@ public final class Emc { ); ErisAlchemy.LOGGER.info("Calculating EMC values from recipes..."); - var recipes = getRecipes(overworld); - var configured = new HashSet<>(VALUES.keySet()); - BuiltInRegistries.ITEM.keySet().forEach(item -> configEmc(recipes, configured, item)); + var recipes = new HashMap>(); + getRecipes(overworld) + .filter(recipe -> !recipe.hasDuplication()) + .filter(recipe -> recipe.isAllowed(BANNED_RECIPES)) + .forEach(recipe -> + recipes + .computeIfAbsent(ItemUtils.getId(recipe.output()), k -> new ArrayList<>()) + .add(recipe) + ); + var sortedItems = sorted(recipes); + sortedItems.stream() + .filter(id -> !VALUES.containsKey(id)) + .forEach(id -> VALUES.put(id, calcEmc(id, recipes))); ErisAlchemy.LOGGER.info("Done calculating EMC values..."); sync(); } + private static void sortDps( + Set permSorted, + ConsList tmpSorted, + ResourceLocation item, + Map> data + ) { + if (permSorted.contains(item)) { + return; + } + + var newTmpSorted = ConsList.cons(item, tmpSorted); + + if (tmpSorted.contains(item)) { + ErisAlchemy.LOGGER.warn("Cycle in recipes detected: {}, breaking here", newTmpSorted); + return; + } + + data.getOrDefault(item, List.of()) + .stream() + .flatMap(SimplifiedRecipe::dependencies) + .distinct() + .forEach(dep -> sortDps(permSorted, newTmpSorted, dep, data)); + permSorted.add(item); + } + + private static Set sorted(Map> unsorted) { + var res = new LinkedHashSet(); + unsorted.forEach((item, recipes) -> sortDps(res, ConsList.nil(), item, unsorted)); + return res; + } + private static void sync() { syncTo(PlayerUtils.all()); } diff --git a/src/main/java/lv/enes/mc/eris_alchemy/EmcLoader.java b/src/main/java/lv/enes/mc/eris_alchemy/EmcLoader.java index dd4613f..373e561 100644 --- a/src/main/java/lv/enes/mc/eris_alchemy/EmcLoader.java +++ b/src/main/java/lv/enes/mc/eris_alchemy/EmcLoader.java @@ -2,6 +2,8 @@ package lv.enes.mc.eris_alchemy; import com.google.gson.reflect.TypeToken; import jakarta.annotation.Nonnull; +import lv.enes.mc.eris_alchemy.recipe.BannedRecipe; +import lv.enes.mc.eris_alchemy.recipe.SimplifiedRecipe; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.packs.resources.ResourceManager; import org.quiltmc.qsl.resource.loader.api.reloader.SimpleSynchronousResourceReloader; @@ -30,7 +32,9 @@ public class EmcLoader implements SimpleSynchronousResourceReloader { var fakeRecipes = loadAllFiles(manager, "fake_recipes", new ArrayList<>(), EmcLoader::loadFakeRecipes); - Emc.reloadData(itemValues, itemTagValues, blockTagValues, fakeRecipes); + var bannedRecipes = loadAllFiles(manager, "cycle_cut", new ArrayList<>(), EmcLoader::loadBannedRecipes); + + Emc.reloadData(itemValues, itemTagValues, blockTagValues, fakeRecipes, bannedRecipes); } private static T loadAllFiles( @@ -58,6 +62,15 @@ public class EmcLoader implements SimpleSynchronousResourceReloader { return arg; } + private static void loadBannedRecipes(List recipes, ResourceLocation id, InputStream is) + throws IOException + { + try (var reader = new InputStreamReader(is)) { + var json = GSON.fromJson(reader, new TypeToken>(){}); + recipes.addAll(json); + } + } + private static void loadEmcValues(Map map, ResourceLocation id, InputStream is) throws IOException { diff --git a/src/main/java/lv/enes/mc/eris_alchemy/ErisAlchemy.java b/src/main/java/lv/enes/mc/eris_alchemy/ErisAlchemy.java index 1d363c2..644a2a4 100644 --- a/src/main/java/lv/enes/mc/eris_alchemy/ErisAlchemy.java +++ b/src/main/java/lv/enes/mc/eris_alchemy/ErisAlchemy.java @@ -2,6 +2,9 @@ package lv.enes.mc.eris_alchemy; import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import lv.enes.mc.eris_alchemy.recipe.BannedRecipe; +import lv.enes.mc.eris_alchemy.recipe.SimplifiedRecipe; +import lv.enes.mc.eris_alchemy.utils.IngredientProvider; import lv.enes.mc.eris_alchemy.utils.PlayerUtils; import net.fabricmc.fabric.api.itemgroup.v1.FabricItemGroup; import net.minecraft.core.Registry; @@ -11,6 +14,7 @@ import net.minecraft.resources.ResourceLocation; import net.minecraft.server.packs.PackType; import net.minecraft.world.item.CreativeModeTab; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.Ingredient; import org.quiltmc.loader.api.ModContainer; import org.quiltmc.qsl.base.api.entrypoint.ModInitializer; import org.quiltmc.qsl.lifecycle.api.event.ServerLifecycleEvents; @@ -20,6 +24,8 @@ import org.slf4j.LoggerFactory; public class ErisAlchemy implements ModInitializer { public static final Gson GSON = new GsonBuilder() + .registerTypeAdapter(BannedRecipe.class, new BannedRecipe.Deserializer()) + .registerTypeAdapter(Ingredient.class, new IngredientProvider()) .registerTypeAdapter(ResourceLocation.class, new ResourceLocation.Serializer()) .registerTypeAdapter(SimplifiedRecipe.class, new SimplifiedRecipe.Deserializer()) .create(); diff --git a/src/main/java/lv/enes/mc/eris_alchemy/SimplifiedRecipe.java b/src/main/java/lv/enes/mc/eris_alchemy/SimplifiedRecipe.java deleted file mode 100644 index ef69b84..0000000 --- a/src/main/java/lv/enes/mc/eris_alchemy/SimplifiedRecipe.java +++ /dev/null @@ -1,89 +0,0 @@ -package lv.enes.mc.eris_alchemy; - -import com.google.gson.*; -import jakarta.annotation.Nonnull; -import lv.enes.mc.eris_alchemy.utils.RecipeUtils; -import net.minecraft.core.RegistryAccess; -import net.minecraft.core.registries.BuiltInRegistries; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.crafting.Ingredient; -import net.minecraft.world.item.crafting.Recipe; -import net.minecraft.world.item.crafting.ShapedRecipe; - -import java.lang.reflect.Type; -import java.util.List; - -public record SimplifiedRecipe(ItemStack output, List remainder, List input) { - public SimplifiedRecipe(Recipe recipe, RegistryAccess registryAccess) { - this( - RecipeUtils.getOutput(recipe, registryAccess), - List.of(), // TODO: - RecipeUtils.getIngredients(recipe).stream().filter(ingredient -> !ingredient.isEmpty()).toList() - ); - } - - static class Deserializer implements JsonDeserializer { - @Nonnull - @Override - public SimplifiedRecipe deserialize( - JsonElement jsonElement, - Type type, - JsonDeserializationContext jsonDeserializationContext - ) throws JsonParseException { - if (!jsonElement.isJsonObject()) { - throw new JsonParseException("Recipe must be an object"); - } - var obj = jsonElement.getAsJsonObject(); - - if (obj.get("output") == null || obj.get("input") == null) { - throw new JsonParseException("Recipe must have 'output' and 'input' fields"); - } - - var output = parseOutputOrRemainder(obj.get("output")); - var remainder = parseRemainders(obj.get("remainder")); - var input = parseInputs(obj.get("input")); - return new SimplifiedRecipe(output, remainder, input); - } - - private List parseInputs(JsonElement el) { - if (el.isJsonArray()) { - return el.getAsJsonArray().asList().stream().map(this::parseInput).map(Ingredient::of).toList(); - } - - return List.of(Ingredient.of(parseInput(el))); - } - - private ItemStack parseInput(JsonElement el) { - if (el.isJsonObject()) { - return ShapedRecipe.itemStackFromJson(el.getAsJsonObject()); - } else if (el.isJsonPrimitive() && el.getAsJsonPrimitive().isString()) { - var id = new ResourceLocation(el.getAsString()); - return BuiltInRegistries.ITEM.get(id).getDefaultInstance(); - } else { - throw new JsonParseException("Every recipe input should be an object or a string"); - } - } - - private ItemStack parseOutputOrRemainder(JsonElement el) { - if (el.isJsonObject()) { - return ShapedRecipe.itemStackFromJson(el.getAsJsonObject()); - } else if (el.isJsonPrimitive() && el.getAsJsonPrimitive().isString()) { - var id = new ResourceLocation(el.getAsString()); - return BuiltInRegistries.ITEM.get(id).getDefaultInstance(); - } else { - throw new JsonParseException("Recipe's output or remainder must be an object or a string"); - } - } - - private List parseRemainders(JsonElement el) { - if (el == null) { - return List.of(); - } else if (el.isJsonArray()) { - return el.getAsJsonArray().asList().stream().map(this::parseOutputOrRemainder).toList(); - } else { - return List.of(parseOutputOrRemainder(el)); - } - } - } -} diff --git a/src/main/java/lv/enes/mc/eris_alchemy/recipe/BannedRecipe.java b/src/main/java/lv/enes/mc/eris_alchemy/recipe/BannedRecipe.java new file mode 100644 index 0000000..d94222c --- /dev/null +++ b/src/main/java/lv/enes/mc/eris_alchemy/recipe/BannedRecipe.java @@ -0,0 +1,40 @@ +package lv.enes.mc.eris_alchemy.recipe; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import lv.enes.mc.eris_alchemy.utils.IngredientProvider; +import net.minecraft.world.item.crafting.Ingredient; + +import java.lang.reflect.Type; +import java.util.function.Supplier; + +public record BannedRecipe(Supplier input, Supplier output) { + public static BannedRecipe deserialize(JsonElement el) { + if (el.isJsonPrimitive() && el.getAsJsonPrimitive().isString()) { + return deserialize(el.getAsString()); + } else { + // TODO: Verbose object representation + throw new JsonParseException("Banned recipes should all be strings"); + } + } + + public static BannedRecipe deserialize(String str) { + var split = str.indexOf("->"); + var input = IngredientProvider.deserialize(str.substring(0, split).trim()); + var output = IngredientProvider.deserialize(str.substring(split + 2).trim()); + return new BannedRecipe(input, output); + } + + public static class Deserializer implements JsonDeserializer { + @Override + public BannedRecipe deserialize( + JsonElement el, + Type t, + JsonDeserializationContext ctx + ) throws JsonParseException { + return BannedRecipe.deserialize(el); + } + } +} diff --git a/src/main/java/lv/enes/mc/eris_alchemy/recipe/SimplifiedRecipe.java b/src/main/java/lv/enes/mc/eris_alchemy/recipe/SimplifiedRecipe.java new file mode 100644 index 0000000..469ed52 --- /dev/null +++ b/src/main/java/lv/enes/mc/eris_alchemy/recipe/SimplifiedRecipe.java @@ -0,0 +1,123 @@ +package lv.enes.mc.eris_alchemy.recipe; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import jakarta.annotation.Nonnull; +import lv.enes.mc.eris_alchemy.utils.IngredientProvider; +import lv.enes.mc.eris_alchemy.utils.ItemUtils; +import lv.enes.mc.eris_alchemy.utils.RecipeUtils; +import net.minecraft.core.RegistryAccess; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.Ingredient; +import net.minecraft.world.item.crafting.Recipe; +import net.minecraft.world.item.crafting.ShapedRecipe; + +import java.lang.reflect.Type; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.function.Supplier; +import java.util.stream.Stream; + +public record SimplifiedRecipe(ItemStack output, List remainder, List> input) { + public SimplifiedRecipe(Recipe recipe, RegistryAccess registryAccess) { + this( + RecipeUtils.getOutput(recipe, registryAccess), + List.of(), // TODO: + RecipeUtils.getIngredients(recipe) + .stream() + .filter(ingredient -> !ingredient.isEmpty()) + .map(x -> (Supplier) () -> x) + .toList() + ); + } + + public Stream dependencies() { + return Stream.concat( + input.stream().map(Supplier::get).map(Ingredient::getItems).flatMap(Arrays::stream), + remainder.stream() + ).map(ItemUtils::getId); + } + + public boolean hasDuplication() { + var outputId = ItemUtils.getId(output); + var outputsInRemainder = remainder.stream() + .map(ItemUtils::getId) + .anyMatch(id -> Objects.equals(id, outputId)); + if (outputsInRemainder) { + return true; + } + + return input.stream().anyMatch(ingredient -> ingredient.get().test(output)); + } + + public boolean isAllowed(BannedRecipe ban) { + if (!ban.output().get().test(output())) { + return true; + } + + return dependencies().noneMatch(dep -> ban.input().get().test(ItemUtils.get(dep).getDefaultInstance())); + } + + public boolean isAllowed(Collection bans) { + return bans.stream().allMatch(this::isAllowed); + } + + public static class Deserializer implements JsonDeserializer { + @Nonnull + @Override + public SimplifiedRecipe deserialize( + JsonElement jsonElement, + Type type, + JsonDeserializationContext jsonDeserializationContext + ) throws JsonParseException { + if (!jsonElement.isJsonObject()) { + throw new JsonParseException("Recipe must be an object"); + } + var obj = jsonElement.getAsJsonObject(); + + if (obj.get("output") == null || obj.get("input") == null) { + throw new JsonParseException("Recipe must have 'output' and 'input' fields"); + } + + var output = parseOutputOrRemainder(obj.get("output")); + var remainder = parseRemainders(obj.get("remainder")); + var input = parseInputs(obj.get("input")); + return new SimplifiedRecipe(output, remainder, input); + } + + private List> parseInputs(JsonElement el) { + if (el.isJsonArray()) { + return el.getAsJsonArray().asList().stream().map(IngredientProvider::deserialize).toList(); + } + + return List.of(IngredientProvider.deserialize(el)); + } + + private ItemStack parseOutputOrRemainder(JsonElement el) { + if (el.isJsonObject()) { + return ShapedRecipe.itemStackFromJson(el.getAsJsonObject()); + } else if (el.isJsonPrimitive() && el.getAsJsonPrimitive().isString()) { + var id = new ResourceLocation(el.getAsString()); + return BuiltInRegistries.ITEM.get(id).getDefaultInstance(); + } else { + throw new JsonParseException("Recipe's output or remainder must be an object or a string"); + } + } + + private List parseRemainders(JsonElement el) { + if (el == null) { + return List.of(); + } else if (el.isJsonArray()) { + return el.getAsJsonArray().asList().stream().map(this::parseOutputOrRemainder).toList(); + } else { + return List.of(parseOutputOrRemainder(el)); + } + } + } +} diff --git a/src/main/java/lv/enes/mc/eris_alchemy/utils/ConsList.java b/src/main/java/lv/enes/mc/eris_alchemy/utils/ConsList.java new file mode 100644 index 0000000..bbab8a6 --- /dev/null +++ b/src/main/java/lv/enes/mc/eris_alchemy/utils/ConsList.java @@ -0,0 +1,103 @@ +package lv.enes.mc.eris_alchemy.utils; + +import jakarta.annotation.Nonnull; + +import java.util.Objects; +import java.util.Optional; +import java.util.function.Supplier; +import java.util.stream.Stream; + +public sealed abstract class ConsList { + public static ConsList cons(E car, ConsList cdr) { + return new Cons<>(car, cdr); + } + + public static ConsList nil() { + return Nil.of(); + } + + @Nonnull + public abstract Optional car(); + + @Nonnull + public abstract ConsList cdr(); + + public boolean contains(E elem) { + return stream().anyMatch(x -> Objects.equals(x, elem)); + } + + public Stream stream() { + var car = car(); + if (car.isEmpty()) { + return Stream.empty(); + } + + return Stream.>>of( + () -> Stream.of(car.get()), + () -> cdr().stream() + ) + .flatMap(Supplier::get); + } + + @Override + public String toString() { + if (car().isEmpty()) { + return "[]"; + } + + var sb = new StringBuilder("["); + sb.append(car().get()); + stream().skip(1) + .forEach(x -> { + sb.append(" -> "); + sb.append(x); + }); + sb.append("]"); + return sb.toString(); + } + + private static final class Cons extends ConsList { + private final E car; + private final ConsList cdr; + + public Cons(E car, ConsList cdr) { + this.car = car; + this.cdr = cdr; + } + + @Nonnull + @Override + public Optional car() { + return Optional.of(car); + } + + @Nonnull + @Override + public ConsList cdr() { + return cdr; + } + } + + private static final class Nil extends ConsList { + private static final Nil INSTANCE = new Nil<>(); + + @SuppressWarnings("unchecked") + public static Nil of() { + return (Nil)INSTANCE; + } + + private Nil(){} + + @Nonnull + @Override + public Optional car() { + return Optional.empty(); + } + + @Nonnull + @Override + public ConsList cdr() { + return Nil.of(); + } + } +} diff --git a/src/main/java/lv/enes/mc/eris_alchemy/utils/IngredientProvider.java b/src/main/java/lv/enes/mc/eris_alchemy/utils/IngredientProvider.java new file mode 100644 index 0000000..e859114 --- /dev/null +++ b/src/main/java/lv/enes/mc/eris_alchemy/utils/IngredientProvider.java @@ -0,0 +1,44 @@ +package lv.enes.mc.eris_alchemy.utils; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.crafting.Ingredient; +import net.minecraft.world.item.crafting.ShapedRecipe; + +import java.lang.reflect.Type; +import java.util.function.Supplier; + +public class IngredientProvider implements JsonDeserializer> { + public static Supplier deserialize(JsonElement el) { + if (el.isJsonObject()) { + return () -> Ingredient.of(ShapedRecipe.itemStackFromJson(el.getAsJsonObject())); + } else if (el.isJsonPrimitive() && el.getAsJsonPrimitive().isString()) { + return deserialize(el.getAsString().strip()); + } else { + throw new JsonParseException("Every ingredient should be an object or a string"); + } + } + + public static Supplier deserialize(String str) { + if (str.startsWith("#")) { + var tag = TagKey.create(Registries.ITEM, new ResourceLocation(str.substring(1).strip())); + return () -> Ingredient.of(tag); + } else { + return () -> Ingredient.of(ItemUtils.get(new ResourceLocation(str))); + } + } + + @Override + public Supplier deserialize( + JsonElement el, + Type t, + JsonDeserializationContext c + ) { + return IngredientProvider.deserialize(el); + } +} diff --git a/src/main/java/lv/enes/mc/eris_alchemy/utils/ItemUtils.java b/src/main/java/lv/enes/mc/eris_alchemy/utils/ItemUtils.java index 4414d06..7ac7358 100644 --- a/src/main/java/lv/enes/mc/eris_alchemy/utils/ItemUtils.java +++ b/src/main/java/lv/enes/mc/eris_alchemy/utils/ItemUtils.java @@ -3,10 +3,15 @@ package lv.enes.mc.eris_alchemy.utils; import net.minecraft.core.Holder; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.ItemLike; public final class ItemUtils { + public static Item get(ResourceLocation id) { + return BuiltInRegistries.ITEM.get(id); + } + public static ResourceLocation getId(Holder holder) { return getId(holder.value()); } diff --git a/src/main/java/lv/enes/mc/eris_alchemy/utils/OptionalDoubleSummer.java b/src/main/java/lv/enes/mc/eris_alchemy/utils/OptionalDoubleSummer.java new file mode 100644 index 0000000..04b6ca8 --- /dev/null +++ b/src/main/java/lv/enes/mc/eris_alchemy/utils/OptionalDoubleSummer.java @@ -0,0 +1,47 @@ +package lv.enes.mc.eris_alchemy.utils; + +import java.util.OptionalDouble; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.BinaryOperator; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collector; + +public class OptionalDoubleSummer implements Collector, OptionalDouble> { + @Override + public Supplier> supplier() { + return () -> new Ref<>(OptionalDouble.of(0)); + } + + @Override + public BiConsumer, OptionalDouble> accumulator() { + return (ref, od) -> { + if (ref.getValue().isPresent()) { + if (od.isPresent()) { + ref.setValue(OptionalDouble.of(ref.getValue().getAsDouble() + od.getAsDouble())); + } else { + ref.setValue(OptionalDouble.empty()); + } + } + }; + } + + @Override + public BinaryOperator> combiner() { + return (ref1, ref2) -> { + accumulator().accept(ref1, ref2.getValue()); + return ref1; + }; + } + + @Override + public Function, OptionalDouble> finisher() { + return Ref::getValue; + } + + @Override + public Set characteristics() { + return Set.of(Characteristics.UNORDERED); + } +} diff --git a/src/main/java/lv/enes/mc/eris_alchemy/utils/Ref.java b/src/main/java/lv/enes/mc/eris_alchemy/utils/Ref.java new file mode 100644 index 0000000..dc2624a --- /dev/null +++ b/src/main/java/lv/enes/mc/eris_alchemy/utils/Ref.java @@ -0,0 +1,14 @@ +package lv.enes.mc.eris_alchemy.utils; + +import lombok.Getter; +import lombok.Setter; + +@Setter +@Getter +public class Ref { + private T value; + + public Ref(T value) { + this.value = value; + } +} diff --git a/src/main/resources/data/eris_alchemy/eris_alchemy/cycle_cut/minecraft.json b/src/main/resources/data/eris_alchemy/eris_alchemy/cycle_cut/minecraft.json new file mode 100644 index 0000000..663e70e --- /dev/null +++ b/src/main/resources/data/eris_alchemy/eris_alchemy/cycle_cut/minecraft.json @@ -0,0 +1,64 @@ +[ + "#minecraft:beds -> #minecraft:beds", + "#minecraft:wool -> #minecraft:wool", + "#minecraft:wool_carpets -> #minecraft:wool_carpets", + + "minecraft:bone_block -> minecraft:bone_meal", + + "minecraft:chiseled_quartz_block -> minecraft:quartz_slab", + "minecraft:chiseled_red_sandstone -> minecraft:red_sandstone_slab", + "minecraft:chiseled_sandstone -> minecraft:sandstone_slab", + "minecraft:coal_block -> minecraft:coal", + "minecraft:copper_block -> minecraft:copper_ingot", + + "minecraft:diamond_block -> minecraft:diamond", + "minecraft:disc_fragment_5 -> minecraft:music_disc_5", + "minecraft:dried_kelp_block -> minecraft:dried_kelp", + + "minecraft:emerald_block -> minecraft:emerald", + + "minecraft:filled_map -> minecraft:map", + + "minecraft:gold_block -> minecraft:gold_ingot", + "minecraft:gold_nugget -> minecraft:gold_ingot", + "minecraft:golden_helmet -> minecraft:gold_nugget", + "minecraft:golden_chestplate -> minecraft:gold_nugget", + "minecraft:golden_leggings -> minecraft:gold_nugget", + "minecraft:golden_boots -> minecraft:gold_nugget", + "minecraft:golden_sword -> minecraft:gold_nugget", + "minecraft:golden_pickaxe -> minecraft:gold_nugget", + "minecraft:golden_axe -> minecraft:gold_nugget", + "minecraft:golden_shovel -> minecraft:gold_nugget", + "minecraft:golden_hoe -> minecraft:gold_nugget", + + "minecraft:hay_block -> minecraft:wheat", + "minecraft:honey_block -> minecraft:honey_bottle", + + "minecraft:iron_block -> minecraft:iron_ingot", + "minecraft:iron_nugget -> minecraft:iron_ingot", + "minecraft:iron_helmet -> minecraft:iron_nugget", + "minecraft:iron_chestplate -> minecraft:iron_nugget", + "minecraft:iron_leggings -> minecraft:iron_nugget", + "minecraft:iron_boots -> minecraft:iron_nugget", + "minecraft:iron_sword -> minecraft:iron_nugget", + "minecraft:iron_pickaxe -> minecraft:iron_nugget", + "minecraft:iron_axe -> minecraft:iron_nugget", + "minecraft:iron_shovel -> minecraft:iron_nugget", + "minecraft:iron_hoe -> minecraft:iron_nugget", + + "minecraft:lapis_block -> minecraft:lapis_lazuli", + + "minecraft:netherite_block -> minecraft:netherite_ingot", + + "minecraft:redstone_block -> minecraft:redstone", + + "minecraft:slime_block -> minecraft:slime_ball", + + "minecraft:purpur_pillar -> minecraft:purpur_slab", + + "minecraft:raw_copper_block -> minecraft:raw_copper", + "minecraft:raw_gold_block -> minecraft:raw_gold", + "minecraft:raw_iron_block -> minecraft:raw_iron", + + "minecraft:waxed_copper_block -> minecraft:copper_ingot" +] \ No newline at end of file diff --git a/src/main/resources/data/eris_alchemy/eris_alchemy/item_emcs/minecraft.json b/src/main/resources/data/eris_alchemy/eris_alchemy/item_emcs/minecraft.json index d4066ce..196769a 100644 --- a/src/main/resources/data/eris_alchemy/eris_alchemy/item_emcs/minecraft.json +++ b/src/main/resources/data/eris_alchemy/eris_alchemy/item_emcs/minecraft.json @@ -20,15 +20,15 @@ "minecraft:blaze_spawn_egg": null, "minecraft:blue_dye": 8, "minecraft:bone": 96, - "minecraft:brain_coral": 1.0, - "minecraft:brain_coral_block": 1.0, - "minecraft:brain_coral_fan": 1.0, + "minecraft:brain_coral": 1, + "minecraft:brain_coral_block": 1, + "minecraft:brain_coral_fan": 1, "minecraft:brown_dye": 8, "minecraft:brown_mushroom": 32, "minecraft:brown_mushroom_block": null, - "minecraft:bubble_coral": 1.0, - "minecraft:bubble_coral_block": 1.0, - "minecraft:bubble_coral_fan": 1.0, + "minecraft:bubble_coral": 1, + "minecraft:bubble_coral_block": 1, + "minecraft:bubble_coral_fan": 1, "minecraft:budding_amethyst": null, "minecraft:bundle": null, "minecraft:cactus": 8, @@ -60,22 +60,22 @@ "minecraft:crimson_roots": 1, "minecraft:crying_obsidian": 64, "minecraft:cyan_dye": 8, - "minecraft:dead_brain_coral": 1.0, - "minecraft:dead_brain_coral_block": 1.0, - "minecraft:dead_brain_coral_fan": 1.0, - "minecraft:dead_bubble_coral": 1.0, - "minecraft:dead_bubble_coral_block": 1.0, - "minecraft:dead_bubble_coral_fan": 1.0, + "minecraft:dead_brain_coral": 1, + "minecraft:dead_brain_coral_block": 1, + "minecraft:dead_brain_coral_fan": 1, + "minecraft:dead_bubble_coral": 1, + "minecraft:dead_bubble_coral_block": 1, + "minecraft:dead_bubble_coral_fan": 1, "minecraft:dead_bush": 1, - "minecraft:dead_fire_coral": 1.0, - "minecraft:dead_fire_coral_block": 1.0, - "minecraft:dead_fire_coral_fan": 1.0, - "minecraft:dead_horn_coral": 1.0, - "minecraft:dead_horn_coral_block": 1.0, - "minecraft:dead_horn_coral_fan": 1.0, - "minecraft:dead_tube_coral": 1.0, - "minecraft:dead_tube_coral_block": 1.0, - "minecraft:dead_tube_coral_fan": 1.0, + "minecraft:dead_fire_coral": 1, + "minecraft:dead_fire_coral_block": 1, + "minecraft:dead_fire_coral_fan": 1, + "minecraft:dead_horn_coral": 1, + "minecraft:dead_horn_coral_block": 1, + "minecraft:dead_horn_coral_fan": 1, + "minecraft:dead_tube_coral": 1, + "minecraft:dead_tube_coral_block": 1, + "minecraft:dead_tube_coral_fan": 1, "minecraft:debug_stick": null, "minecraft:diamond": 8192, "minecraft:dirt_path": 1, @@ -102,9 +102,9 @@ "minecraft:farmland": null, "minecraft:feather": 48, "minecraft:fern": 1, - "minecraft:fire_coral": 1.0, - "minecraft:fire_coral_block": 1.0, - "minecraft:fire_coral_fan": 1.0, + "minecraft:fire_coral": 1, + "minecraft:fire_coral_block": 1, + "minecraft:fire_coral_fan": 1, "minecraft:firework_star": null, "minecraft:flint": 4, "minecraft:fox_spawn_egg": null, @@ -119,7 +119,6 @@ "minecraft:goat_horn": 32, "minecraft:goat_spawn_egg": null, "minecraft:gold_ingot": 2048, - "minecraft:gold_nugget": 227.556, "minecraft:grass": 1, "minecraft:gravel": 4, "minecraft:gray_dye": 8, @@ -130,9 +129,9 @@ "minecraft:heart_of_the_sea": 4096, "minecraft:hoglin_spawn_egg": null, "minecraft:honeycomb": 24, - "minecraft:horn_coral": 1.0, - "minecraft:horn_coral_block": 1.0, - "minecraft:horn_coral_fan": 1.0, + "minecraft:horn_coral": 1, + "minecraft:horn_coral_block": 1, + "minecraft:horn_coral_fan": 1, "minecraft:horse_spawn_egg": null, "minecraft:husk_spawn_egg": null, "minecraft:ice": 1, @@ -146,7 +145,6 @@ "minecraft:ink_sac": 8, "minecraft:iron_golem_spawn_egg": null, "minecraft:iron_ingot": 256, - "minecraft:iron_nugget": 28.4444, "minecraft:jigsaw": null, "minecraft:kelp": 32, "minecraft:knowledge_book": null, @@ -271,9 +269,9 @@ "minecraft:totem_of_undying": 4096, "minecraft:trader_llama_spawn_egg": null, "minecraft:tropical_fish_spawn_egg": null, - "minecraft:tube_coral": 1.0, - "minecraft:tube_coral_block": 1.0, - "minecraft:tube_coral_fan": 1.0, + "minecraft:tube_coral": 1, + "minecraft:tube_coral_block": 1, + "minecraft:tube_coral_fan": 1, "minecraft:turtle_egg": 32, "minecraft:turtle_spawn_egg": null, "minecraft:twisting_vines": 8, -- cgit v1.2.3