package net.minecraft.world.item.component; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.collect.ImmutableList.Builder; import com.google.gson.JsonElement; import com.mojang.serialization.Codec; import com.mojang.serialization.DataResult; import com.mojang.serialization.JsonOps; import com.mojang.serialization.codecs.RecordCodecBuilder; import java.util.List; import java.util.Optional; import java.util.function.Consumer; import net.minecraft.ChatFormatting; import net.minecraft.commands.CommandSourceStack; import net.minecraft.core.HolderLookup; import net.minecraft.core.component.DataComponentGetter; import net.minecraft.core.component.DataComponents; import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.ComponentSerialization; import net.minecraft.network.chat.ComponentUtils; import net.minecraft.network.codec.ByteBufCodecs; import net.minecraft.network.codec.StreamCodec; import net.minecraft.server.network.Filterable; import net.minecraft.util.ExtraCodecs; import net.minecraft.util.GsonHelper; import net.minecraft.util.StringUtil; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.TooltipFlag; import org.jetbrains.annotations.Nullable; public record WrittenBookContent(Filterable title, String author, int generation, List> pages, boolean resolved) implements BookContent, TooltipProvider { public static final WrittenBookContent EMPTY = new WrittenBookContent(Filterable.passThrough(""), "", 0, List.of(), true); public static final int PAGE_LENGTH = 32767; public static final int TITLE_LENGTH = 16; public static final int TITLE_MAX_LENGTH = 32; public static final int MAX_GENERATION = 3; public static final int MAX_CRAFTABLE_GENERATION = 2; public static final Codec CONTENT_CODEC = ComponentSerialization.flatRestrictedCodec(32767); public static final Codec>> PAGES_CODEC = pagesCodec(CONTENT_CODEC); public static final Codec CODEC = RecordCodecBuilder.create( instance -> instance.group( Filterable.codec(Codec.string(0, 32)).fieldOf("title").forGetter(WrittenBookContent::title), Codec.STRING.fieldOf("author").forGetter(WrittenBookContent::author), ExtraCodecs.intRange(0, 3).optionalFieldOf("generation", 0).forGetter(WrittenBookContent::generation), PAGES_CODEC.optionalFieldOf("pages", List.of()).forGetter(WrittenBookContent::pages), Codec.BOOL.optionalFieldOf("resolved", false).forGetter(WrittenBookContent::resolved) ) .apply(instance, WrittenBookContent::new) ); public static final StreamCodec STREAM_CODEC = StreamCodec.composite( Filterable.streamCodec(ByteBufCodecs.stringUtf8(32)), WrittenBookContent::title, ByteBufCodecs.STRING_UTF8, WrittenBookContent::author, ByteBufCodecs.VAR_INT, WrittenBookContent::generation, Filterable.streamCodec(ComponentSerialization.STREAM_CODEC).apply(ByteBufCodecs.list()), WrittenBookContent::pages, ByteBufCodecs.BOOL, WrittenBookContent::resolved, WrittenBookContent::new ); public WrittenBookContent(Filterable title, String author, int generation, List> pages, boolean resolved) { if (generation >= 0 && generation <= 3) { this.title = title; this.author = author; this.generation = generation; this.pages = pages; this.resolved = resolved; } else { throw new IllegalArgumentException("Generation was " + generation + ", but must be between 0 and 3"); } } private static Codec> pageCodec(Codec codec) { return Filterable.codec(codec); } public static Codec>> pagesCodec(Codec codec) { return pageCodec(codec).listOf(); } @Nullable public WrittenBookContent tryCraftCopy() { return this.generation >= 2 ? null : new WrittenBookContent(this.title, this.author, this.generation + 1, this.pages, this.resolved); } public static boolean resolveForItem(ItemStack stack, CommandSourceStack source, @Nullable Player player) { WrittenBookContent writtenBookContent = stack.get(DataComponents.WRITTEN_BOOK_CONTENT); if (writtenBookContent != null && !writtenBookContent.resolved()) { WrittenBookContent writtenBookContent2 = writtenBookContent.resolve(source, player); if (writtenBookContent2 != null) { stack.set(DataComponents.WRITTEN_BOOK_CONTENT, writtenBookContent2); return true; } stack.set(DataComponents.WRITTEN_BOOK_CONTENT, writtenBookContent.markResolved()); } return false; } @Nullable public WrittenBookContent resolve(CommandSourceStack source, @Nullable Player player) { if (this.resolved) { return null; } else { Builder> builder = ImmutableList.builderWithExpectedSize(this.pages.size()); for (Filterable filterable : this.pages) { Optional> optional = resolvePage(source, player, filterable); if (optional.isEmpty()) { return null; } builder.add((Filterable)optional.get()); } return new WrittenBookContent(this.title, this.author, this.generation, builder.build(), true); } } public WrittenBookContent markResolved() { return new WrittenBookContent(this.title, this.author, this.generation, this.pages, true); } private static Optional> resolvePage(CommandSourceStack source, @Nullable Player player, Filterable pages) { return pages.resolve(component -> { try { Component component2 = ComponentUtils.updateForEntity(source, component, player, 0); return isPageTooLarge(component2, source.registryAccess()) ? Optional.empty() : Optional.of(component2); } catch (Exception var4) { return Optional.of(component); } }); } private static boolean isPageTooLarge(Component page, HolderLookup.Provider registryAccess) { DataResult dataResult = ComponentSerialization.CODEC.encodeStart(registryAccess.createSerializationContext(JsonOps.INSTANCE), page); return dataResult.isSuccess() && GsonHelper.encodesLongerThan(dataResult.getOrThrow(), 32767); } public List getPages(boolean filtered) { return Lists.transform(this.pages, filterable -> (Component)filterable.get(filtered)); } public WrittenBookContent withReplacedPages(List> list) { return new WrittenBookContent(this.title, this.author, this.generation, list, false); } @Override public void addToTooltip(Item.TooltipContext context, Consumer tooltipAdder, TooltipFlag flag, DataComponentGetter componentGetter) { if (!StringUtil.isBlank(this.author)) { tooltipAdder.accept(Component.translatable("book.byAuthor", this.author).withStyle(ChatFormatting.GRAY)); } tooltipAdder.accept(Component.translatable("book.generation." + this.generation).withStyle(ChatFormatting.GRAY)); } }