package net.minecraft.stats; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.mojang.logging.LogUtils; import com.mojang.serialization.Codec; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Set; import java.util.function.Consumer; import java.util.function.Predicate; import net.minecraft.advancements.CriteriaTriggers; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.nbt.StringTag; import net.minecraft.network.protocol.game.ClientboundRecipeBookAddPacket; import net.minecraft.network.protocol.game.ClientboundRecipeBookRemovePacket; import net.minecraft.network.protocol.game.ClientboundRecipeBookSettingsPacket; import net.minecraft.network.protocol.game.ClientboundRecipeBookAddPacket.Entry; import net.minecraft.resources.ResourceKey; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.item.crafting.Recipe; import net.minecraft.world.item.crafting.RecipeHolder; import net.minecraft.world.item.crafting.display.RecipeDisplayEntry; import net.minecraft.world.item.crafting.display.RecipeDisplayId; import org.slf4j.Logger; public class ServerRecipeBook extends RecipeBook { public static final String RECIPE_BOOK_TAG = "recipeBook"; private static final Logger LOGGER = LogUtils.getLogger(); private static final Codec>>> RECIPE_LIST_CODEC = Recipe.KEY_CODEC.listOf(); private final ServerRecipeBook.DisplayResolver displayResolver; @VisibleForTesting protected final Set>> known = Sets.newIdentityHashSet(); @VisibleForTesting protected final Set>> highlight = Sets.newIdentityHashSet(); public ServerRecipeBook(ServerRecipeBook.DisplayResolver displayResolver) { this.displayResolver = displayResolver; } public void add(ResourceKey> recipe) { this.known.add(recipe); } public boolean contains(ResourceKey> recipe) { return this.known.contains(recipe); } public void remove(ResourceKey> recipe) { this.known.remove(recipe); this.highlight.remove(recipe); } public void removeHighlight(ResourceKey> recipe) { this.highlight.remove(recipe); } private void addHighlight(ResourceKey> recipe) { this.highlight.add(recipe); } public int addRecipes(Collection> recipes, ServerPlayer player) { List list = new ArrayList(); for (RecipeHolder recipeHolder : recipes) { ResourceKey> resourceKey = recipeHolder.id(); if (!this.known.contains(resourceKey) && !recipeHolder.value().isSpecial()) { this.add(resourceKey); this.addHighlight(resourceKey); this.displayResolver .displaysForRecipe(resourceKey, recipeDisplayEntry -> list.add(new Entry(recipeDisplayEntry, recipeHolder.value().showNotification(), true))); CriteriaTriggers.RECIPE_UNLOCKED.trigger(player, recipeHolder); } } if (!list.isEmpty()) { player.connection.send(new ClientboundRecipeBookAddPacket(list, false)); } return list.size(); } public int removeRecipes(Collection> recipes, ServerPlayer player) { List list = Lists.newArrayList(); for (RecipeHolder recipeHolder : recipes) { ResourceKey> resourceKey = recipeHolder.id(); if (this.known.contains(resourceKey)) { this.remove(resourceKey); this.displayResolver.displaysForRecipe(resourceKey, recipeDisplayEntry -> list.add(recipeDisplayEntry.id())); } } if (!list.isEmpty()) { player.connection.send(new ClientboundRecipeBookRemovePacket(list)); } return list.size(); } public CompoundTag toNbt() { CompoundTag compoundTag = new CompoundTag(); this.getBookSettings().write(compoundTag); ListTag listTag = new ListTag(); for (ResourceKey> resourceKey : this.known) { listTag.add(StringTag.valueOf(resourceKey.location().toString())); } compoundTag.put("recipes", listTag); ListTag listTag2 = new ListTag(); for (ResourceKey> resourceKey2 : this.highlight) { listTag2.add(StringTag.valueOf(resourceKey2.location().toString())); } compoundTag.put("toBeDisplayed", listTag2); return compoundTag; } public void fromNbt(CompoundTag tag, Predicate>> isRecognized) { this.setBookSettings(RecipeBookSettings.read(tag)); List>> list = (List>>)tag.read("recipes", RECIPE_LIST_CODEC).orElse(List.of()); this.loadRecipes(list, this::add, isRecognized); List>> list2 = (List>>)tag.read("toBeDisplayed", RECIPE_LIST_CODEC).orElse(List.of()); this.loadRecipes(list2, this::addHighlight, isRecognized); } private void loadRecipes(List>> recipes, Consumer>> output, Predicate>> isRecognized) { for (ResourceKey> resourceKey : recipes) { if (!isRecognized.test(resourceKey)) { LOGGER.error("Tried to load unrecognized recipe: {} removed now.", resourceKey); } else { output.accept(resourceKey); } } } public void sendInitialRecipeBook(ServerPlayer player) { player.connection.send(new ClientboundRecipeBookSettingsPacket(this.getBookSettings())); List list = new ArrayList(this.known.size()); for (ResourceKey> resourceKey : this.known) { this.displayResolver .displaysForRecipe(resourceKey, recipeDisplayEntry -> list.add(new Entry(recipeDisplayEntry, false, this.highlight.contains(resourceKey)))); } player.connection.send(new ClientboundRecipeBookAddPacket(list, true)); } public void copyOverData(ServerRecipeBook other) { this.known.clear(); this.highlight.clear(); this.bookSettings.replaceFrom(other.bookSettings); this.known.addAll(other.known); this.highlight.addAll(other.highlight); } @FunctionalInterface public interface DisplayResolver { void displaysForRecipe(ResourceKey> resourceKey, Consumer consumer); } }