Add BookByteChunkLimitListener and BookWriteEvent to enforce book byte limits and prevent chunk saturation

This commit is contained in:
2026-03-27 18:51:43 +01:00
parent bd7a46c283
commit e03c51198c
6 changed files with 437 additions and 3 deletions
@@ -0,0 +1,70 @@
package com.alttd.playerutils.util;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.BookMeta;
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)
public static final int MAX_BOOK_BYTES = 65_000;
public static boolean isWrittenBook(ItemStack stack) {
if (stack == null) {
return false;
}
if (!(stack.getItemMeta() instanceof BookMeta meta)) {
return false;
}
return meta.hasAuthor() || meta.hasPages() || meta.hasTitle();
}
/**
* 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;
}
if (!(stack.getItemMeta() instanceof BookMeta meta)) {
return 0;
}
// written books do not stack; still, be safe and multiply by amount if it ever changes
int perBook = computeBytes(meta);
return perBook * Math.max(1, stack.getAmount());
}
}