From b2ce31234d5785361f3349ff32c8cb8d9f4ed6f1 Mon Sep 17 00:00:00 2001 From: EpicKnarvik97 Date: Sun, 3 Aug 2025 17:23:19 +0200 Subject: [PATCH] Adds ability to set name and lore shown when chiseled bookshelves are peeked --- .../BooksWithoutBorders.java | 15 ++ .../command/CommandBooksWithoutBorders.java | 3 + .../command/CommandSetBookshelfData.java | 111 ++++++++++++++ .../command/CommandSetLore.java | 3 +- .../container/Bookshelf.java | 81 ++++++++++ .../handler/BookshelfHandler.java | 145 ++++++++++++++++++ .../listener/BookshelfListener.java | 42 ++++- src/main/resources/plugin.yml | 9 +- 8 files changed, 403 insertions(+), 6 deletions(-) create mode 100644 src/main/java/net/knarcraft/bookswithoutborders/command/CommandSetBookshelfData.java create mode 100644 src/main/java/net/knarcraft/bookswithoutborders/container/Bookshelf.java create mode 100644 src/main/java/net/knarcraft/bookswithoutborders/handler/BookshelfHandler.java diff --git a/src/main/java/net/knarcraft/bookswithoutborders/BooksWithoutBorders.java b/src/main/java/net/knarcraft/bookswithoutborders/BooksWithoutBorders.java index 6b390bb..a6f59ab 100644 --- a/src/main/java/net/knarcraft/bookswithoutborders/BooksWithoutBorders.java +++ b/src/main/java/net/knarcraft/bookswithoutborders/BooksWithoutBorders.java @@ -18,11 +18,13 @@ import net.knarcraft.bookswithoutborders.command.CommandSave; import net.knarcraft.bookswithoutborders.command.CommandSavePublic; import net.knarcraft.bookswithoutborders.command.CommandSetAuthor; import net.knarcraft.bookswithoutborders.command.CommandSetBookPrice; +import net.knarcraft.bookswithoutborders.command.CommandSetBookshelfData; import net.knarcraft.bookswithoutborders.command.CommandSetGeneration; import net.knarcraft.bookswithoutborders.command.CommandSetLore; import net.knarcraft.bookswithoutborders.command.CommandSetTitle; import net.knarcraft.bookswithoutborders.command.CommandUnSign; import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig; +import net.knarcraft.bookswithoutborders.handler.BookshelfHandler; import net.knarcraft.bookswithoutborders.listener.BookEventListener; import net.knarcraft.bookswithoutborders.listener.BookshelfListener; import net.knarcraft.bookswithoutborders.listener.PlayerEventListener; @@ -68,6 +70,7 @@ public class BooksWithoutBorders extends JavaPlugin { private static Map> playerLetterIndex; private static BooksWithoutBorders booksWithoutBorders; private static ConsoleCommandSender consoleSender; + private static BookshelfHandler bookshelfHandler; /** * Gets the console sender for printing to the console @@ -170,6 +173,8 @@ public class BooksWithoutBorders extends JavaPlugin { publicBooksList = files; publicLetterIndex = BookFileHelper.populateLetterIndices(files); } + bookshelfHandler = new BookshelfHandler(); + bookshelfHandler.load(); PluginManager pluginManager = this.getServer().getPluginManager(); @@ -224,6 +229,7 @@ public class BooksWithoutBorders extends JavaPlugin { registerCommand("formatBook", new CommandFormat()); registerCommand("setBookGeneration", new CommandSetGeneration()); registerCommand("clearBook", new CommandClear()); + registerCommand("setBookshelfData", new CommandSetBookshelfData()); } /** @@ -268,6 +274,15 @@ public class BooksWithoutBorders extends JavaPlugin { return testFileSaving(); } + /** + * Gets the bookshelf handler + * + * @return

The bookshelf handler

+ */ + public static BookshelfHandler getBookshelfHandler() { + return bookshelfHandler; + } + /** * Gets the server's item factory * diff --git a/src/main/java/net/knarcraft/bookswithoutborders/command/CommandBooksWithoutBorders.java b/src/main/java/net/knarcraft/bookswithoutborders/command/CommandBooksWithoutBorders.java index 24ccbc6..f20923a 100644 --- a/src/main/java/net/knarcraft/bookswithoutborders/command/CommandBooksWithoutBorders.java +++ b/src/main/java/net/knarcraft/bookswithoutborders/command/CommandBooksWithoutBorders.java @@ -69,6 +69,7 @@ public class CommandBooksWithoutBorders implements TabExecutor { } sender.sendMessage(getCommandColor() + "Commands:"); + showCommandInfo("booksWithoutBorders", sender); showCommandInfo("copyBook", sender); showCommandInfo("decryptBook", sender); showCommandInfo("deleteBook", sender); @@ -89,6 +90,8 @@ public class CommandBooksWithoutBorders implements TabExecutor { showCommandInfo("setLore", sender); showCommandInfo("setTitle", sender); showCommandInfo("unsignBook", sender); + showCommandInfo("clearBook", sender); + showCommandInfo("setBookshelfData", sender); } /** diff --git a/src/main/java/net/knarcraft/bookswithoutborders/command/CommandSetBookshelfData.java b/src/main/java/net/knarcraft/bookswithoutborders/command/CommandSetBookshelfData.java new file mode 100644 index 0000000..4ee893c --- /dev/null +++ b/src/main/java/net/knarcraft/bookswithoutborders/command/CommandSetBookshelfData.java @@ -0,0 +1,111 @@ +package net.knarcraft.bookswithoutborders.command; + +import net.knarcraft.bookswithoutborders.BooksWithoutBorders; +import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig; +import net.knarcraft.bookswithoutborders.container.Bookshelf; +import net.knarcraft.bookswithoutborders.handler.BookshelfHandler; +import net.knarcraft.knarlib.util.TabCompletionHelper; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.command.TabExecutor; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * The command for setting information for a chiseled bookshelf + */ +public class CommandSetBookshelfData implements TabExecutor { + + @Override + public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s, + @NotNull String[] arguments) { + if (!(commandSender instanceof Player player)) { + BooksWithoutBorders.sendErrorMessage(commandSender, "This command must be used by a player!"); + return false; + } + + Block targetBlock = player.getTargetBlockExact(7); + if (targetBlock == null || targetBlock.getType() != Material.CHISELED_BOOKSHELF) { + BooksWithoutBorders.sendErrorMessage(commandSender, "You are not looking at a bookshelf!"); + return false; + } + + BookshelfHandler shelfHandler = BooksWithoutBorders.getBookshelfHandler(); + Bookshelf bookshelf = shelfHandler.getFromLocation(targetBlock.getLocation()); + + if (arguments.length < 2) { + if (arguments.length == 1 && arguments[0].equalsIgnoreCase("delete")) { + if (bookshelf != null) { + shelfHandler.unregisterBookshelf(bookshelf); + shelfHandler.save(); + BooksWithoutBorders.sendSuccessMessage(commandSender, "Bookshelf successfully deleted"); + } else { + BooksWithoutBorders.sendErrorMessage(commandSender, "The block you are looking at is not a registered bookshelf"); + } + return true; + } else { + return false; + } + } + + // 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]); + } + + switch (arguments[0].toLowerCase()) { + case "name": + if (bookshelf == null) { + Bookshelf newShelf = new Bookshelf(targetBlock.getLocation(), arguments[1], new ArrayList<>()); + shelfHandler.registerBookshelf(newShelf); + } else { + bookshelf.setTitle(builder.toString()); + } + shelfHandler.save(); + BooksWithoutBorders.sendSuccessMessage(commandSender, "Title successfully saved"); + return true; + case "lore": + if (bookshelf == null) { + BooksWithoutBorders.sendErrorMessage(commandSender, "You must name the bookshelf before " + + "assigning lore!"); + } else { + List loreParts = Arrays.asList(builder.toString().split(BooksWithoutBordersConfig.getLoreSeparator())); + bookshelf.setLore(loreParts); + shelfHandler.save(); + BooksWithoutBorders.sendSuccessMessage(commandSender, "Lore successfully saved"); + } + return true; + } + + return false; + } + + @Nullable + @Override + public List onTabComplete(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s, + @NotNull String[] arguments) { + if (arguments.length == 1) { + return TabCompletionHelper.filterMatchingStartsWith(List.of("delete", "name", "lore"), arguments[0]); + } else if (arguments.length == 2) { + return switch (arguments[0].toLowerCase()) { + case "delete" -> new ArrayList<>(); + case "name" -> + TabCompletionHelper.filterMatchingStartsWith(List.of("Epic Title", "Lame Title"), arguments[1]); + case "lore" -> + TabCompletionHelper.filterMatchingStartsWith(List.of("Interesting lore", "Line1~Line2~Line3"), arguments[1]); + default -> null; + }; + } else { + return List.of(); + } + } + +} diff --git a/src/main/java/net/knarcraft/bookswithoutborders/command/CommandSetLore.java b/src/main/java/net/knarcraft/bookswithoutborders/command/CommandSetLore.java index eeac959..aeb1ace 100644 --- a/src/main/java/net/knarcraft/bookswithoutborders/command/CommandSetLore.java +++ b/src/main/java/net/knarcraft/bookswithoutborders/command/CommandSetLore.java @@ -47,8 +47,7 @@ public class CommandSetLore implements TabExecutor { //Format lore rawLore = ColorHelper.translateColorCodes(rawLore, ColorConversion.RGB); - String[] loreParts = rawLore.split(BooksWithoutBordersConfig.getLoreSeparator()); - List newLore = new ArrayList<>(Arrays.asList(loreParts)); + List newLore = Arrays.asList(rawLore.split(BooksWithoutBordersConfig.getLoreSeparator())); //Update lore ItemMeta meta = heldItem.getItemMeta(); diff --git a/src/main/java/net/knarcraft/bookswithoutborders/container/Bookshelf.java b/src/main/java/net/knarcraft/bookswithoutborders/container/Bookshelf.java new file mode 100644 index 0000000..9d43e30 --- /dev/null +++ b/src/main/java/net/knarcraft/bookswithoutborders/container/Bookshelf.java @@ -0,0 +1,81 @@ +package net.knarcraft.bookswithoutborders.container; + +import org.bukkit.Location; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +/** + * A representation of a bookshelf with extra data used when displaying its contents + */ +public class Bookshelf { + + private final @NotNull Location location; + private @NotNull String title; + private @NotNull List lore; + + /** + * Instantiates a new bookshelf + * + * @param location

The location of the bookshelf

+ * @param title

The title of the bookshelf

+ * @param lore

The lore of the bookshelf

+ */ + public Bookshelf(@NotNull Location location, @NotNull String title, @NotNull List lore) { + this.location = location; + this.title = title; + this.lore = lore; + } + + /** + * Gets the location of this bookshelf + * + * @return

The location of this bookshelf

+ */ + @NotNull + public Location getLocation() { + return this.location; + } + + /** + * Gets the title of this bookshelf + * + * @return

The title of this bookshelf

+ */ + @NotNull + public String getTitle() { + return this.title; + } + + /** + * Gets the lore of this bookshelf + * + * @return

The lore of this bookshelf

+ */ + @NotNull + public List getLore() { + return this.lore; + } + + /** + * Sets the title of this bookshelf + * + * @param title

The new title

+ */ + public void setTitle(@NotNull String title) { + if (title.isBlank()) { + throw new IllegalArgumentException("Bookshelves cannot have empty titles!"); + } + this.title = title; + } + + /** + * Sets the lore of this bookshelf + * + * @param lore

The new lore

+ */ + public void setLore(@NotNull List lore) { + this.lore = lore; + } + +} diff --git a/src/main/java/net/knarcraft/bookswithoutborders/handler/BookshelfHandler.java b/src/main/java/net/knarcraft/bookswithoutborders/handler/BookshelfHandler.java new file mode 100644 index 0000000..d1aea42 --- /dev/null +++ b/src/main/java/net/knarcraft/bookswithoutborders/handler/BookshelfHandler.java @@ -0,0 +1,145 @@ +package net.knarcraft.bookswithoutborders.handler; + +import net.knarcraft.bookswithoutborders.BooksWithoutBorders; +import net.knarcraft.bookswithoutborders.container.Bookshelf; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.YamlConfiguration; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.logging.Level; + +/** + * A handler keeping track of all bookshelves + */ +public class BookshelfHandler { + + private static final File bookshelfFile = new File(BooksWithoutBorders.getInstance().getDataFolder(), + "bookShelves.yml"); + private Set bookShelves; + private Map locationLookup; + + /** + * Gets a bookshelf from the given location + * + * @param location

The location of the bookshelf

+ * @return

The bookshelf at the location, or null if no such bookshelf exists

+ */ + @Nullable + public Bookshelf getFromLocation(@NotNull Location location) { + return locationLookup.get(location); + } + + /** + * Registers the given bookshelf to this handler + * + * @param bookshelf

The bookshelf to register

+ */ + public void registerBookshelf(@NotNull Bookshelf bookshelf) { + this.bookShelves.add(bookshelf); + this.locationLookup.put(bookshelf.getLocation(), bookshelf); + } + + /** + * Unregisters the given bookshelf from this handler + * + * @param bookshelf

The bookshelf to unregister

+ */ + public void unregisterBookshelf(@NotNull Bookshelf bookshelf) { + this.locationLookup.remove(bookshelf.getLocation()); + this.bookShelves.remove(bookshelf); + } + + /** + * Loads all stored bookshelves + */ + public void load() { + this.bookShelves = new HashSet<>(); + this.locationLookup = new HashMap<>(); + + YamlConfiguration configuration = YamlConfiguration.loadConfiguration(bookshelfFile); + ConfigurationSection bookshelfSection = configuration.getConfigurationSection("bookshelves"); + if (bookshelfSection == null) { + BooksWithoutBorders.getInstance().getLogger().log(Level.INFO, + "BooksWithoutBorders found no bookshelves to load"); + return; + } + + for (String key : bookshelfSection.getKeys(false)) { + String[] locationInfo = key.split(","); + World world = Bukkit.getWorld(UUID.fromString(locationInfo[0])); + double x = Integer.parseInt(locationInfo[1]); + double y = Integer.parseInt(locationInfo[2]); + double z = Integer.parseInt(locationInfo[3]); + Location bookshelfLocation = new Location(world, x, y, z); + + String titleKey = key + ".title"; + String loreKey = key + ".lore"; + + String title = bookshelfSection.getString(titleKey, null); + List loreStrings = new ArrayList<>(); + List lore = bookshelfSection.getList(loreKey); + if (lore == null) { + throw new IllegalArgumentException("Lore is missing from bookshelf data!"); + } + lore.forEach((item) -> { + if (item instanceof String) { + loreStrings.add((String) item); + } + }); + + if (title != null) { + registerBookshelf(new Bookshelf(bookshelfLocation, title, loreStrings)); + } + } + } + + /** + * Saves all current bookshelves + */ + public void save() { + try { + YamlConfiguration configuration = new YamlConfiguration(); + ConfigurationSection bookshelfSection = configuration.createSection("bookshelves"); + for (Bookshelf bookshelf : bookShelves) { + saveBookshelf(bookshelfSection, bookshelf); + } + configuration.save(bookshelfFile); + } catch (IOException exception) { + BooksWithoutBorders.getInstance().getLogger().log(Level.SEVERE, "Unable to save bookshelves!"); + } + } + + /** + * Saves a bookshelf to the given configuration section + * + * @param section

The configuration section to save to

+ * @param bookshelf

The bookshelf to save

+ */ + private void saveBookshelf(@NotNull ConfigurationSection section, @NotNull Bookshelf bookshelf) { + Location location = bookshelf.getLocation(); + if (location.getWorld() == null) { + return; + } + + String key = location.getWorld().getUID() + "," + location.getBlockX() + "," + location.getBlockY() + + "," + location.getBlockZ(); + String titleKey = key + ".title"; + String loreKey = key + ".lore"; + section.set(titleKey, bookshelf.getTitle()); + section.set(loreKey, bookshelf.getLore()); + } + +} diff --git a/src/main/java/net/knarcraft/bookswithoutborders/listener/BookshelfListener.java b/src/main/java/net/knarcraft/bookswithoutborders/listener/BookshelfListener.java index 95857c8..968d25e 100644 --- a/src/main/java/net/knarcraft/bookswithoutborders/listener/BookshelfListener.java +++ b/src/main/java/net/knarcraft/bookswithoutborders/listener/BookshelfListener.java @@ -1,15 +1,23 @@ package net.knarcraft.bookswithoutborders.listener; +import net.knarcraft.bookswithoutborders.BooksWithoutBorders; import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig; +import net.knarcraft.bookswithoutborders.container.Bookshelf; +import net.knarcraft.bookswithoutborders.handler.BookshelfHandler; import net.knarcraft.bookswithoutborders.utility.IntegerToRomanConverter; import net.md_5.bungee.api.ChatColor; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; import org.bukkit.block.ChiseledBookshelf; import org.bukkit.enchantments.Enchantment; import org.bukkit.entity.Player; import org.bukkit.event.Event; import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.block.Action; +import org.bukkit.event.block.BlockBreakEvent; import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.inventory.ChiseledBookshelfInventory; import org.bukkit.inventory.ItemStack; @@ -27,6 +35,22 @@ import java.util.Map; */ public class BookshelfListener implements Listener { + @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) + public void onBookshelfBreak(@NotNull BlockBreakEvent event) { + Block block = event.getBlock(); + + if (block.getType() != Material.CHISELED_BOOKSHELF) { + return; + } + + BookshelfHandler bookshelfHandler = BooksWithoutBorders.getBookshelfHandler(); + Bookshelf bookshelf = bookshelfHandler.getFromLocation(block.getLocation()); + if (bookshelf != null) { + bookshelfHandler.unregisterBookshelf(bookshelf); + bookshelfHandler.save(); + } + } + @EventHandler public void onBookshelfClick(@NotNull PlayerInteractEvent event) { Player player = event.getPlayer(); @@ -48,19 +72,31 @@ public class BookshelfListener implements Listener { event.setUseItemInHand(Event.Result.DENY); ChiseledBookshelfInventory bookshelfInventory = chiseledBookshelf.getInventory(); - player.sendMessage(getBookshelfDescription(bookshelfInventory)); + player.sendMessage(getBookshelfDescription(bookshelfInventory, event.getClickedBlock().getLocation())); } /** * Gets the description for a bookshelf's contents * * @param bookshelfInventory

The inventory of the bookshelf to describe

+ * @param location

The location of the clicked bookshelf

* @return

A textual description of the bookshelf's contents

*/ @NotNull - private String getBookshelfDescription(@NotNull ChiseledBookshelfInventory bookshelfInventory) { + private String getBookshelfDescription(@NotNull ChiseledBookshelfInventory bookshelfInventory, @NotNull Location location) { StringBuilder builder = new StringBuilder(); - builder.append(ChatColor.of("#FF5700")).append("Books in shelf:").append(ChatColor.RESET); + + Bookshelf bookshelf = BooksWithoutBorders.getBookshelfHandler().getFromLocation(location); + if (bookshelf != null) { + builder.append(ChatColor.of("#FF5700")).append("Books in ").append(bookshelf.getTitle()) + .append(":").append(ChatColor.RESET); + for (String lore : bookshelf.getLore()) { + builder.append("\n ").append(ChatColor.LIGHT_PURPLE).append(lore); + } + } else { + builder.append(ChatColor.of("#FF5700")).append("Books in shelf:").append(ChatColor.RESET); + } + for (int i = 0; i < bookshelfInventory.getSize(); i++) { int index = (i % 3) + 1; if (i % 3 == 0) { diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index e9a926d..eff96e3 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -97,6 +97,10 @@ commands: description: Reloads BwB's configuration file usage: / permission: bookswithoutborders.reload + setBookshelfData: + description: Sets custom data for a chiseled bookshelf used when peeking at the bookshelf + usage: / [more text] + permission: bookswithoutborders.editbookshelf permissions: bookswithoutborders.*: description: Grants all permissions @@ -125,6 +129,7 @@ permissions: bookswithoutborders.setbookprice: true bookswithoutborders.reload: true bookswithoutborders.setgeneration: true + bookswithoutborders.editbookshelf: true bookswithoutborders.use: description: Allows player to use commands to save/load/delete in their personal directory, and peeking at bookshelves if enabled children: @@ -193,4 +198,6 @@ permissions: bookswithoutborders.setgeneration: description: Allows player to change the generation of a book (Original, Copy, Copy of Copy) bookswithoutborders.peekbookshelf: - description: Allows player to left-click a bookshelf to see the contents of the shelf \ No newline at end of file + 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