package com.alttd.playerutils.util; import lombok.Getter; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.minimessage.MiniMessage; import org.bukkit.Material; import org.bukkit.block.ShulkerBox; import org.bukkit.entity.HumanEntity; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.BookMeta; import org.bukkit.inventory.meta.BlockStateMeta; import java.nio.charset.StandardCharsets; /** * Utility to compute the UTF-8 byte size of a written book's contents. */ public final class BookByteUtils { private BookByteUtils() { } // 65,000 bytes per book (below CoreProtect hard limit ~65,535) @Getter private static final int MAX_BOOK_BYTES = 30_000; @Getter private static final int BIG_BOOK_BYTES = 10_000; public static int getBigBookBytes(HumanEntity humanEntity) { return calcBookBytes(humanEntity, BIG_BOOK_BYTES); } public static int getMaxBookBytes(HumanEntity humanEntity) { return calcBookBytes(humanEntity, MAX_BOOK_BYTES); } private static int calcBookBytes(HumanEntity humanEntity, int bookBytes) { if (humanEntity.hasPermission("playerutils.bigbook.bypass")) { return Integer.MAX_VALUE; } else if (humanEntity.hasPermission("playerutils.bigbook.double")) { return bookBytes * 2; } else if (humanEntity.hasPermission("playerutils.bigbook.quadruple")) { return bookBytes * 4; } else { return bookBytes; } } public static boolean shouldCountForBookByteLimit(ItemStack stack) { if (stack == null) { return false; } Material type = stack.getType(); if (type == Material.WRITTEN_BOOK || type == Material.WRITABLE_BOOK) { return true; } if (stack.getItemMeta() instanceof BookMeta) { return true; } if (!(stack.getItemMeta() instanceof BlockStateMeta bsm) || !(bsm.getBlockState() instanceof ShulkerBox shulker)) { return false; } for (ItemStack content : shulker.getInventory().getContents()) { if (content == null) { continue; } Material contentType = content.getType(); if (contentType == Material.WRITTEN_BOOK || contentType == Material.WRITABLE_BOOK) { return true; } if (content.getItemMeta() instanceof BookMeta) { return true; } } return false; } /** * Compute the number of bytes used by the provided BookMeta. */ public static int computeBytes(BookMeta meta) { if (meta == null) { return 0; } int totalBytes = 0; String title = meta.getTitle(); if (title != null) { totalBytes += title.getBytes(StandardCharsets.UTF_8).length; } for (Component page : meta.pages()) { if (page == null) { continue; } String pageString = MiniMessage.miniMessage().serialize(page); if (pageString.isEmpty()) { continue; } totalBytes += pageString.getBytes(StandardCharsets.UTF_8).length; } return totalBytes; } /** * Compute the number of bytes used by a book item stack. If the item is not a written book, returns 0. */ public static int computeBytes(ItemStack stack) { if (stack == null) { return 0; } // Direct written book if (stack.getItemMeta() instanceof BookMeta meta) { int perBook = computeBytes(meta); return perBook * Math.max(1, stack.getAmount()); } // Shulker box: sum bytes of contained written books if (stack.getItemMeta() instanceof BlockStateMeta bsm && bsm.getBlockState() instanceof ShulkerBox shulker) { int total = 0; for (ItemStack content : shulker.getInventory().getContents()) { if (content == null) { continue; } if (content.getItemMeta() instanceof BookMeta bookMeta) { total += computeBytes(bookMeta) * Math.max(1, content.getAmount()); } } return total; } return 0; } }