summaryrefslogtreecommitdiff
path: root/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'src/main')
-rw-r--r--src/main/java/lv/enes/mc/eris_alchemy/Emc.java171
-rw-r--r--src/main/java/lv/enes/mc/eris_alchemy/EmcLoader.java15
-rw-r--r--src/main/java/lv/enes/mc/eris_alchemy/ErisAlchemy.java6
-rw-r--r--src/main/java/lv/enes/mc/eris_alchemy/recipe/BannedRecipe.java40
-rw-r--r--src/main/java/lv/enes/mc/eris_alchemy/recipe/SimplifiedRecipe.java (renamed from src/main/java/lv/enes/mc/eris_alchemy/SimplifiedRecipe.java)72
-rw-r--r--src/main/java/lv/enes/mc/eris_alchemy/utils/ConsList.java103
-rw-r--r--src/main/java/lv/enes/mc/eris_alchemy/utils/IngredientProvider.java44
-rw-r--r--src/main/java/lv/enes/mc/eris_alchemy/utils/ItemUtils.java5
-rw-r--r--src/main/java/lv/enes/mc/eris_alchemy/utils/OptionalDoubleSummer.java47
-rw-r--r--src/main/java/lv/enes/mc/eris_alchemy/utils/Ref.java14
-rw-r--r--src/main/resources/data/eris_alchemy/eris_alchemy/cycle_cut/minecraft.json64
-rw-r--r--src/main/resources/data/eris_alchemy/eris_alchemy/item_emcs/minecraft.json62
12 files changed, 519 insertions, 124 deletions
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;
2 2
3import jakarta.annotation.Nullable; 3import jakarta.annotation.Nullable;
4import lv.enes.mc.eris_alchemy.ErisAlchemyRegistry.NetworkingConstants; 4import lv.enes.mc.eris_alchemy.ErisAlchemyRegistry.NetworkingConstants;
5import lv.enes.mc.eris_alchemy.utils.BufUtils; 5import lv.enes.mc.eris_alchemy.recipe.BannedRecipe;
6import lv.enes.mc.eris_alchemy.utils.ItemUtils; 6import lv.enes.mc.eris_alchemy.recipe.SimplifiedRecipe;
7import lv.enes.mc.eris_alchemy.utils.PlayerUtils; 7import lv.enes.mc.eris_alchemy.utils.*;
8import net.minecraft.client.Minecraft; 8import net.minecraft.client.Minecraft;
9import net.minecraft.core.registries.BuiltInRegistries; 9import net.minecraft.core.registries.BuiltInRegistries;
10import net.minecraft.core.registries.Registries; 10import net.minecraft.core.registries.Registries;
@@ -27,6 +27,7 @@ import org.quiltmc.qsl.networking.api.client.ClientPlayNetworking;
27 27
28import java.text.DecimalFormat; 28import java.text.DecimalFormat;
29import java.util.*; 29import java.util.*;
30import java.util.function.Supplier;
30import java.util.stream.Stream; 31import java.util.stream.Stream;
31 32
32public final class Emc { 33public final class Emc {
@@ -36,6 +37,7 @@ public final class Emc {
36 private static final Map<TagKey<Item>, OptionalDouble> ITEM_TAG_VALUES = new HashMap<>(); 37 private static final Map<TagKey<Item>, OptionalDouble> ITEM_TAG_VALUES = new HashMap<>();
37 private static final Map<TagKey<Block>, OptionalDouble> BLOCK_TAG_VALUES = new HashMap<>(); 38 private static final Map<TagKey<Block>, OptionalDouble> BLOCK_TAG_VALUES = new HashMap<>();
38 private static final List<SimplifiedRecipe> FAKE_RECIPES = new ArrayList<>(); 39 private static final List<SimplifiedRecipe> FAKE_RECIPES = new ArrayList<>();
40 private static final List<BannedRecipe> BANNED_RECIPES = new ArrayList<>();
39 41
40 private static final Map<ResourceLocation, OptionalDouble> VALUES = Collections.synchronizedMap(new HashMap<>()); 42 private static final Map<ResourceLocation, OptionalDouble> VALUES = Collections.synchronizedMap(new HashMap<>());
41 43
@@ -102,7 +104,8 @@ public final class Emc {
102 Map<ResourceLocation, OptionalDouble> itemValues, 104 Map<ResourceLocation, OptionalDouble> itemValues,
103 Map<ResourceLocation, OptionalDouble> itemTagValues, 105 Map<ResourceLocation, OptionalDouble> itemTagValues,
104 Map<ResourceLocation, OptionalDouble> blockTagValues, 106 Map<ResourceLocation, OptionalDouble> blockTagValues,
105 List<SimplifiedRecipe> fakeRecipes 107 List<SimplifiedRecipe> fakeRecipes,
108 List<BannedRecipe> bannedRecipes
106 ) { 109 ) {
107 ITEM_VALUES.clear(); 110 ITEM_VALUES.clear();
108 ITEM_VALUES.putAll(itemValues); 111 ITEM_VALUES.putAll(itemValues);
@@ -116,86 +119,69 @@ public final class Emc {
116 FAKE_RECIPES.clear(); 119 FAKE_RECIPES.clear();
117 FAKE_RECIPES.addAll(fakeRecipes); 120 FAKE_RECIPES.addAll(fakeRecipes);
118 121
122 BANNED_RECIPES.clear();
123 BANNED_RECIPES.addAll(bannedRecipes);
124
119 reinit(); 125 reinit();
120 warnOfMissingValues(); 126 warnOfMissingValues();
121 } 127 }
122 128
123 private static OptionalDouble calculateEmcForIngredient( 129 private static OptionalDouble calcEmc(
124 List<SimplifiedRecipe> recipes, 130 ResourceLocation item,
125 Set<ResourceLocation> configured, 131 Map<ResourceLocation, List<SimplifiedRecipe>> allRecipes
126 Ingredient ingredient
127 ) { 132 ) {
133 return allRecipes.getOrDefault(item, List.of())
134 .stream()
135 .map(Emc::calcEmcForRecipe)
136 .flatMapToDouble(OptionalDouble::stream)
137 .average();
138 }
139
140 private static OptionalDouble calcEmcForIngredient(Ingredient ingredient) {
128 return Arrays.stream(ingredient.getItems()) 141 return Arrays.stream(ingredient.getItems())
129 .map(stack -> configEmc(recipes, configured, ItemUtils.getId(stack.getItem())).stream() 142 .map(Emc::get)
130 .map(x -> x * stack.getCount()) 143 .flatMapToDouble(OptionalDouble::stream)
131 .findFirst())
132 .filter(OptionalDouble::isPresent)
133 .mapToDouble(OptionalDouble::getAsDouble)
134 .filter(x -> x > 0)
135 .average(); 144 .average();
136 } 145 }
137 146
138 private static OptionalDouble calculateEmcForRecipe( 147 private static OptionalDouble calcEmcForRecipe(SimplifiedRecipe recipe) {
139 List<SimplifiedRecipe> recipes, 148 if (recipe.input().isEmpty()) {
140 Set<ResourceLocation> configured,
141 SimplifiedRecipe recipe
142 ) {
143 try {
144 if (recipe.input().isEmpty()) {
145 return OptionalDouble.empty();
146 }
147
148 var inputEmc = recipe.input()
149 .stream()
150 .map(ingredient -> calculateEmcForIngredient(recipes, configured, ingredient))
151 .mapToDouble(OptionalDouble::orElseThrow)
152 .sum();
153
154 var remainderEmc = recipe.remainder()
155 .stream()
156 .map(remainder -> configEmc(recipes, configured, ItemUtils.getId(remainder)))
157 .mapToDouble(OptionalDouble::orElseThrow)
158 .sum();
159
160 if (remainderEmc > inputEmc) {
161 ErisAlchemy.LOGGER.warn("Recipe generating {} creates too much EMC out of thin air!", recipe.output());
162 return OptionalDouble.empty();
163 }
164
165 var outputDivisor = (double) recipe.output().getCount();
166
167 return OptionalDouble.of((inputEmc - remainderEmc) / outputDivisor);
168 } catch (NoSuchElementException e) {
169 return OptionalDouble.empty(); 149 return OptionalDouble.empty();
170 } 150 }
171 }
172 151
173 private static OptionalDouble configEmc( 152 var inputEmcOpt = recipe.input()
174 List<SimplifiedRecipe> recipes, 153 .stream()
175 Set<ResourceLocation> configured, 154 .map(Supplier::get)
176 ResourceLocation itemId 155 .map(Emc::calcEmcForIngredient)
177 ) { 156 .collect(new OptionalDoubleSummer());
178 var res = get(itemId); 157
179 if (res.isPresent() || configured.contains(itemId)) { 158 if (inputEmcOpt.isEmpty()) {
180 return res; 159 return OptionalDouble.empty();
181 } 160 }
182 161
183 configured.add(itemId); 162 var remainderEmcOpt = recipe.remainder()
184 var item = BuiltInRegistries.ITEM.get(itemId); 163 .stream()
185 res = recipes.stream() 164 .map(Emc::get)
186 .filter(recipe -> recipe.output().is(item)) 165 .collect(new OptionalDoubleSummer());
187 .map(recipe -> calculateEmcForRecipe(recipes, configured, recipe)) 166
188 .flatMapToDouble(OptionalDouble::stream) 167 if (remainderEmcOpt.isEmpty()) {
189 .average(); 168 return OptionalDouble.empty();
190 res.ifPresentOrElse( 169 }
191 emc -> VALUES.put(itemId, OptionalDouble.of(emc)), 170
192 () -> configured.remove(itemId) 171 var inputEmc = inputEmcOpt.getAsDouble();
193 ); 172 var remainderEmc = remainderEmcOpt.getAsDouble();
194 return res; 173 if (remainderEmc > inputEmc) {
174 ErisAlchemy.LOGGER.warn("Recipe generating {} creates too much EMC out of thin air!", recipe.output());
175 return OptionalDouble.empty();
176 }
177
178 var outputDivisor = (double) recipe.output().getCount();
179
180 return OptionalDouble.of((inputEmc - remainderEmc) / outputDivisor);
195 } 181 }
196 182
197 private static List<SimplifiedRecipe> getRecipes(@Nullable Level world) { 183 private static Stream<SimplifiedRecipe> getRecipes(@Nullable Level world) {
198 Stream<SimplifiedRecipe> recipes = FAKE_RECIPES.stream(); 184 var recipes = FAKE_RECIPES.stream();
199 185
200 if (world != null) { 186 if (world != null) {
201 recipes = Stream.concat( 187 recipes = Stream.concat(
@@ -207,7 +193,7 @@ public final class Emc {
207 ); 193 );
208 } 194 }
209 195
210 return recipes.toList(); 196 return recipes;
211 } 197 }
212 198
213 private static void reinit() { 199 private static void reinit() {
@@ -225,14 +211,55 @@ public final class Emc {
225 ); 211 );
226 212
227 ErisAlchemy.LOGGER.info("Calculating EMC values from recipes..."); 213 ErisAlchemy.LOGGER.info("Calculating EMC values from recipes...");
228 var recipes = getRecipes(overworld); 214 var recipes = new HashMap<ResourceLocation, List<SimplifiedRecipe>>();
229 var configured = new HashSet<>(VALUES.keySet()); 215 getRecipes(overworld)
230 BuiltInRegistries.ITEM.keySet().forEach(item -> configEmc(recipes, configured, item)); 216 .filter(recipe -> !recipe.hasDuplication())
217 .filter(recipe -> recipe.isAllowed(BANNED_RECIPES))
218 .forEach(recipe ->
219 recipes
220 .computeIfAbsent(ItemUtils.getId(recipe.output()), k -> new ArrayList<>())
221 .add(recipe)
222 );
223 var sortedItems = sorted(recipes);
224 sortedItems.stream()
225 .filter(id -> !VALUES.containsKey(id))
226 .forEach(id -> VALUES.put(id, calcEmc(id, recipes)));
231 ErisAlchemy.LOGGER.info("Done calculating EMC values..."); 227 ErisAlchemy.LOGGER.info("Done calculating EMC values...");
232 228
233 sync(); 229 sync();
234 } 230 }
235 231
232 private static void sortDps(
233 Set<ResourceLocation> permSorted,
234 ConsList<ResourceLocation> tmpSorted,
235 ResourceLocation item,
236 Map<ResourceLocation, List<SimplifiedRecipe>> data
237 ) {
238 if (permSorted.contains(item)) {
239 return;
240 }
241
242 var newTmpSorted = ConsList.cons(item, tmpSorted);
243
244 if (tmpSorted.contains(item)) {
245 ErisAlchemy.LOGGER.warn("Cycle in recipes detected: {}, breaking here", newTmpSorted);
246 return;
247 }
248
249 data.getOrDefault(item, List.of())
250 .stream()
251 .flatMap(SimplifiedRecipe::dependencies)
252 .distinct()
253 .forEach(dep -> sortDps(permSorted, newTmpSorted, dep, data));
254 permSorted.add(item);
255 }
256
257 private static Set<ResourceLocation> sorted(Map<ResourceLocation, List<SimplifiedRecipe>> unsorted) {
258 var res = new LinkedHashSet<ResourceLocation>();
259 unsorted.forEach((item, recipes) -> sortDps(res, ConsList.nil(), item, unsorted));
260 return res;
261 }
262
236 private static void sync() { 263 private static void sync() {
237 syncTo(PlayerUtils.all()); 264 syncTo(PlayerUtils.all());
238 } 265 }
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;
2 2
3import com.google.gson.reflect.TypeToken; 3import com.google.gson.reflect.TypeToken;
4import jakarta.annotation.Nonnull; 4import jakarta.annotation.Nonnull;
5import lv.enes.mc.eris_alchemy.recipe.BannedRecipe;
6import lv.enes.mc.eris_alchemy.recipe.SimplifiedRecipe;
5import net.minecraft.resources.ResourceLocation; 7import net.minecraft.resources.ResourceLocation;
6import net.minecraft.server.packs.resources.ResourceManager; 8import net.minecraft.server.packs.resources.ResourceManager;
7import org.quiltmc.qsl.resource.loader.api.reloader.SimpleSynchronousResourceReloader; 9import org.quiltmc.qsl.resource.loader.api.reloader.SimpleSynchronousResourceReloader;
@@ -30,7 +32,9 @@ public class EmcLoader implements SimpleSynchronousResourceReloader {
30 32
31 var fakeRecipes = loadAllFiles(manager, "fake_recipes", new ArrayList<>(), EmcLoader::loadFakeRecipes); 33 var fakeRecipes = loadAllFiles(manager, "fake_recipes", new ArrayList<>(), EmcLoader::loadFakeRecipes);
32 34
33 Emc.reloadData(itemValues, itemTagValues, blockTagValues, fakeRecipes); 35 var bannedRecipes = loadAllFiles(manager, "cycle_cut", new ArrayList<>(), EmcLoader::loadBannedRecipes);
36
37 Emc.reloadData(itemValues, itemTagValues, blockTagValues, fakeRecipes, bannedRecipes);
34 } 38 }
35 39
36 private static <T> T loadAllFiles( 40 private static <T> T loadAllFiles(
@@ -58,6 +62,15 @@ public class EmcLoader implements SimpleSynchronousResourceReloader {
58 return arg; 62 return arg;
59 } 63 }
60 64
65 private static void loadBannedRecipes(List<BannedRecipe> recipes, ResourceLocation id, InputStream is)
66 throws IOException
67 {
68 try (var reader = new InputStreamReader(is)) {
69 var json = GSON.fromJson(reader, new TypeToken<List<BannedRecipe>>(){});
70 recipes.addAll(json);
71 }
72 }
73
61 private static void loadEmcValues(Map<ResourceLocation, OptionalDouble> map, ResourceLocation id, InputStream is) 74 private static void loadEmcValues(Map<ResourceLocation, OptionalDouble> map, ResourceLocation id, InputStream is)
62 throws IOException 75 throws IOException
63 { 76 {
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;
2 2
3import com.google.gson.Gson; 3import com.google.gson.Gson;
4import com.google.gson.GsonBuilder; 4import com.google.gson.GsonBuilder;
5import lv.enes.mc.eris_alchemy.recipe.BannedRecipe;
6import lv.enes.mc.eris_alchemy.recipe.SimplifiedRecipe;
7import lv.enes.mc.eris_alchemy.utils.IngredientProvider;
5import lv.enes.mc.eris_alchemy.utils.PlayerUtils; 8import lv.enes.mc.eris_alchemy.utils.PlayerUtils;
6import net.fabricmc.fabric.api.itemgroup.v1.FabricItemGroup; 9import net.fabricmc.fabric.api.itemgroup.v1.FabricItemGroup;
7import net.minecraft.core.Registry; 10import net.minecraft.core.Registry;
@@ -11,6 +14,7 @@ import net.minecraft.resources.ResourceLocation;
11import net.minecraft.server.packs.PackType; 14import net.minecraft.server.packs.PackType;
12import net.minecraft.world.item.CreativeModeTab; 15import net.minecraft.world.item.CreativeModeTab;
13import net.minecraft.world.item.ItemStack; 16import net.minecraft.world.item.ItemStack;
17import net.minecraft.world.item.crafting.Ingredient;
14import org.quiltmc.loader.api.ModContainer; 18import org.quiltmc.loader.api.ModContainer;
15import org.quiltmc.qsl.base.api.entrypoint.ModInitializer; 19import org.quiltmc.qsl.base.api.entrypoint.ModInitializer;
16import org.quiltmc.qsl.lifecycle.api.event.ServerLifecycleEvents; 20import org.quiltmc.qsl.lifecycle.api.event.ServerLifecycleEvents;
@@ -20,6 +24,8 @@ import org.slf4j.LoggerFactory;
20 24
21public class ErisAlchemy implements ModInitializer { 25public class ErisAlchemy implements ModInitializer {
22 public static final Gson GSON = new GsonBuilder() 26 public static final Gson GSON = new GsonBuilder()
27 .registerTypeAdapter(BannedRecipe.class, new BannedRecipe.Deserializer())
28 .registerTypeAdapter(Ingredient.class, new IngredientProvider())
23 .registerTypeAdapter(ResourceLocation.class, new ResourceLocation.Serializer()) 29 .registerTypeAdapter(ResourceLocation.class, new ResourceLocation.Serializer())
24 .registerTypeAdapter(SimplifiedRecipe.class, new SimplifiedRecipe.Deserializer()) 30 .registerTypeAdapter(SimplifiedRecipe.class, new SimplifiedRecipe.Deserializer())
25 .create(); 31 .create();
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 @@
1package lv.enes.mc.eris_alchemy.recipe;
2
3import com.google.gson.JsonDeserializationContext;
4import com.google.gson.JsonDeserializer;
5import com.google.gson.JsonElement;
6import com.google.gson.JsonParseException;
7import lv.enes.mc.eris_alchemy.utils.IngredientProvider;
8import net.minecraft.world.item.crafting.Ingredient;
9
10import java.lang.reflect.Type;
11import java.util.function.Supplier;
12
13public record BannedRecipe(Supplier<Ingredient> input, Supplier<Ingredient> output) {
14 public static BannedRecipe deserialize(JsonElement el) {
15 if (el.isJsonPrimitive() && el.getAsJsonPrimitive().isString()) {
16 return deserialize(el.getAsString());
17 } else {
18 // TODO: Verbose object representation
19 throw new JsonParseException("Banned recipes should all be strings");
20 }
21 }
22
23 public static BannedRecipe deserialize(String str) {
24 var split = str.indexOf("->");
25 var input = IngredientProvider.deserialize(str.substring(0, split).trim());
26 var output = IngredientProvider.deserialize(str.substring(split + 2).trim());
27 return new BannedRecipe(input, output);
28 }
29
30 public static class Deserializer implements JsonDeserializer<BannedRecipe> {
31 @Override
32 public BannedRecipe deserialize(
33 JsonElement el,
34 Type t,
35 JsonDeserializationContext ctx
36 ) throws JsonParseException {
37 return BannedRecipe.deserialize(el);
38 }
39 }
40}
diff --git a/src/main/java/lv/enes/mc/eris_alchemy/SimplifiedRecipe.java b/src/main/java/lv/enes/mc/eris_alchemy/recipe/SimplifiedRecipe.java
index ef69b84..469ed52 100644
--- a/src/main/java/lv/enes/mc/eris_alchemy/SimplifiedRecipe.java
+++ b/src/main/java/lv/enes/mc/eris_alchemy/recipe/SimplifiedRecipe.java
@@ -1,7 +1,12 @@
1package lv.enes.mc.eris_alchemy; 1package lv.enes.mc.eris_alchemy.recipe;
2 2
3import com.google.gson.*; 3import com.google.gson.JsonDeserializationContext;
4import com.google.gson.JsonDeserializer;
5import com.google.gson.JsonElement;
6import com.google.gson.JsonParseException;
4import jakarta.annotation.Nonnull; 7import jakarta.annotation.Nonnull;
8import lv.enes.mc.eris_alchemy.utils.IngredientProvider;
9import lv.enes.mc.eris_alchemy.utils.ItemUtils;
5import lv.enes.mc.eris_alchemy.utils.RecipeUtils; 10import lv.enes.mc.eris_alchemy.utils.RecipeUtils;
6import net.minecraft.core.RegistryAccess; 11import net.minecraft.core.RegistryAccess;
7import net.minecraft.core.registries.BuiltInRegistries; 12import net.minecraft.core.registries.BuiltInRegistries;
@@ -12,18 +17,58 @@ import net.minecraft.world.item.crafting.Recipe;
12import net.minecraft.world.item.crafting.ShapedRecipe; 17import net.minecraft.world.item.crafting.ShapedRecipe;
13 18
14import java.lang.reflect.Type; 19import java.lang.reflect.Type;
20import java.util.Arrays;
21import java.util.Collection;
15import java.util.List; 22import java.util.List;
23import java.util.Objects;
24import java.util.function.Supplier;
25import java.util.stream.Stream;
16 26
17public record SimplifiedRecipe(ItemStack output, List<ItemStack> remainder, List<Ingredient> input) { 27public record SimplifiedRecipe(ItemStack output, List<ItemStack> remainder, List<Supplier<Ingredient>> input) {
18 public SimplifiedRecipe(Recipe<?> recipe, RegistryAccess registryAccess) { 28 public SimplifiedRecipe(Recipe<?> recipe, RegistryAccess registryAccess) {
19 this( 29 this(
20 RecipeUtils.getOutput(recipe, registryAccess), 30 RecipeUtils.getOutput(recipe, registryAccess),
21 List.of(), // TODO: 31 List.of(), // TODO:
22 RecipeUtils.getIngredients(recipe).stream().filter(ingredient -> !ingredient.isEmpty()).toList() 32 RecipeUtils.getIngredients(recipe)
33 .stream()
34 .filter(ingredient -> !ingredient.isEmpty())
35 .map(x -> (Supplier<Ingredient>) () -> x)
36 .toList()
23 ); 37 );
24 } 38 }
25 39
26 static class Deserializer implements JsonDeserializer<SimplifiedRecipe> { 40 public Stream<ResourceLocation> dependencies() {
41 return Stream.concat(
42 input.stream().map(Supplier::get).map(Ingredient::getItems).flatMap(Arrays::stream),
43 remainder.stream()
44 ).map(ItemUtils::getId);
45 }
46
47 public boolean hasDuplication() {
48 var outputId = ItemUtils.getId(output);
49 var outputsInRemainder = remainder.stream()
50 .map(ItemUtils::getId)
51 .anyMatch(id -> Objects.equals(id, outputId));
52 if (outputsInRemainder) {
53 return true;
54 }
55
56 return input.stream().anyMatch(ingredient -> ingredient.get().test(output));
57 }
58
59 public boolean isAllowed(BannedRecipe ban) {
60 if (!ban.output().get().test(output())) {
61 return true;
62 }
63
64 return dependencies().noneMatch(dep -> ban.input().get().test(ItemUtils.get(dep).getDefaultInstance()));
65 }
66
67 public boolean isAllowed(Collection<BannedRecipe> bans) {
68 return bans.stream().allMatch(this::isAllowed);
69 }
70
71 public static class Deserializer implements JsonDeserializer<SimplifiedRecipe> {
27 @Nonnull 72 @Nonnull
28 @Override 73 @Override
29 public SimplifiedRecipe deserialize( 74 public SimplifiedRecipe deserialize(
@@ -46,23 +91,12 @@ public record SimplifiedRecipe(ItemStack output, List<ItemStack> remainder, List
46 return new SimplifiedRecipe(output, remainder, input); 91 return new SimplifiedRecipe(output, remainder, input);
47 } 92 }
48 93
49 private List<Ingredient> parseInputs(JsonElement el) { 94 private List<Supplier<Ingredient>> parseInputs(JsonElement el) {
50 if (el.isJsonArray()) { 95 if (el.isJsonArray()) {
51 return el.getAsJsonArray().asList().stream().map(this::parseInput).map(Ingredient::of).toList(); 96 return el.getAsJsonArray().asList().stream().map(IngredientProvider::deserialize).toList();
52 } 97 }
53 98
54 return List.of(Ingredient.of(parseInput(el))); 99 return List.of(IngredientProvider.deserialize(el));
55 }
56
57 private ItemStack parseInput(JsonElement el) {
58 if (el.isJsonObject()) {
59 return ShapedRecipe.itemStackFromJson(el.getAsJsonObject());
60 } else if (el.isJsonPrimitive() && el.getAsJsonPrimitive().isString()) {
61 var id = new ResourceLocation(el.getAsString());
62 return BuiltInRegistries.ITEM.get(id).getDefaultInstance();
63 } else {
64 throw new JsonParseException("Every recipe input should be an object or a string");
65 }
66 } 100 }
67 101
68 private ItemStack parseOutputOrRemainder(JsonElement el) { 102 private ItemStack parseOutputOrRemainder(JsonElement 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 @@
1package lv.enes.mc.eris_alchemy.utils;
2
3import jakarta.annotation.Nonnull;
4
5import java.util.Objects;
6import java.util.Optional;
7import java.util.function.Supplier;
8import java.util.stream.Stream;
9
10public sealed abstract class ConsList<E> {
11 public static <E> ConsList<E> cons(E car, ConsList<E> cdr) {
12 return new Cons<>(car, cdr);
13 }
14
15 public static <E> ConsList<E> nil() {
16 return Nil.of();
17 }
18
19 @Nonnull
20 public abstract Optional<E> car();
21
22 @Nonnull
23 public abstract ConsList<E> cdr();
24
25 public boolean contains(E elem) {
26 return stream().anyMatch(x -> Objects.equals(x, elem));
27 }
28
29 public Stream<E> stream() {
30 var car = car();
31 if (car.isEmpty()) {
32 return Stream.empty();
33 }
34
35 return Stream.<Supplier<Stream<E>>>of(
36 () -> Stream.of(car.get()),
37 () -> cdr().stream()
38 )
39 .flatMap(Supplier::get);
40 }
41
42 @Override
43 public String toString() {
44 if (car().isEmpty()) {
45 return "[]";
46 }
47
48 var sb = new StringBuilder("[");
49 sb.append(car().get());
50 stream().skip(1)
51 .forEach(x -> {
52 sb.append(" -> ");
53 sb.append(x);
54 });
55 sb.append("]");
56 return sb.toString();
57 }
58
59 private static final class Cons<E> extends ConsList<E> {
60 private final E car;
61 private final ConsList<E> cdr;
62
63 public Cons(E car, ConsList<E> cdr) {
64 this.car = car;
65 this.cdr = cdr;
66 }
67
68 @Nonnull
69 @Override
70 public Optional<E> car() {
71 return Optional.of(car);
72 }
73
74 @Nonnull
75 @Override
76 public ConsList<E> cdr() {
77 return cdr;
78 }
79 }
80
81 private static final class Nil<E> extends ConsList<E> {
82 private static final Nil<?> INSTANCE = new Nil<>();
83
84 @SuppressWarnings("unchecked")
85 public static <E> Nil<E> of() {
86 return (Nil<E>)INSTANCE;
87 }
88
89 private Nil(){}
90
91 @Nonnull
92 @Override
93 public Optional<E> car() {
94 return Optional.empty();
95 }
96
97 @Nonnull
98 @Override
99 public ConsList<E> cdr() {
100 return Nil.of();
101 }
102 }
103}
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 @@
1package lv.enes.mc.eris_alchemy.utils;
2
3import com.google.gson.JsonDeserializationContext;
4import com.google.gson.JsonDeserializer;
5import com.google.gson.JsonElement;
6import com.google.gson.JsonParseException;
7import net.minecraft.core.registries.Registries;
8import net.minecraft.resources.ResourceLocation;
9import net.minecraft.tags.TagKey;
10import net.minecraft.world.item.crafting.Ingredient;
11import net.minecraft.world.item.crafting.ShapedRecipe;
12
13import java.lang.reflect.Type;
14import java.util.function.Supplier;
15
16public class IngredientProvider implements JsonDeserializer<Supplier<Ingredient>> {
17 public static Supplier<Ingredient> deserialize(JsonElement el) {
18 if (el.isJsonObject()) {
19 return () -> Ingredient.of(ShapedRecipe.itemStackFromJson(el.getAsJsonObject()));
20 } else if (el.isJsonPrimitive() && el.getAsJsonPrimitive().isString()) {
21 return deserialize(el.getAsString().strip());
22 } else {
23 throw new JsonParseException("Every ingredient should be an object or a string");
24 }
25 }
26
27 public static Supplier<Ingredient> deserialize(String str) {
28 if (str.startsWith("#")) {
29 var tag = TagKey.create(Registries.ITEM, new ResourceLocation(str.substring(1).strip()));
30 return () -> Ingredient.of(tag);
31 } else {
32 return () -> Ingredient.of(ItemUtils.get(new ResourceLocation(str)));
33 }
34 }
35
36 @Override
37 public Supplier<Ingredient> deserialize(
38 JsonElement el,
39 Type t,
40 JsonDeserializationContext c
41 ) {
42 return IngredientProvider.deserialize(el);
43 }
44}
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;
3import net.minecraft.core.Holder; 3import net.minecraft.core.Holder;
4import net.minecraft.core.registries.BuiltInRegistries; 4import net.minecraft.core.registries.BuiltInRegistries;
5import net.minecraft.resources.ResourceLocation; 5import net.minecraft.resources.ResourceLocation;
6import net.minecraft.world.item.Item;
6import net.minecraft.world.item.ItemStack; 7import net.minecraft.world.item.ItemStack;
7import net.minecraft.world.level.ItemLike; 8import net.minecraft.world.level.ItemLike;
8 9
9public final class ItemUtils { 10public final class ItemUtils {
11 public static Item get(ResourceLocation id) {
12 return BuiltInRegistries.ITEM.get(id);
13 }
14
10 public static <I extends ItemLike> ResourceLocation getId(Holder<I> holder) { 15 public static <I extends ItemLike> ResourceLocation getId(Holder<I> holder) {
11 return getId(holder.value()); 16 return getId(holder.value());
12 } 17 }
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 @@
1package lv.enes.mc.eris_alchemy.utils;
2
3import java.util.OptionalDouble;
4import java.util.Set;
5import java.util.function.BiConsumer;
6import java.util.function.BinaryOperator;
7import java.util.function.Function;
8import java.util.function.Supplier;
9import java.util.stream.Collector;
10
11public class OptionalDoubleSummer implements Collector<OptionalDouble, Ref<OptionalDouble>, OptionalDouble> {
12 @Override
13 public Supplier<Ref<OptionalDouble>> supplier() {
14 return () -> new Ref<>(OptionalDouble.of(0));
15 }
16
17 @Override
18 public BiConsumer<Ref<OptionalDouble>, OptionalDouble> accumulator() {
19 return (ref, od) -> {
20 if (ref.getValue().isPresent()) {
21 if (od.isPresent()) {
22 ref.setValue(OptionalDouble.of(ref.getValue().getAsDouble() + od.getAsDouble()));
23 } else {
24 ref.setValue(OptionalDouble.empty());
25 }
26 }
27 };
28 }
29
30 @Override
31 public BinaryOperator<Ref<OptionalDouble>> combiner() {
32 return (ref1, ref2) -> {
33 accumulator().accept(ref1, ref2.getValue());
34 return ref1;
35 };
36 }
37
38 @Override
39 public Function<Ref<OptionalDouble>, OptionalDouble> finisher() {
40 return Ref::getValue;
41 }
42
43 @Override
44 public Set<Characteristics> characteristics() {
45 return Set.of(Characteristics.UNORDERED);
46 }
47}
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 @@
1package lv.enes.mc.eris_alchemy.utils;
2
3import lombok.Getter;
4import lombok.Setter;
5
6@Setter
7@Getter
8public class Ref<T> {
9 private T value;
10
11 public Ref(T value) {
12 this.value = value;
13 }
14}
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 @@
1[
2 "#minecraft:beds -> #minecraft:beds",
3 "#minecraft:wool -> #minecraft:wool",
4 "#minecraft:wool_carpets -> #minecraft:wool_carpets",
5
6 "minecraft:bone_block -> minecraft:bone_meal",
7
8 "minecraft:chiseled_quartz_block -> minecraft:quartz_slab",
9 "minecraft:chiseled_red_sandstone -> minecraft:red_sandstone_slab",
10 "minecraft:chiseled_sandstone -> minecraft:sandstone_slab",
11 "minecraft:coal_block -> minecraft:coal",
12 "minecraft:copper_block -> minecraft:copper_ingot",
13
14 "minecraft:diamond_block -> minecraft:diamond",
15 "minecraft:disc_fragment_5 -> minecraft:music_disc_5",
16 "minecraft:dried_kelp_block -> minecraft:dried_kelp",
17
18 "minecraft:emerald_block -> minecraft:emerald",
19
20 "minecraft:filled_map -> minecraft:map",
21
22 "minecraft:gold_block -> minecraft:gold_ingot",
23 "minecraft:gold_nugget -> minecraft:gold_ingot",
24 "minecraft:golden_helmet -> minecraft:gold_nugget",
25 "minecraft:golden_chestplate -> minecraft:gold_nugget",
26 "minecraft:golden_leggings -> minecraft:gold_nugget",
27 "minecraft:golden_boots -> minecraft:gold_nugget",
28 "minecraft:golden_sword -> minecraft:gold_nugget",
29 "minecraft:golden_pickaxe -> minecraft:gold_nugget",
30 "minecraft:golden_axe -> minecraft:gold_nugget",
31 "minecraft:golden_shovel -> minecraft:gold_nugget",
32 "minecraft:golden_hoe -> minecraft:gold_nugget",
33
34 "minecraft:hay_block -> minecraft:wheat",
35 "minecraft:honey_block -> minecraft:honey_bottle",
36
37 "minecraft:iron_block -> minecraft:iron_ingot",
38 "minecraft:iron_nugget -> minecraft:iron_ingot",
39 "minecraft:iron_helmet -> minecraft:iron_nugget",
40 "minecraft:iron_chestplate -> minecraft:iron_nugget",
41 "minecraft:iron_leggings -> minecraft:iron_nugget",
42 "minecraft:iron_boots -> minecraft:iron_nugget",
43 "minecraft:iron_sword -> minecraft:iron_nugget",
44 "minecraft:iron_pickaxe -> minecraft:iron_nugget",
45 "minecraft:iron_axe -> minecraft:iron_nugget",
46 "minecraft:iron_shovel -> minecraft:iron_nugget",
47 "minecraft:iron_hoe -> minecraft:iron_nugget",
48
49 "minecraft:lapis_block -> minecraft:lapis_lazuli",
50
51 "minecraft:netherite_block -> minecraft:netherite_ingot",
52
53 "minecraft:redstone_block -> minecraft:redstone",
54
55 "minecraft:slime_block -> minecraft:slime_ball",
56
57 "minecraft:purpur_pillar -> minecraft:purpur_slab",
58
59 "minecraft:raw_copper_block -> minecraft:raw_copper",
60 "minecraft:raw_gold_block -> minecraft:raw_gold",
61 "minecraft:raw_iron_block -> minecraft:raw_iron",
62
63 "minecraft:waxed_copper_block -> minecraft:copper_ingot"
64] \ 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 @@
20 "minecraft:blaze_spawn_egg": null, 20 "minecraft:blaze_spawn_egg": null,
21 "minecraft:blue_dye": 8, 21 "minecraft:blue_dye": 8,
22 "minecraft:bone": 96, 22 "minecraft:bone": 96,
23 "minecraft:brain_coral": 1.0, 23 "minecraft:brain_coral": 1,
24 "minecraft:brain_coral_block": 1.0, 24 "minecraft:brain_coral_block": 1,
25 "minecraft:brain_coral_fan": 1.0, 25 "minecraft:brain_coral_fan": 1,
26 "minecraft:brown_dye": 8, 26 "minecraft:brown_dye": 8,
27 "minecraft:brown_mushroom": 32, 27 "minecraft:brown_mushroom": 32,
28 "minecraft:brown_mushroom_block": null, 28 "minecraft:brown_mushroom_block": null,
29 "minecraft:bubble_coral": 1.0, 29 "minecraft:bubble_coral": 1,
30 "minecraft:bubble_coral_block": 1.0, 30 "minecraft:bubble_coral_block": 1,
31 "minecraft:bubble_coral_fan": 1.0, 31 "minecraft:bubble_coral_fan": 1,
32 "minecraft:budding_amethyst": null, 32 "minecraft:budding_amethyst": null,
33 "minecraft:bundle": null, 33 "minecraft:bundle": null,
34 "minecraft:cactus": 8, 34 "minecraft:cactus": 8,
@@ -60,22 +60,22 @@
60 "minecraft:crimson_roots": 1, 60 "minecraft:crimson_roots": 1,
61 "minecraft:crying_obsidian": 64, 61 "minecraft:crying_obsidian": 64,
62 "minecraft:cyan_dye": 8, 62 "minecraft:cyan_dye": 8,
63 "minecraft:dead_brain_coral": 1.0, 63 "minecraft:dead_brain_coral": 1,
64 "minecraft:dead_brain_coral_block": 1.0, 64 "minecraft:dead_brain_coral_block": 1,
65 "minecraft:dead_brain_coral_fan": 1.0, 65 "minecraft:dead_brain_coral_fan": 1,
66 "minecraft:dead_bubble_coral": 1.0, 66 "minecraft:dead_bubble_coral": 1,
67 "minecraft:dead_bubble_coral_block": 1.0, 67 "minecraft:dead_bubble_coral_block": 1,
68 "minecraft:dead_bubble_coral_fan": 1.0, 68 "minecraft:dead_bubble_coral_fan": 1,
69 "minecraft:dead_bush": 1, 69 "minecraft:dead_bush": 1,
70 "minecraft:dead_fire_coral": 1.0, 70 "minecraft:dead_fire_coral": 1,
71 "minecraft:dead_fire_coral_block": 1.0, 71 "minecraft:dead_fire_coral_block": 1,
72 "minecraft:dead_fire_coral_fan": 1.0, 72 "minecraft:dead_fire_coral_fan": 1,
73 "minecraft:dead_horn_coral": 1.0, 73 "minecraft:dead_horn_coral": 1,
74 "minecraft:dead_horn_coral_block": 1.0, 74 "minecraft:dead_horn_coral_block": 1,
75 "minecraft:dead_horn_coral_fan": 1.0, 75 "minecraft:dead_horn_coral_fan": 1,
76 "minecraft:dead_tube_coral": 1.0, 76 "minecraft:dead_tube_coral": 1,
77 "minecraft:dead_tube_coral_block": 1.0, 77 "minecraft:dead_tube_coral_block": 1,
78 "minecraft:dead_tube_coral_fan": 1.0, 78 "minecraft:dead_tube_coral_fan": 1,
79 "minecraft:debug_stick": null, 79 "minecraft:debug_stick": null,
80 "minecraft:diamond": 8192, 80 "minecraft:diamond": 8192,
81 "minecraft:dirt_path": 1, 81 "minecraft:dirt_path": 1,
@@ -102,9 +102,9 @@
102 "minecraft:farmland": null, 102 "minecraft:farmland": null,
103 "minecraft:feather": 48, 103 "minecraft:feather": 48,
104 "minecraft:fern": 1, 104 "minecraft:fern": 1,
105 "minecraft:fire_coral": 1.0, 105 "minecraft:fire_coral": 1,
106 "minecraft:fire_coral_block": 1.0, 106 "minecraft:fire_coral_block": 1,
107 "minecraft:fire_coral_fan": 1.0, 107 "minecraft:fire_coral_fan": 1,
108 "minecraft:firework_star": null, 108 "minecraft:firework_star": null,
109 "minecraft:flint": 4, 109 "minecraft:flint": 4,
110 "minecraft:fox_spawn_egg": null, 110 "minecraft:fox_spawn_egg": null,
@@ -119,7 +119,6 @@
119 "minecraft:goat_horn": 32, 119 "minecraft:goat_horn": 32,
120 "minecraft:goat_spawn_egg": null, 120 "minecraft:goat_spawn_egg": null,
121 "minecraft:gold_ingot": 2048, 121 "minecraft:gold_ingot": 2048,
122 "minecraft:gold_nugget": 227.556,
123 "minecraft:grass": 1, 122 "minecraft:grass": 1,
124 "minecraft:gravel": 4, 123 "minecraft:gravel": 4,
125 "minecraft:gray_dye": 8, 124 "minecraft:gray_dye": 8,
@@ -130,9 +129,9 @@
130 "minecraft:heart_of_the_sea": 4096, 129 "minecraft:heart_of_the_sea": 4096,
131 "minecraft:hoglin_spawn_egg": null, 130 "minecraft:hoglin_spawn_egg": null,
132 "minecraft:honeycomb": 24, 131 "minecraft:honeycomb": 24,
133 "minecraft:horn_coral": 1.0, 132 "minecraft:horn_coral": 1,
134 "minecraft:horn_coral_block": 1.0, 133 "minecraft:horn_coral_block": 1,
135 "minecraft:horn_coral_fan": 1.0, 134 "minecraft:horn_coral_fan": 1,
136 "minecraft:horse_spawn_egg": null, 135 "minecraft:horse_spawn_egg": null,
137 "minecraft:husk_spawn_egg": null, 136 "minecraft:husk_spawn_egg": null,
138 "minecraft:ice": 1, 137 "minecraft:ice": 1,
@@ -146,7 +145,6 @@
146 "minecraft:ink_sac": 8, 145 "minecraft:ink_sac": 8,
147 "minecraft:iron_golem_spawn_egg": null, 146 "minecraft:iron_golem_spawn_egg": null,
148 "minecraft:iron_ingot": 256, 147 "minecraft:iron_ingot": 256,
149 "minecraft:iron_nugget": 28.4444,
150 "minecraft:jigsaw": null, 148 "minecraft:jigsaw": null,
151 "minecraft:kelp": 32, 149 "minecraft:kelp": 32,
152 "minecraft:knowledge_book": null, 150 "minecraft:knowledge_book": null,
@@ -271,9 +269,9 @@
271 "minecraft:totem_of_undying": 4096, 269 "minecraft:totem_of_undying": 4096,
272 "minecraft:trader_llama_spawn_egg": null, 270 "minecraft:trader_llama_spawn_egg": null,
273 "minecraft:tropical_fish_spawn_egg": null, 271 "minecraft:tropical_fish_spawn_egg": null,
274 "minecraft:tube_coral": 1.0, 272 "minecraft:tube_coral": 1,
275 "minecraft:tube_coral_block": 1.0, 273 "minecraft:tube_coral_block": 1,
276 "minecraft:tube_coral_fan": 1.0, 274 "minecraft:tube_coral_fan": 1,
277 "minecraft:turtle_egg": 32, 275 "minecraft:turtle_egg": 32,
278 "minecraft:turtle_spawn_egg": null, 276 "minecraft:turtle_spawn_egg": null,
279 "minecraft:twisting_vines": 8, 277 "minecraft:twisting_vines": 8,