summaryrefslogtreecommitdiff
path: root/src/main/java/lv/enes/mc/eris_alchemy/Emc.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/lv/enes/mc/eris_alchemy/Emc.java')
-rw-r--r--src/main/java/lv/enes/mc/eris_alchemy/Emc.java214
1 files changed, 214 insertions, 0 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
new file mode 100644
index 0000000..fdc7c0a
--- /dev/null
+++ b/src/main/java/lv/enes/mc/eris_alchemy/Emc.java
@@ -0,0 +1,214 @@
1package lv.enes.mc.eris_alchemy;
2
3import jakarta.annotation.Nonnull;
4import jakarta.annotation.Nullable;
5import lv.enes.mc.eris_alchemy.utils.ItemUtils;
6import net.minecraft.core.registries.BuiltInRegistries;
7import net.minecraft.core.registries.Registries;
8import net.minecraft.resources.ResourceLocation;
9import net.minecraft.tags.TagKey;
10import net.minecraft.world.item.BlockItem;
11import net.minecraft.world.item.Item;
12import net.minecraft.world.item.ItemStack;
13import net.minecraft.world.item.crafting.Ingredient;
14import net.minecraft.world.level.Level;
15import net.minecraft.world.level.block.Block;
16
17import java.text.DecimalFormat;
18import java.util.*;
19import java.util.stream.Stream;
20
21public class Emc {
22 private static final Map<ResourceLocation, OptionalDouble> ITEM_VALUES = new HashMap<>();
23 private static final Map<TagKey<Item>, OptionalDouble> ITEM_TAG_VALUES = new HashMap<>();
24 private static final Map<TagKey<Block>, OptionalDouble> BLOCK_TAG_VALUES = new HashMap<>();
25 private static final List<SimplifiedRecipe> FAKE_RECIPES = new ArrayList<>();
26
27 private static final Map<Level, Emc> instances = Collections.synchronizedMap(new WeakHashMap<>());
28
29 private static final DecimalFormat formatter = new DecimalFormat("0");
30 static {
31 formatter.setMaximumFractionDigits(1);
32 }
33
34 public static Emc getInstance(@Nonnull Level world) {
35 if (instances.containsKey(world)) {
36 return instances.get(world);
37 }
38
39 var instance = new Emc(world);
40 instances.put(world, instance);
41 return instance;
42 }
43
44 public static String formatEmc(double value) {
45 return formatter.format(value);
46 }
47
48 public static void reloadData(
49 Map<ResourceLocation, OptionalDouble> itemValues,
50 Map<ResourceLocation, OptionalDouble> itemTagValues,
51 Map<ResourceLocation, OptionalDouble> blockTagValues,
52 List<SimplifiedRecipe> fakeRecipes
53 ) {
54 ITEM_VALUES.clear();
55 ITEM_VALUES.putAll(itemValues);
56
57 ITEM_TAG_VALUES.clear();
58 itemTagValues.forEach((id, value) -> ITEM_TAG_VALUES.put(TagKey.create(Registries.ITEM, id), value));
59
60 BLOCK_TAG_VALUES.clear();
61 blockTagValues.forEach((id, value) -> BLOCK_TAG_VALUES.put(TagKey.create(Registries.BLOCK, id), value));
62
63 FAKE_RECIPES.clear();
64 FAKE_RECIPES.addAll(fakeRecipes);
65
66 instances.clear();
67 }
68
69 private final Map<ResourceLocation, OptionalDouble> data;
70
71 private Emc(@Nonnull Level world) {
72 data = Collections.synchronizedMap(new HashMap<>(ITEM_VALUES));
73 ITEM_TAG_VALUES.forEach(
74 (tag, emcValue) -> BuiltInRegistries.ITEM
75 .getTagOrEmpty(tag)
76 .forEach(holder -> data.putIfAbsent(ItemUtils.getId(holder), emcValue))
77 );
78 BLOCK_TAG_VALUES.forEach(
79 (tag, emcValue) -> BuiltInRegistries.BLOCK
80 .getTagOrEmpty(tag)
81 .forEach(holder -> data.putIfAbsent(ItemUtils.getId(holder), emcValue))
82 );
83
84 ErisAlchemy.LOGGER.info("Calculating EMC values...");
85 var recipes = getRecipes(world);
86 var configured = new HashSet<>(data.keySet());
87 BuiltInRegistries.ITEM.keySet().forEach(item -> {
88 configEmc(recipes, configured, item);
89 if (!data.containsKey(item)) {
90 ErisAlchemy.LOGGER.warn("No EMC value for '{}' known", item);
91 }
92 });
93 }
94
95 public OptionalDouble get(ItemStack stack) {
96 if (stack.isEmpty()) {
97 return OptionalDouble.empty();
98 }
99
100 var item = stack.getItem();
101 var itemId = ItemUtils.getId(item);
102 return get(itemId)
103 .stream()
104 .map(value -> {
105 EmcStorage storage = null;
106 if (item instanceof EmcStorage emcStorage) {
107 storage = emcStorage;
108 } else if (item instanceof BlockItem blockItem) {
109 if (blockItem.getBlock() instanceof EmcStorage emcStorage) {
110 storage = emcStorage;
111 }
112 }
113 if (storage != null) {
114 return value + storage.getStoredEmc(stack);
115 }
116 return value;
117 })
118 .findFirst();
119 }
120
121 public OptionalDouble get(ResourceLocation itemId) {
122 return data.getOrDefault(itemId, OptionalDouble.empty());
123 }
124
125 private List<SimplifiedRecipe> getRecipes(@Nullable Level world) {
126 Stream<SimplifiedRecipe> recipes = FAKE_RECIPES.stream();
127
128 if (world != null) {
129 recipes = Stream.concat(
130 recipes,
131 world.getRecipeManager()
132 .getRecipes()
133 .stream()
134 .map(recipe -> new SimplifiedRecipe(recipe, world.registryAccess()))
135 );
136 }
137
138 return recipes.toList();
139 }
140
141 private OptionalDouble configEmc(
142 List<SimplifiedRecipe> recipes,
143 Set<ResourceLocation> configured,
144 ResourceLocation itemId
145 ) {
146 var res = get(itemId);
147 if (res.isPresent() || configured.contains(itemId)) {
148 return res;
149 }
150
151 configured.add(itemId);
152 var item = BuiltInRegistries.ITEM.get(itemId);
153 res = recipes.stream()
154 .filter(recipe -> recipe.output().is(item))
155 .map(recipe -> calculateEmcForRecipe(recipes, configured, recipe))
156 .flatMapToDouble(OptionalDouble::stream)
157 .average();
158 res.ifPresentOrElse(
159 emc -> data.put(itemId, OptionalDouble.of(emc)),
160 () -> configured.remove(itemId)
161 );
162 return res;
163 }
164
165 private OptionalDouble calculateEmcForRecipe(
166 List<SimplifiedRecipe> recipes,
167 Set<ResourceLocation> configured,
168 SimplifiedRecipe recipe
169 ) {
170 try {
171 if (recipe.input().isEmpty()) {
172 return OptionalDouble.empty();
173 }
174
175 var inputEmc = recipe.input()
176 .stream()
177 .map(ingredient -> calculateEmcForIngredient(recipes, configured, ingredient))
178 .mapToDouble(OptionalDouble::orElseThrow)
179 .sum();
180
181 var remainderEmc = recipe.remainder()
182 .stream()
183 .map(remainder -> configEmc(recipes, configured, ItemUtils.getId(remainder)))
184 .mapToDouble(OptionalDouble::orElseThrow)
185 .sum();
186
187 if (remainderEmc > inputEmc) {
188 ErisAlchemy.LOGGER.warn("Recipe generating {} creates too much EMC out of thin air!", recipe.output());
189 return OptionalDouble.empty();
190 }
191
192 var outputDivisor = (double) recipe.output().getCount();
193
194 return OptionalDouble.of((inputEmc - remainderEmc) / outputDivisor);
195 } catch (NoSuchElementException e) {
196 return OptionalDouble.empty();
197 }
198 }
199
200 private OptionalDouble calculateEmcForIngredient(
201 List<SimplifiedRecipe> recipes,
202 Set<ResourceLocation> configured,
203 Ingredient ingredient
204 ) {
205 return Arrays.stream(ingredient.getItems())
206 .map(stack -> configEmc(recipes, configured, ItemUtils.getId(stack.getItem())).stream()
207 .map(x -> x * stack.getCount())
208 .findFirst())
209 .filter(OptionalDouble::isPresent)
210 .mapToDouble(OptionalDouble::getAsDouble)
211 .filter(x -> x > 0)
212 .average();
213 }
214}