package lv.enes.mc.eris_alchemy; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; import lv.enes.mc.eris_alchemy.utils.ItemUtils; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.core.registries.Registries; import net.minecraft.resources.ResourceLocation; import net.minecraft.tags.TagKey; import net.minecraft.world.item.BlockItem; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.crafting.Ingredient; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Block; import java.text.DecimalFormat; import java.util.*; import java.util.stream.Stream; public class Emc { private static final Map ITEM_VALUES = new HashMap<>(); 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 Map instances = Collections.synchronizedMap(new WeakHashMap<>()); private static final DecimalFormat formatter = new DecimalFormat("0"); static { formatter.setMaximumFractionDigits(1); } public static Emc getInstance(@Nonnull Level world) { if (instances.containsKey(world)) { return instances.get(world); } var instance = new Emc(world); instances.put(world, instance); return instance; } public static String formatEmc(double value) { return formatter.format(value); } public static void reloadData( Map itemValues, Map itemTagValues, Map blockTagValues, List fakeRecipes ) { ITEM_VALUES.clear(); ITEM_VALUES.putAll(itemValues); ITEM_TAG_VALUES.clear(); itemTagValues.forEach((id, value) -> ITEM_TAG_VALUES.put(TagKey.create(Registries.ITEM, id), value)); BLOCK_TAG_VALUES.clear(); blockTagValues.forEach((id, value) -> BLOCK_TAG_VALUES.put(TagKey.create(Registries.BLOCK, id), value)); FAKE_RECIPES.clear(); FAKE_RECIPES.addAll(fakeRecipes); instances.clear(); } private final Map data; private Emc(@Nonnull Level world) { data = Collections.synchronizedMap(new HashMap<>(ITEM_VALUES)); ITEM_TAG_VALUES.forEach( (tag, emcValue) -> BuiltInRegistries.ITEM .getTagOrEmpty(tag) .forEach(holder -> data.putIfAbsent(ItemUtils.getId(holder), emcValue)) ); BLOCK_TAG_VALUES.forEach( (tag, emcValue) -> BuiltInRegistries.BLOCK .getTagOrEmpty(tag) .forEach(holder -> data.putIfAbsent(ItemUtils.getId(holder), emcValue)) ); ErisAlchemy.LOGGER.info("Calculating EMC values..."); var recipes = getRecipes(world); var configured = new HashSet<>(data.keySet()); BuiltInRegistries.ITEM.keySet().forEach(item -> { configEmc(recipes, configured, item); if (!data.containsKey(item)) { ErisAlchemy.LOGGER.warn("No EMC value for '{}' known", item); } }); } public OptionalDouble get(ItemStack stack) { if (stack.isEmpty()) { return OptionalDouble.empty(); } var item = stack.getItem(); var itemId = ItemUtils.getId(item); return get(itemId) .stream() .map(value -> { EmcStorage storage = null; if (item instanceof EmcStorage emcStorage) { storage = emcStorage; } else if (item instanceof BlockItem blockItem) { if (blockItem.getBlock() instanceof EmcStorage emcStorage) { storage = emcStorage; } } if (storage != null) { return value + storage.getStoredEmc(stack); } return value; }) .findFirst(); } public OptionalDouble get(ResourceLocation itemId) { return data.getOrDefault(itemId, OptionalDouble.empty()); } private List getRecipes(@Nullable Level world) { Stream recipes = FAKE_RECIPES.stream(); if (world != null) { recipes = Stream.concat( recipes, world.getRecipeManager() .getRecipes() .stream() .map(recipe -> new SimplifiedRecipe(recipe, world.registryAccess())) ); } return recipes.toList(); } private OptionalDouble configEmc( List recipes, Set configured, ResourceLocation itemId ) { var res = get(itemId); if (res.isPresent() || configured.contains(itemId)) { return res; } 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 -> data.put(itemId, OptionalDouble.of(emc)), () -> configured.remove(itemId) ); return res; } private 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) { return OptionalDouble.empty(); } } private OptionalDouble calculateEmcForIngredient( List recipes, Set configured, 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) .average(); } }