minecraft-src/net/minecraft/world/item/crafting/RecipeManager.java
2025-07-04 03:45:38 +03:00

292 lines
13 KiB
Java

package net.minecraft.world.item.crafting;
import com.google.common.annotations.VisibleForTesting;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.mojang.logging.LogUtils;
import com.mojang.serialization.JsonOps;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.OptionalInt;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import net.minecraft.core.HolderLookup;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.FileToIdConverter;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener;
import net.minecraft.server.packs.resources.SimplePreparableReloadListener;
import net.minecraft.util.profiling.ProfilerFiller;
import net.minecraft.world.flag.FeatureFlagSet;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.crafting.SelectableRecipe.SingleInputEntry;
import net.minecraft.world.item.crafting.SelectableRecipe.SingleInputSet;
import net.minecraft.world.item.crafting.display.RecipeDisplay;
import net.minecraft.world.item.crafting.display.RecipeDisplayEntry;
import net.minecraft.world.item.crafting.display.RecipeDisplayId;
import net.minecraft.world.level.Level;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
public class RecipeManager extends SimplePreparableReloadListener<RecipeMap> implements RecipeAccess {
private static final Logger LOGGER = LogUtils.getLogger();
private static final Map<ResourceKey<RecipePropertySet>, RecipeManager.IngredientExtractor> RECIPE_PROPERTY_SETS = Map.of(
RecipePropertySet.SMITHING_ADDITION,
(RecipeManager.IngredientExtractor)recipe -> recipe instanceof SmithingRecipe smithingRecipe ? smithingRecipe.additionIngredient() : Optional.empty(),
RecipePropertySet.SMITHING_BASE,
(RecipeManager.IngredientExtractor)recipe -> recipe instanceof SmithingRecipe smithingRecipe
? Optional.of(smithingRecipe.baseIngredient())
: Optional.empty(),
RecipePropertySet.SMITHING_TEMPLATE,
(RecipeManager.IngredientExtractor)recipe -> recipe instanceof SmithingRecipe smithingRecipe ? smithingRecipe.templateIngredient() : Optional.empty(),
RecipePropertySet.FURNACE_INPUT,
forSingleInput(RecipeType.SMELTING),
RecipePropertySet.BLAST_FURNACE_INPUT,
forSingleInput(RecipeType.BLASTING),
RecipePropertySet.SMOKER_INPUT,
forSingleInput(RecipeType.SMOKING),
RecipePropertySet.CAMPFIRE_INPUT,
forSingleInput(RecipeType.CAMPFIRE_COOKING)
);
private static final FileToIdConverter RECIPE_LISTER = FileToIdConverter.registry(Registries.RECIPE);
private final HolderLookup.Provider registries;
private RecipeMap recipes = RecipeMap.EMPTY;
private Map<ResourceKey<RecipePropertySet>, RecipePropertySet> propertySets = Map.of();
private SingleInputSet<StonecutterRecipe> stonecutterRecipes = SingleInputSet.empty();
private List<RecipeManager.ServerDisplayInfo> allDisplays = List.of();
private Map<ResourceKey<Recipe<?>>, List<RecipeManager.ServerDisplayInfo>> recipeToDisplay = Map.of();
public RecipeManager(HolderLookup.Provider registries) {
this.registries = registries;
}
protected RecipeMap prepare(ResourceManager resourceManager, ProfilerFiller profilerFiller) {
SortedMap<ResourceLocation, Recipe<?>> sortedMap = new TreeMap();
SimpleJsonResourceReloadListener.scanDirectory(
resourceManager, RECIPE_LISTER, this.registries.createSerializationContext(JsonOps.INSTANCE), Recipe.CODEC, sortedMap
);
List<RecipeHolder<?>> list = new ArrayList(sortedMap.size());
sortedMap.forEach((resourceLocation, recipe) -> {
ResourceKey<Recipe<?>> resourceKey = ResourceKey.create(Registries.RECIPE, resourceLocation);
RecipeHolder<?> recipeHolder = new RecipeHolder(resourceKey, recipe);
list.add(recipeHolder);
});
return RecipeMap.create(list);
}
protected void apply(RecipeMap recipeMap, ResourceManager resourceManager, ProfilerFiller profilerFiller) {
this.recipes = recipeMap;
LOGGER.info("Loaded {} recipes", recipeMap.values().size());
}
public void finalizeRecipeLoading(FeatureFlagSet enabledFeatures) {
List<SingleInputEntry<StonecutterRecipe>> list = new ArrayList();
List<RecipeManager.IngredientCollector> list2 = RECIPE_PROPERTY_SETS.entrySet()
.stream()
.map(entry -> new RecipeManager.IngredientCollector((ResourceKey<RecipePropertySet>)entry.getKey(), (RecipeManager.IngredientExtractor)entry.getValue()))
.toList();
this.recipes
.values()
.forEach(
recipeHolder -> {
Recipe<?> recipe = recipeHolder.value();
if (!recipe.isSpecial() && recipe.placementInfo().isImpossibleToPlace()) {
LOGGER.warn("Recipe {} can't be placed due to empty ingredients and will be ignored", recipeHolder.id().location());
} else {
list2.forEach(ingredientCollector -> ingredientCollector.accept(recipe));
if (recipe instanceof StonecutterRecipe stonecutterRecipe
&& isIngredientEnabled(enabledFeatures, stonecutterRecipe.input())
&& stonecutterRecipe.resultDisplay().isEnabled(enabledFeatures)) {
list.add(new SingleInputEntry(stonecutterRecipe.input(), new SelectableRecipe(stonecutterRecipe.resultDisplay(), Optional.of(recipeHolder))));
}
}
}
);
this.propertySets = (Map<ResourceKey<RecipePropertySet>, RecipePropertySet>)list2.stream()
.collect(
Collectors.toUnmodifiableMap(ingredientCollector -> ingredientCollector.key, ingredientCollector -> ingredientCollector.asPropertySet(enabledFeatures))
);
this.stonecutterRecipes = new SingleInputSet<>(list);
this.allDisplays = unpackRecipeInfo(this.recipes.values(), enabledFeatures);
this.recipeToDisplay = (Map<ResourceKey<Recipe<?>>, List<RecipeManager.ServerDisplayInfo>>)this.allDisplays
.stream()
.collect(Collectors.groupingBy(serverDisplayInfo -> serverDisplayInfo.parent.id(), IdentityHashMap::new, Collectors.toList()));
}
static List<Ingredient> filterDisabled(FeatureFlagSet enabledFeatures, List<Ingredient> ingredients) {
ingredients.removeIf(ingredient -> !isIngredientEnabled(enabledFeatures, ingredient));
return ingredients;
}
private static boolean isIngredientEnabled(FeatureFlagSet enabledFeatures, Ingredient ingredient) {
return ingredient.items().allMatch(holder -> ((Item)holder.value()).isEnabled(enabledFeatures));
}
public <I extends RecipeInput, T extends Recipe<I>> Optional<RecipeHolder<T>> getRecipeFor(
RecipeType<T> recipeType, I input, Level level, @Nullable ResourceKey<Recipe<?>> recipe
) {
RecipeHolder<T> recipeHolder = recipe != null ? this.byKeyTyped(recipeType, recipe) : null;
return this.getRecipeFor(recipeType, input, level, recipeHolder);
}
public <I extends RecipeInput, T extends Recipe<I>> Optional<RecipeHolder<T>> getRecipeFor(
RecipeType<T> recipeType, I input, Level level, @Nullable RecipeHolder<T> lastRecipe
) {
return lastRecipe != null && lastRecipe.value().matches(input, level) ? Optional.of(lastRecipe) : this.getRecipeFor(recipeType, input, level);
}
public <I extends RecipeInput, T extends Recipe<I>> Optional<RecipeHolder<T>> getRecipeFor(RecipeType<T> recipeType, I input, Level level) {
return this.recipes.getRecipesFor(recipeType, input, level).findFirst();
}
public Optional<RecipeHolder<?>> byKey(ResourceKey<Recipe<?>> key) {
return Optional.ofNullable(this.recipes.byKey(key));
}
@Nullable
private <T extends Recipe<?>> RecipeHolder<T> byKeyTyped(RecipeType<T> type, ResourceKey<Recipe<?>> key) {
RecipeHolder<?> recipeHolder = this.recipes.byKey(key);
return (RecipeHolder<T>)(recipeHolder != null && recipeHolder.value().getType().equals(type) ? recipeHolder : null);
}
public Map<ResourceKey<RecipePropertySet>, RecipePropertySet> getSynchronizedItemProperties() {
return this.propertySets;
}
public SingleInputSet<StonecutterRecipe> getSynchronizedStonecutterRecipes() {
return this.stonecutterRecipes;
}
@Override
public RecipePropertySet propertySet(ResourceKey<RecipePropertySet> propertySet) {
return (RecipePropertySet)this.propertySets.getOrDefault(propertySet, RecipePropertySet.EMPTY);
}
@Override
public SingleInputSet<StonecutterRecipe> stonecutterRecipes() {
return this.stonecutterRecipes;
}
public Collection<RecipeHolder<?>> getRecipes() {
return this.recipes.values();
}
@Nullable
public RecipeManager.ServerDisplayInfo getRecipeFromDisplay(RecipeDisplayId display) {
return (RecipeManager.ServerDisplayInfo)this.allDisplays.get(display.index());
}
public void listDisplaysForRecipe(ResourceKey<Recipe<?>> recipe, Consumer<RecipeDisplayEntry> output) {
List<RecipeManager.ServerDisplayInfo> list = (List<RecipeManager.ServerDisplayInfo>)this.recipeToDisplay.get(recipe);
if (list != null) {
list.forEach(serverDisplayInfo -> output.accept(serverDisplayInfo.display));
}
}
@VisibleForTesting
protected static RecipeHolder<?> fromJson(ResourceKey<Recipe<?>> recipe, JsonObject json, HolderLookup.Provider registries) {
Recipe<?> recipe2 = Recipe.CODEC.parse(registries.createSerializationContext(JsonOps.INSTANCE), json).getOrThrow(JsonParseException::new);
return new RecipeHolder<>(recipe, recipe2);
}
public static <I extends RecipeInput, T extends Recipe<I>> RecipeManager.CachedCheck<I, T> createCheck(RecipeType<T> recipeType) {
return new RecipeManager.CachedCheck<I, T>() {
@Nullable
private ResourceKey<Recipe<?>> lastRecipe;
@Override
public Optional<RecipeHolder<T>> getRecipeFor(I input, ServerLevel level) {
RecipeManager recipeManager = level.recipeAccess();
Optional<RecipeHolder<T>> optional = recipeManager.getRecipeFor(recipeType, input, level, this.lastRecipe);
if (optional.isPresent()) {
RecipeHolder<T> recipeHolder = (RecipeHolder<T>)optional.get();
this.lastRecipe = recipeHolder.id();
return Optional.of(recipeHolder);
} else {
return Optional.empty();
}
}
};
}
private static List<RecipeManager.ServerDisplayInfo> unpackRecipeInfo(Iterable<RecipeHolder<?>> recipes, FeatureFlagSet enabledFeatures) {
List<RecipeManager.ServerDisplayInfo> list = new ArrayList();
Object2IntMap<String> object2IntMap = new Object2IntOpenHashMap<>();
for (RecipeHolder<?> recipeHolder : recipes) {
Recipe<?> recipe = recipeHolder.value();
OptionalInt optionalInt;
if (recipe.group().isEmpty()) {
optionalInt = OptionalInt.empty();
} else {
optionalInt = OptionalInt.of(object2IntMap.computeIfAbsent(recipe.group(), object -> object2IntMap.size()));
}
Optional<List<Ingredient>> optional;
if (recipe.isSpecial()) {
optional = Optional.empty();
} else {
optional = Optional.of(recipe.placementInfo().ingredients());
}
for (RecipeDisplay recipeDisplay : recipe.display()) {
if (recipeDisplay.isEnabled(enabledFeatures)) {
int i = list.size();
RecipeDisplayId recipeDisplayId = new RecipeDisplayId(i);
RecipeDisplayEntry recipeDisplayEntry = new RecipeDisplayEntry(recipeDisplayId, recipeDisplay, optionalInt, recipe.recipeBookCategory(), optional);
list.add(new RecipeManager.ServerDisplayInfo(recipeDisplayEntry, recipeHolder));
}
}
}
return list;
}
private static RecipeManager.IngredientExtractor forSingleInput(RecipeType<? extends SingleItemRecipe> recipeType) {
return recipe -> recipe.getType() == recipeType && recipe instanceof SingleItemRecipe singleItemRecipe
? Optional.of(singleItemRecipe.input())
: Optional.empty();
}
public interface CachedCheck<I extends RecipeInput, T extends Recipe<I>> {
Optional<RecipeHolder<T>> getRecipeFor(I input, ServerLevel level);
}
public static class IngredientCollector implements Consumer<Recipe<?>> {
final ResourceKey<RecipePropertySet> key;
private final RecipeManager.IngredientExtractor extractor;
private final List<Ingredient> ingredients = new ArrayList();
protected IngredientCollector(ResourceKey<RecipePropertySet> key, RecipeManager.IngredientExtractor extractor) {
this.key = key;
this.extractor = extractor;
}
public void accept(Recipe<?> recipe) {
this.extractor.apply(recipe).ifPresent(this.ingredients::add);
}
public RecipePropertySet asPropertySet(FeatureFlagSet enabledFeatures) {
return RecipePropertySet.create(RecipeManager.filterDisabled(enabledFeatures, this.ingredients));
}
}
@FunctionalInterface
public interface IngredientExtractor {
Optional<Ingredient> apply(Recipe<?> recipe);
}
public record ServerDisplayInfo(RecipeDisplayEntry display, RecipeHolder<?> parent) {
}
}