package net.minecraft.world.item.crafting; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.Multimap; import com.google.common.collect.ImmutableMultimap.Builder; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; import com.mojang.logging.LogUtils; import com.mojang.serialization.JsonOps; import java.util.Collection; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Map.Entry; import java.util.stream.Collectors; import java.util.stream.Stream; import net.minecraft.core.HolderLookup; import net.minecraft.core.NonNullList; import net.minecraft.core.registries.Registries; import net.minecraft.resources.RegistryOps; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.packs.resources.ResourceManager; import net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener; import net.minecraft.util.profiling.ProfilerFiller; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.Level; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; public class RecipeManager extends SimpleJsonResourceReloadListener { private static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create(); private static final Logger LOGGER = LogUtils.getLogger(); private final HolderLookup.Provider registries; private Multimap, RecipeHolder> byType = ImmutableMultimap.of(); private Map> byName = ImmutableMap.of(); private boolean hasErrors; public RecipeManager(HolderLookup.Provider registries) { super(GSON, Registries.elementsDirPath(Registries.RECIPE)); this.registries = registries; } protected void apply(Map object, ResourceManager resourceManager, ProfilerFiller profiler) { this.hasErrors = false; Builder, RecipeHolder> builder = ImmutableMultimap.builder(); com.google.common.collect.ImmutableMap.Builder> builder2 = ImmutableMap.builder(); RegistryOps registryOps = this.registries.createSerializationContext(JsonOps.INSTANCE); for (Entry entry : object.entrySet()) { ResourceLocation resourceLocation = (ResourceLocation)entry.getKey(); try { Recipe recipe = Recipe.CODEC.parse(registryOps, (JsonElement)entry.getValue()).getOrThrow(JsonParseException::new); RecipeHolder recipeHolder = new RecipeHolder<>(resourceLocation, recipe); builder.put(recipe.getType(), recipeHolder); builder2.put(resourceLocation, recipeHolder); } catch (IllegalArgumentException | JsonParseException var12) { LOGGER.error("Parsing error loading recipe {}", resourceLocation, var12); } } this.byType = builder.build(); this.byName = builder2.build(); LOGGER.info("Loaded {} recipes", this.byType.size()); } public boolean hadErrorsLoading() { return this.hasErrors; } public > Optional> getRecipeFor(RecipeType recipeType, I input, Level level) { return this.getRecipeFor(recipeType, input, level, (RecipeHolder)null); } public > Optional> getRecipeFor( RecipeType recipeType, I input, Level level, @Nullable ResourceLocation lastRecipe ) { RecipeHolder recipeHolder = lastRecipe != null ? this.byKeyTyped(recipeType, lastRecipe) : null; return this.getRecipeFor(recipeType, input, level, recipeHolder); } public > Optional> getRecipeFor( RecipeType recipeType, I input, Level level, @Nullable RecipeHolder lastRecipe ) { if (input.isEmpty()) { return Optional.empty(); } else { return lastRecipe != null && lastRecipe.value().matches(input, level) ? Optional.of(lastRecipe) : this.byType(recipeType).stream().filter(recipeHolder -> recipeHolder.value().matches(input, level)).findFirst(); } } public > List> getAllRecipesFor(RecipeType recipeType) { return List.copyOf(this.byType(recipeType)); } public > List> getRecipesFor(RecipeType recipeType, I input, Level level) { return (List>)this.byType(recipeType) .stream() .filter(recipeHolder -> recipeHolder.value().matches(input, level)) .sorted(Comparator.comparing(recipeHolder -> recipeHolder.value().getResultItem(level.registryAccess()).getDescriptionId())) .collect(Collectors.toList()); } private > Collection> byType(RecipeType type) { return (Collection>)this.byType.get(type); } public > NonNullList getRemainingItemsFor(RecipeType recipeType, I input, Level lvel) { Optional> optional = this.getRecipeFor(recipeType, input, lvel); if (optional.isPresent()) { return ((RecipeHolder)optional.get()).value().getRemainingItems(input); } else { NonNullList nonNullList = NonNullList.withSize(input.size(), ItemStack.EMPTY); for (int i = 0; i < nonNullList.size(); i++) { nonNullList.set(i, input.getItem(i)); } return nonNullList; } } public Optional> byKey(ResourceLocation recipeId) { return Optional.ofNullable((RecipeHolder)this.byName.get(recipeId)); } @Nullable private > RecipeHolder byKeyTyped(RecipeType type, ResourceLocation name) { RecipeHolder recipeHolder = (RecipeHolder)this.byName.get(name); return (RecipeHolder)(recipeHolder != null && recipeHolder.value().getType().equals(type) ? recipeHolder : null); } public Collection> getOrderedRecipes() { return this.byType.values(); } public Collection> getRecipes() { return this.byName.values(); } public Stream getRecipeIds() { return this.byName.keySet().stream(); } @VisibleForTesting protected static RecipeHolder fromJson(ResourceLocation recipeId, JsonObject json, HolderLookup.Provider registries) { Recipe recipe = Recipe.CODEC.parse(registries.createSerializationContext(JsonOps.INSTANCE), json).getOrThrow(JsonParseException::new); return new RecipeHolder<>(recipeId, recipe); } public void replaceRecipes(Iterable> recipes) { this.hasErrors = false; Builder, RecipeHolder> builder = ImmutableMultimap.builder(); com.google.common.collect.ImmutableMap.Builder> builder2 = ImmutableMap.builder(); for (RecipeHolder recipeHolder : recipes) { RecipeType recipeType = recipeHolder.value().getType(); builder.put(recipeType, recipeHolder); builder2.put(recipeHolder.id(), recipeHolder); } this.byType = builder.build(); this.byName = builder2.build(); } public static > RecipeManager.CachedCheck createCheck(RecipeType recipeType) { return new RecipeManager.CachedCheck() { @Nullable private ResourceLocation lastRecipe; @Override public Optional> getRecipeFor(I input, Level level) { RecipeManager recipeManager = level.getRecipeManager(); Optional> optional = recipeManager.getRecipeFor(recipeType, input, level, this.lastRecipe); if (optional.isPresent()) { RecipeHolder recipeHolder = (RecipeHolder)optional.get(); this.lastRecipe = recipeHolder.id(); return Optional.of(recipeHolder); } else { return Optional.empty(); } } }; } public interface CachedCheck> { Optional> getRecipeFor(I input, Level level); } }