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 net.minecraft.client.Minecraft; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.core.registries.Registries; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; 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 org.quiltmc.loader.api.minecraft.ClientOnly; import org.quiltmc.qsl.networking.api.PacketByteBufs; import org.quiltmc.qsl.networking.api.ServerPlayNetworking; import org.quiltmc.qsl.networking.api.client.ClientPlayNetworking; import java.text.DecimalFormat; import java.util.*; import java.util.stream.Stream; public final class Emc { private 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 VALUES = Collections.synchronizedMap(new HashMap<>()); private static final DecimalFormat FORMATTER = new DecimalFormat("0"); static { FORMATTER.setMaximumFractionDigits(1); } private static ServerLevel overworld = null; public static String formatEmc(double value) { return FORMATTER.format(value); } public static 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 static OptionalDouble get(ResourceLocation itemId) { return VALUES.getOrDefault(itemId, OptionalDouble.empty()); } @ClientOnly public static void initClient(Minecraft ignoredClient) { ClientPlayNetworking.registerGlobalReceiver( NetworkingConstants.UPDATE_EMCS, (client1, handler, buf, responseSender) -> { var map = buf.readMap(FriendlyByteBuf::readResourceLocation, BufUtils::readOptionalDouble); VALUES.clear(); VALUES.putAll(map); } ); } public static void initServer(MinecraftServer server) { overworld = server.overworld(); reinit(); warnOfMissingValues(); } 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); reinit(); warnOfMissingValues(); } private static 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(); } 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) { 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; } 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; } private static 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 static void reinit() { VALUES.clear(); VALUES.putAll(ITEM_VALUES); ITEM_TAG_VALUES.forEach( (tag, emcValue) -> BuiltInRegistries.ITEM .getTagOrEmpty(tag) .forEach(holder -> VALUES.putIfAbsent(ItemUtils.getId(holder), emcValue)) ); BLOCK_TAG_VALUES.forEach( (tag, emcValue) -> BuiltInRegistries.BLOCK .getTagOrEmpty(tag) .forEach(holder -> VALUES.putIfAbsent(ItemUtils.getId(holder), emcValue)) ); 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)); sync(); } private static void sync() { syncTo(PlayerUtils.all()); } private static void syncTo(Collection player) { var buf = PacketByteBufs.create(); buf.writeMap(VALUES, FriendlyByteBuf::writeResourceLocation, BufUtils::writeOptionalDouble); ServerPlayNetworking.send(player, NetworkingConstants.UPDATE_EMCS, buf); } private static void warnOfMissingValues() { if (overworld == null) { return; } BuiltInRegistries.ITEM .keySet() .stream() .filter(item -> !VALUES.containsKey(item)) .forEach(item -> ErisAlchemy.LOGGER.warn("No EMC value for '{}' known", item)); } }