From f6d5108c7b4cbfd657a5232821053b2a11fa07b4 Mon Sep 17 00:00:00 2001 From: EpicKnarvik97 Date: Fri, 8 Aug 2025 00:49:54 +0200 Subject: [PATCH 1/2] Fixes an exception encountered when importing a text file as a book --- .../bookswithoutborders/utility/BookFormatter.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/knarcraft/bookswithoutborders/utility/BookFormatter.java b/src/main/java/net/knarcraft/bookswithoutborders/utility/BookFormatter.java index 5c218e4..29dbcbb 100644 --- a/src/main/java/net/knarcraft/bookswithoutborders/utility/BookFormatter.java +++ b/src/main/java/net/knarcraft/bookswithoutborders/utility/BookFormatter.java @@ -95,8 +95,11 @@ public final class BookFormatter { */ public static void formatLastPageAddNewline(@NotNull List rawPages, int fitsNewline) { int pageIndex = rawPages.size() - 1; - if (rawPages.get(pageIndex).length() <= fitsNewline && !rawPages.get(pageIndex).isEmpty()) { - rawPages.set(pageIndex, (rawPages.get(pageIndex)) + "\n"); + String pageContents = rawPages.get(pageIndex); + if (pageContents == null) { + rawPages.set(pageIndex, ""); + } else if (pageContents.length() <= fitsNewline && !pageContents.isEmpty()) { + rawPages.set(pageIndex, pageContents + "\n"); } } From af094f9931715e39ae1952a7e6846fc7a8c8da6f Mon Sep 17 00:00:00 2001 From: EpicKnarvik97 Date: Sun, 10 Aug 2025 19:10:08 +0200 Subject: [PATCH 2/2] Adds commands for adding book title pages and for deleting book pages --- .../BooksWithoutBorders.java | 4 + .../command/CommandAddTitlePage.java | 155 ++++++++++++++++++ .../command/CommandDeletePage.java | 91 ++++++++++ .../utility/InventoryHelper.java | 19 +++ src/main/resources/plugin.yml | 16 +- 5 files changed, 284 insertions(+), 1 deletion(-) create mode 100644 src/main/java/net/knarcraft/bookswithoutborders/command/CommandAddTitlePage.java create mode 100644 src/main/java/net/knarcraft/bookswithoutborders/command/CommandDeletePage.java diff --git a/src/main/java/net/knarcraft/bookswithoutborders/BooksWithoutBorders.java b/src/main/java/net/knarcraft/bookswithoutborders/BooksWithoutBorders.java index a6f59ab..711f24c 100644 --- a/src/main/java/net/knarcraft/bookswithoutborders/BooksWithoutBorders.java +++ b/src/main/java/net/knarcraft/bookswithoutborders/BooksWithoutBorders.java @@ -1,10 +1,12 @@ package net.knarcraft.bookswithoutborders; +import net.knarcraft.bookswithoutborders.command.CommandAddTitlePage; import net.knarcraft.bookswithoutborders.command.CommandBooksWithoutBorders; import net.knarcraft.bookswithoutborders.command.CommandClear; import net.knarcraft.bookswithoutborders.command.CommandCopy; import net.knarcraft.bookswithoutborders.command.CommandDecrypt; import net.knarcraft.bookswithoutborders.command.CommandDelete; +import net.knarcraft.bookswithoutborders.command.CommandDeletePage; import net.knarcraft.bookswithoutborders.command.CommandDeletePublic; import net.knarcraft.bookswithoutborders.command.CommandEncrypt; import net.knarcraft.bookswithoutborders.command.CommandFormat; @@ -230,6 +232,8 @@ public class BooksWithoutBorders extends JavaPlugin { registerCommand("setBookGeneration", new CommandSetGeneration()); registerCommand("clearBook", new CommandClear()); registerCommand("setBookshelfData", new CommandSetBookshelfData()); + registerCommand("addBookTitlePage", new CommandAddTitlePage()); + registerCommand("deleteBookPage", new CommandDeletePage()); } /** diff --git a/src/main/java/net/knarcraft/bookswithoutborders/command/CommandAddTitlePage.java b/src/main/java/net/knarcraft/bookswithoutborders/command/CommandAddTitlePage.java new file mode 100644 index 0000000..7774ed8 --- /dev/null +++ b/src/main/java/net/knarcraft/bookswithoutborders/command/CommandAddTitlePage.java @@ -0,0 +1,155 @@ +package net.knarcraft.bookswithoutborders.command; + +import net.knarcraft.bookswithoutborders.BooksWithoutBorders; +import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig; +import net.knarcraft.bookswithoutborders.utility.BookFormatter; +import net.knarcraft.bookswithoutborders.utility.InventoryHelper; +import net.md_5.bungee.api.ChatColor; +import org.bukkit.Material; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.command.TabExecutor; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.BookMeta; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +/** + * A command for adding a title page to a book + */ +public class CommandAddTitlePage implements TabExecutor { + + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String s, + @NotNull String[] arguments) { + if (!(sender instanceof Player player)) { + BooksWithoutBorders.sendErrorMessage(sender, "This command can only be used by a player!"); + return false; + } + + + ItemStack heldBook = InventoryHelper.getHeldBook(player); + if (heldBook == null) { + BooksWithoutBorders.sendErrorMessage(sender, "You must be holding a book to perform this command"); + return false; + } + + int index; + if (arguments.length < 1) { + if (InventoryHelper.notHoldingOneWrittenBookCheck(player, "You must be holding a written book to " + + "add an author title page!", "You cannot add an author title page to two books at once!")) { + return false; + } + + index = 0; + } else { + try { + index = Integer.parseInt(arguments[0]) - 1; + } catch (NumberFormatException exception) { + BooksWithoutBorders.sendErrorMessage(sender, "Invalid page index given!"); + return false; + } + } + + String title = null; + + if (arguments.length > 1) { + // Get all arguments as a space-separated string + StringBuilder builder = new StringBuilder(arguments[1]); + for (int i = 2; i < arguments.length; i++) { + builder.append(" ").append(arguments[i]); + } + title = builder.toString(); + } + + BookMeta bookMeta = (BookMeta) heldBook.getItemMeta(); + if (bookMeta == null) { + BooksWithoutBorders.sendErrorMessage(sender, "Unable to get metadata for the held book!"); + return false; + } + List pages = new ArrayList<>(bookMeta.getPages()); + + if (index < 0) { + BooksWithoutBorders.sendErrorMessage(sender, "The given page index is out of bounds!"); + return false; + } + + if (title == null && heldBook.getType() == Material.WRITTEN_BOOK) { + String loreSeparator = BooksWithoutBordersConfig.getLoreSeparator(); + if (index > pages.size()) { + pages.add(formatTitle(bookMeta.getTitle() + loreSeparator + "By: " + bookMeta.getAuthor())); + } else { + pages.add(index, formatTitle(bookMeta.getTitle() + loreSeparator + "By: " + bookMeta.getAuthor())); + } + } else if (title == null) { + if (index > pages.size()) { + pages.add(""); + } else { + pages.add(index, ""); + } + } else { + if (index > pages.size()) { + pages.add(formatTitle(title)); + } else { + pages.add(index, formatTitle(title)); + } + } + bookMeta.setPages(pages); + heldBook.setItemMeta(bookMeta); + BooksWithoutBorders.sendSuccessMessage(sender, "Title page added!"); + return true; + } + + /** + * Formats a book title + * + * @param input

The input to format

+ * @return

The formatted input

+ */ + private String formatTitle(@NotNull String input) { + String loreSeparator = BooksWithoutBordersConfig.getLoreSeparator(); + if (input.contains(loreSeparator)) { + String[] parts = input.split(loreSeparator); + StringBuilder output = new StringBuilder("\n"); + output.append(ChatColor.UNDERLINE).append(ChatColor.BOLD).append(BookFormatter.stripColor(parts[0])).append(ChatColor.RESET); + + for (int i = 1; i < parts.length; i++) { + output.append("\n").append("\n").append(ChatColor.ITALIC).append(BookFormatter.stripColor(parts[i])).append(ChatColor.RESET); + } + return output.toString(); + } else { + return ChatColor.UNDERLINE + ChatColor.BOLD.toString() + input; + } + } + + @Nullable + @Override + public List onTabComplete(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s, + @NotNull String[] arguments) { + if (arguments.length == 1) { + if (!(commandSender instanceof Player player)) { + return List.of("1", "2", "3", "4"); + } + ItemStack heldBook = InventoryHelper.getHeldBook(player); + if (heldBook != null) { + BookMeta bookMeta = (BookMeta) heldBook.getItemMeta(); + if (bookMeta != null) { + List pages = new ArrayList<>(); + pages.add("1"); + for (int i = 1; i <= bookMeta.getPages().size(); i++) { + pages.add(String.valueOf(i + 1)); + } + return pages; + } + } + } else if (arguments.length == 2) { + return List.of("Title", "Chapter~Description"); + } + return List.of(); + } + +} diff --git a/src/main/java/net/knarcraft/bookswithoutborders/command/CommandDeletePage.java b/src/main/java/net/knarcraft/bookswithoutborders/command/CommandDeletePage.java new file mode 100644 index 0000000..894ee48 --- /dev/null +++ b/src/main/java/net/knarcraft/bookswithoutborders/command/CommandDeletePage.java @@ -0,0 +1,91 @@ +package net.knarcraft.bookswithoutborders.command; + +import net.knarcraft.bookswithoutborders.BooksWithoutBorders; +import net.knarcraft.bookswithoutborders.utility.InventoryHelper; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.command.TabExecutor; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.BookMeta; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +/** + * A command for deleting a single page from a book + */ +public class CommandDeletePage implements TabExecutor { + + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String s, @NotNull String[] arguments) { + if (!(sender instanceof Player player)) { + BooksWithoutBorders.sendErrorMessage(sender, "This command can only be used by a player!"); + return false; + } + + if (arguments.length == 0) { + BooksWithoutBorders.sendErrorMessage(sender, "You must supply a page index"); + return false; + } + + ItemStack heldBook = InventoryHelper.getHeldBook(player); + if (heldBook == null) { + BooksWithoutBorders.sendErrorMessage(sender, "You must be holding a book to perform this command"); + return false; + } + + int index; + try { + index = Integer.parseInt(arguments[0]) - 1; + } catch (NumberFormatException exception) { + BooksWithoutBorders.sendErrorMessage(sender, "Invalid page index given!"); + return false; + } + + BookMeta bookMeta = (BookMeta) heldBook.getItemMeta(); + if (bookMeta == null) { + BooksWithoutBorders.sendErrorMessage(sender, "Unable to get metadata for the held book!"); + return false; + } + List pages = new ArrayList<>(bookMeta.getPages()); + + if (index < 0 || index >= pages.size()) { + BooksWithoutBorders.sendErrorMessage(sender, "The given page index is out of bounds!"); + return false; + } + + pages.remove(index); + + bookMeta.setPages(pages); + heldBook.setItemMeta(bookMeta); + BooksWithoutBorders.sendSuccessMessage(sender, "Page deleted!"); + return true; + } + + @Nullable + @Override + public List onTabComplete(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s, + @NotNull String[] arguments) { + if (arguments.length == 1) { + if (!(commandSender instanceof Player player)) { + return List.of("1", "2", "3", "4"); + } + ItemStack heldBook = InventoryHelper.getHeldBook(player); + if (heldBook != null) { + BookMeta bookMeta = (BookMeta) heldBook.getItemMeta(); + if (bookMeta != null) { + List pages = new ArrayList<>(); + for (int i = 0; i < bookMeta.getPages().size(); i++) { + pages.add(String.valueOf(i + 1)); + } + return pages; + } + } + } + return List.of(); + } + +} diff --git a/src/main/java/net/knarcraft/bookswithoutborders/utility/InventoryHelper.java b/src/main/java/net/knarcraft/bookswithoutborders/utility/InventoryHelper.java index 9a9cfb8..e6d8ea4 100644 --- a/src/main/java/net/knarcraft/bookswithoutborders/utility/InventoryHelper.java +++ b/src/main/java/net/knarcraft/bookswithoutborders/utility/InventoryHelper.java @@ -18,6 +18,25 @@ public final class InventoryHelper { private InventoryHelper() { } + /** + * Gets the book from a player's main hand + * + * @param player

The player holding the book

+ * @return

The held book, or null if not holding one book in the main hand

+ */ + public static ItemStack getHeldBook(@NotNull Player player) { + @NotNull ItemSlot heldSigned = InventoryHelper.getHeldSlotBook(player, true, true, + true, true); + @NotNull ItemSlot heldUnSigned = InventoryHelper.getHeldSlotBook(player, true, true, + true, false); + if (heldSigned == ItemSlot.MAIN_HAND || heldUnSigned == ItemSlot.MAIN_HAND) { + boolean holdingSigned = heldSigned == ItemSlot.MAIN_HAND; + return InventoryHelper.getHeldBook(player, holdingSigned); + } else { + return null; + } + } + /** * Gets the book the holder is playing * diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index eff96e3..b8aa604 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -101,6 +101,14 @@ commands: description: Sets custom data for a chiseled bookshelf used when peeking at the bookshelf usage: / [more text] permission: bookswithoutborders.editbookshelf + addBookTitlePage: + description: Adds a blank page, title page or chapter page depending on input and whether the book is signed + usage: / [page index] [title~description] + permission: bookswithoutborders.addtitlepage + deleteBookPage: + description: Deletes one page from a book + usage: / + permission: bookswithoutborders.deletepage permissions: bookswithoutborders.*: description: Grants all permissions @@ -147,6 +155,8 @@ permissions: bookswithoutborders.setlore: true bookswithoutborders.format: true bookswithoutborders.setgeneration: true + bookswithoutborders.addtitlepage: true + bookswithoutborders.deletepage: true bookswithoutborders.format: description: Allows a player to format a book bookswithoutborders.save: @@ -200,4 +210,8 @@ permissions: bookswithoutborders.peekbookshelf: description: Allows player to left-click a bookshelf to see the contents of the shelf bookswithoutborders.editbookshelf: - description: Allows player to set name/lore for bookshelves, used for peeking \ No newline at end of file + description: Allows player to set name/lore for bookshelves, used for peeking + bookswithoutborders.addtitlepage: + description: Allows player to add a blank title page to a book + bookswithoutborders.deletepage: + description: Allows player to delete a page from a book \ No newline at end of file