15 Commits
1.3.6 ... dev

Author SHA1 Message Date
36b57b9191 Removes some debug output, and fixes some formatting
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-08-02 18:07:52 +02:00
0d5ad490ff Fixes sorting and character indexes for filenames with color codes
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-08-02 18:02:33 +02:00
9e300afbef Adds missing spacing when replacing underscores in book names
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-08-02 08:02:09 +02:00
a84a56391a Prevents the § from being used in filenames
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-08-02 07:52:05 +02:00
b15ad18ae3 Makes it easier to manually go to any book page
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-08-02 07:09:07 +02:00
67ccdf3b1d Removes spacing for letter search
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-08-02 06:58:36 +02:00
b9bd686ae9 Removes the unnecessary display of manual command input in the book menu
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-08-02 06:52:23 +02:00
a5be6bb72c Adds ability to easier find books by first letter
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-08-02 06:47:46 +02:00
ed0a750eb4 Improves the book list somewhat
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-08-02 03:33:44 +02:00
6aa422d461 Fixes wrong color on the inactive next button
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-08-01 21:05:03 +02:00
4be023bd63 Adds a ChatComponent-enhanced book list
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-08-01 20:59:16 +02:00
35e98e0f18 Bumps version for development
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-07-28 04:24:40 +02:00
74d59bf71b Bump version for release
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-07-28 04:18:54 +02:00
a1ed6b9566 Re-implements depreciated enchantment key getting
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-07-28 04:15:53 +02:00
3095586d2b Bumps version for development
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-07-27 21:03:15 +02:00
14 changed files with 297 additions and 56 deletions

View File

@@ -6,7 +6,7 @@
<groupId>net.knarcraft</groupId> <groupId>net.knarcraft</groupId>
<artifactId>BooksWithoutBorders</artifactId> <artifactId>BooksWithoutBorders</artifactId>
<version>1.3.6</version> <version>1.3.9-SNAPSHOT</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<licenses> <licenses>

View File

@@ -39,12 +39,15 @@ import org.bukkit.inventory.ItemFactory;
import org.bukkit.plugin.PluginDescriptionFile; import org.bukkit.plugin.PluginDescriptionFile;
import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.UUID; import java.util.UUID;
import java.util.logging.Level; import java.util.logging.Level;
@@ -61,6 +64,8 @@ public class BooksWithoutBorders extends JavaPlugin {
private static ItemFactory itemFactory; private static ItemFactory itemFactory;
private static Map<UUID, List<String>> playerBooksList; private static Map<UUID, List<String>> playerBooksList;
private static List<String> publicBooksList; private static List<String> publicBooksList;
private static Map<Character, Integer> publicLetterIndex;
private static Map<UUID, Map<Character, Integer>> playerLetterIndex;
private static BooksWithoutBorders booksWithoutBorders; private static BooksWithoutBorders booksWithoutBorders;
private static ConsoleCommandSender consoleSender; private static ConsoleCommandSender consoleSender;
@@ -88,6 +93,7 @@ public class BooksWithoutBorders extends JavaPlugin {
if (!playerBooksList.containsKey(playerUUID)) { if (!playerBooksList.containsKey(playerUUID)) {
List<String> newFiles = BookFileHelper.listFiles(sender, false); List<String> newFiles = BookFileHelper.listFiles(sender, false);
playerBooksList.put(playerUUID, newFiles); playerBooksList.put(playerUUID, newFiles);
playerLetterIndex.put(playerUUID, BookFileHelper.populateLetterIndices(newFiles));
} }
return playerBooksList.get(playerUUID); return playerBooksList.get(playerUUID);
} else { } else {
@@ -95,21 +101,40 @@ public class BooksWithoutBorders extends JavaPlugin {
} }
} }
/**
* Gets the letter index map for public books, or a specific player's books
*
* @param playerIndex <p>The player to get the index for, or null for the public index</p>
* @return <p>An index mapping between a character and the first index containing that character</p>
*/
@NotNull
public static Map<Character, Integer> getLetterIndex(@Nullable UUID playerIndex) {
if (playerIndex == null) {
return publicLetterIndex;
} else {
Map<Character, Integer> letterIndex = playerLetterIndex.get(playerIndex);
return Objects.requireNonNullElseGet(letterIndex, HashMap::new);
}
}
/** /**
* Updates available books * Updates available books
* *
* @param sender <p>The sender to update books for</p> * @param sender <p>The sender to update books for</p>
* @param updatePublic <p>Whether to update public books</p> * @param updatePublic <p>Whether to update public books</p>
*/ */
public static void updateBooks(CommandSender sender, boolean updatePublic) { public static void updateBooks(@NotNull CommandSender sender, boolean updatePublic) {
List<String> newFiles = BookFileHelper.listFiles(sender, updatePublic); List<String> newFiles = BookFileHelper.listFiles(sender, updatePublic);
if (updatePublic) { if (updatePublic) {
publicBooksList = newFiles; publicBooksList = newFiles;
publicLetterIndex = BookFileHelper.populateLetterIndices(newFiles);
} else if (sender instanceof Player player) { } else if (sender instanceof Player player) {
playerBooksList.put(player.getUniqueId(), newFiles); playerBooksList.put(player.getUniqueId(), newFiles);
playerLetterIndex.put(player.getUniqueId(), BookFileHelper.populateLetterIndices(newFiles));
} }
} }
@Override @Override
public void onEnable() { public void onEnable() {
FileConfiguration config = this.getConfig(); FileConfiguration config = this.getConfig();
@@ -123,8 +148,10 @@ public class BooksWithoutBorders extends JavaPlugin {
booksWithoutBorders = this; booksWithoutBorders = this;
consoleSender = this.getServer().getConsoleSender(); consoleSender = this.getServer().getConsoleSender();
playerBooksList = new HashMap<>(); playerBooksList = new HashMap<>();
playerLetterIndex = new HashMap<>();
BooksWithoutBordersConfig.initialize(this); BooksWithoutBordersConfig.initialize(this);
publicBooksList = BookFileHelper.listFiles(consoleSender, true); publicBooksList = BookFileHelper.listFiles(consoleSender, true);
publicLetterIndex = BookFileHelper.populateLetterIndices(publicBooksList);
PluginManager pluginManager = this.getServer().getPluginManager(); PluginManager pluginManager = this.getServer().getPluginManager();

View File

@@ -4,6 +4,7 @@ import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.state.BookDirectory; import net.knarcraft.bookswithoutborders.state.BookDirectory;
import net.knarcraft.bookswithoutborders.utility.BookFileHelper; import net.knarcraft.bookswithoutborders.utility.BookFileHelper;
import net.knarcraft.bookswithoutborders.utility.BookHelper; import net.knarcraft.bookswithoutborders.utility.BookHelper;
import net.knarcraft.bookswithoutborders.utility.InputCleaningHelper;
import net.knarcraft.knarlib.util.TabCompletionHelper; import net.knarcraft.knarlib.util.TabCompletionHelper;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
@@ -39,13 +40,20 @@ public class CommandDelete implements TabExecutor {
* @return <p>True if the book was deleted successfully</p> * @return <p>True if the book was deleted successfully</p>
*/ */
boolean deleteBook(CommandSender sender, String[] args, boolean deletePublic) { boolean deleteBook(CommandSender sender, String[] args, boolean deletePublic) {
String command = deletePublic ? "deletepublicbook" : "deletebook";
//List deletable files //List deletable files
if (args.length == 0) { if (args.length == 0) {
BookFileHelper.printBooks(sender, deletePublic); BookFileHelper.printBooks(sender, deletePublic, command, 1);
return true; return true;
} }
//Delete the file //Delete the file
if (args.length == 1) { if (args.length == 1) {
int page = InputCleaningHelper.parsePageNumber(args[0]);
if (page > 0) {
BookFileHelper.printBooks(sender, deletePublic, command, page);
return true;
}
List<String> availableBooks = BooksWithoutBorders.getAvailableBooks(sender, deletePublic); List<String> availableBooks = BooksWithoutBorders.getAvailableBooks(sender, deletePublic);
if (!availableBooks.isEmpty()) { if (!availableBooks.isEmpty()) {
performBookDeletion(sender, args[0], deletePublic); performBookDeletion(sender, args[0], deletePublic);

View File

@@ -44,16 +44,23 @@ public class CommandGive implements TabExecutor {
* @return <p>True if the book was given successfully</p> * @return <p>True if the book was given successfully</p>
*/ */
boolean giveBook(CommandSender sender, String[] args, boolean givePublic, String folder) { boolean giveBook(CommandSender sender, String[] args, boolean givePublic, String folder) {
String command = givePublic ? "givepublicbook" : "givebook";
if (args.length == 0) {
BookFileHelper.printBooks(sender, givePublic, command, 1);
return true;
} else if (args.length == 1) {
int page = InputCleaningHelper.parsePageNumber(args[0]);
if (page > 0) {
BookFileHelper.printBooks(sender, givePublic, command, page);
return true;
}
}
if (args.length == 1 || args.length > 4) { if (args.length == 1 || args.length > 4) {
BooksWithoutBorders.sendErrorMessage(sender, "Incorrect number of arguments for this command!"); BooksWithoutBorders.sendErrorMessage(sender, "Incorrect number of arguments for this command!");
return false; return false;
} }
if (args.length == 0) {
BookFileHelper.printBooks(sender, givePublic);
return true;
}
//Organize and parse input //Organize and parse input
String bookIdentifier = args[0]; String bookIdentifier = args[0];
String receivingPlayerName = args[1]; String receivingPlayerName = args[1];

View File

@@ -49,19 +49,26 @@ public class CommandLoad implements TabExecutor {
int argumentCount = args.length; int argumentCount = args.length;
//Show books available to the player //Show books available to the player
String command = loadPublic ? "loadpublicbook" : "loadbook";
if (argumentCount == 0) { if (argumentCount == 0) {
BookFileHelper.printBooks(sender, loadPublic); BookFileHelper.printBooks(sender, loadPublic, command, 1);
return true; return true;
} else if (argumentCount == 1) {
int page = InputCleaningHelper.parsePageNumber(args[0]);
if (page > 0) {
BookFileHelper.printBooks(sender, loadPublic, command, page);
return true;
}
} }
//Organize and parse input //Organize and parse input
String bookIdentifier = args[0]; String bookIdentifier = args[0];
String copies = "1"; String copies = "1";
String isSigned = "true"; String isSigned = "true";
if (args.length == 3) { if (argumentCount == 3) {
copies = args[1]; copies = args[1];
isSigned = args[2]; isSigned = args[2];
} else if (args.length == 2) { } else if (argumentCount == 2) {
if (args[1].equalsIgnoreCase("true") || args[1].equalsIgnoreCase("false")) { if (args[1].equalsIgnoreCase("true") || args[1].equalsIgnoreCase("false")) {
isSigned = args[1]; isSigned = args[1];
} else { } else {

View File

@@ -8,6 +8,7 @@ import net.knarcraft.bookswithoutborders.utility.BookFileHelper;
import net.knarcraft.bookswithoutborders.utility.BookHelper; import net.knarcraft.bookswithoutborders.utility.BookHelper;
import net.knarcraft.bookswithoutborders.utility.BookToFromTextHelper; import net.knarcraft.bookswithoutborders.utility.BookToFromTextHelper;
import net.knarcraft.bookswithoutborders.utility.InventoryHelper; import net.knarcraft.bookswithoutborders.utility.InventoryHelper;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor; import org.bukkit.command.TabExecutor;
@@ -87,6 +88,11 @@ public class CommandSave implements TabExecutor {
String savePath = BookHelper.getBookDirectoryPathString( String savePath = BookHelper.getBookDirectoryPathString(
saveToPublicFolder ? BookDirectory.PUBLIC : BookDirectory.PLAYER, player); saveToPublicFolder ? BookDirectory.PUBLIC : BookDirectory.PLAYER, player);
if (savePath == null) {
BooksWithoutBorders.sendErrorMessage(player, "Saving Failed! Unable to find the save path!");
return;
}
//Generate book filename //Generate book filename
String fileName = BookHelper.getBookFile(book, player, saveToPublicFolder); String fileName = BookHelper.getBookFile(book, player, saveToPublicFolder);
@@ -142,7 +148,7 @@ public class CommandSave implements TabExecutor {
//Update the relevant book list //Update the relevant book list
BooksWithoutBorders.updateBooks(player, saveToPublicFolder); BooksWithoutBorders.updateBooks(player, saveToPublicFolder);
BooksWithoutBorders.sendSuccessMessage(player, "Book Saved as \"" + fileName + "\""); BooksWithoutBorders.sendSuccessMessage(player, "Book Saved as \"" + fileName + ChatColor.RESET + "\"");
} catch (IOException exception) { } catch (IOException exception) {
BooksWithoutBorders.getInstance().getLogger().log(Level.SEVERE, "Unable to save book"); BooksWithoutBorders.getInstance().getLogger().log(Level.SEVERE, "Unable to save book");
} }

View File

@@ -3,7 +3,6 @@ package net.knarcraft.bookswithoutborders.listener;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig; import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
import net.knarcraft.bookswithoutborders.utility.IntegerToRomanConverter; import net.knarcraft.bookswithoutborders.utility.IntegerToRomanConverter;
import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.ChatColor;
import org.bukkit.NamespacedKey;
import org.bukkit.block.ChiseledBookshelf; import org.bukkit.block.ChiseledBookshelf;
import org.bukkit.enchantments.Enchantment; import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@@ -141,12 +140,8 @@ public class BookshelfListener implements Listener {
* @return <p>The prettified enchantment name</p> * @return <p>The prettified enchantment name</p>
*/ */
private String getEnchantmentName(Enchantment enchantment) { private String getEnchantmentName(Enchantment enchantment) {
NamespacedKey key = enchantment.getKeyOrNull(); // Note: While depreciated, changing this is incompatible with Paper
if (key != null) { return uppercaseFirst(enchantment.getKey().getKey().replace("_", " "));
return uppercaseFirst(key.getKey().replace("_", " "));
} else {
return "Unknown Enchantment";
}
} }
/** /**

View File

@@ -4,6 +4,7 @@ import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig; import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
import net.knarcraft.bookswithoutborders.state.EncryptionStyle; import net.knarcraft.bookswithoutborders.state.EncryptionStyle;
import net.knarcraft.bookswithoutborders.utility.BookFileHelper; import net.knarcraft.bookswithoutborders.utility.BookFileHelper;
import net.knarcraft.bookswithoutborders.utility.BookFormatter;
import net.knarcraft.bookswithoutborders.utility.BookLoader; import net.knarcraft.bookswithoutborders.utility.BookLoader;
import net.knarcraft.bookswithoutborders.utility.EncryptionHelper; import net.knarcraft.bookswithoutborders.utility.EncryptionHelper;
import net.knarcraft.bookswithoutborders.utility.InputCleaningHelper; import net.knarcraft.bookswithoutborders.utility.InputCleaningHelper;
@@ -144,7 +145,7 @@ public class SignEventListener implements Listener {
player.closeInventory(); player.closeInventory();
//Converts user supplied key into integer form //Converts user supplied key into integer form
String lineText = ChatColor.stripColor(sign.getSide(Side.FRONT).getLine(2)); String lineText = BookFormatter.stripColor(sign.getSide(Side.FRONT).getLine(2));
String key = EncryptionHelper.getNumberKeyFromStringKey(lineText); String key = EncryptionHelper.getNumberKeyFromStringKey(lineText);
ItemStack book = EncryptionHelper.loadEncryptedBook(player, key, false); ItemStack book = EncryptionHelper.loadEncryptedBook(player, key, false);
@@ -163,7 +164,7 @@ public class SignEventListener implements Listener {
*/ */
private ChatColor getSignLine2Color(Sign sign) { private ChatColor getSignLine2Color(Sign sign) {
String line = sign.getSide(Side.FRONT).getLine(2); String line = sign.getSide(Side.FRONT).getLine(2);
if (!ChatColor.stripColor(line).equals(line)) { if (!BookFormatter.stripColor(line).equals(line)) {
return ChatColor.getByChar(sign.getSide(Side.FRONT).getLine(2).substring(1, 2).charAt(0)); return ChatColor.getByChar(sign.getSide(Side.FRONT).getLine(2).substring(1, 2).charAt(0));
} else { } else {
return null; return null;
@@ -291,8 +292,8 @@ public class SignEventListener implements Listener {
boolean mainHand = hand == EquipmentSlot.HAND; boolean mainHand = hand == EquipmentSlot.HAND;
if (heldItemType == Material.WRITTEN_BOOK) { if (heldItemType == Material.WRITTEN_BOOK) {
player.closeInventory(); player.closeInventory();
eBook = EncryptionHelper.encryptBook(player, mainHand, ChatColor.stripColor(lines[2]), eBook = EncryptionHelper.encryptBook(player, mainHand, BookFormatter.stripColor(lines[2]),
EncryptionStyle.getFromString(ChatColor.stripColor(lines[3]))); EncryptionStyle.getFromString(BookFormatter.stripColor(lines[3])));
if (eBook != null) { if (eBook != null) {
player.getInventory().setItem(hand, eBook); player.getInventory().setItem(hand, eBook);
} }
@@ -306,7 +307,7 @@ public class SignEventListener implements Listener {
* @param player <p>The player which clicked the sign</p> * @param player <p>The player which clicked the sign</p>
*/ */
private void giveBook(Sign sign, Player player) { private void giveBook(Sign sign, Player player) {
String fileName = ChatColor.stripColor(sign.getSide(Side.FRONT).getLine(2)); String fileName = BookFormatter.stripColor(sign.getSide(Side.FRONT).getLine(2));
boolean isLoadListNumber = false; boolean isLoadListNumber = false;
try { try {
@@ -318,7 +319,7 @@ public class SignEventListener implements Listener {
//Add the third line to the second line for the full filename //Add the third line to the second line for the full filename
String thirdLine = sign.getSide(Side.FRONT).getLine(3); String thirdLine = sign.getSide(Side.FRONT).getLine(3);
if (!isLoadListNumber && thirdLine.length() >= 2) { if (!isLoadListNumber && thirdLine.length() >= 2) {
fileName += ChatColor.stripColor(thirdLine); fileName += BookFormatter.stripColor(thirdLine);
} }
ItemStack newBook = BookLoader.loadBook(player, fileName, "true", "public"); ItemStack newBook = BookLoader.loadBook(player, fileName, "true", "public");

View File

@@ -4,11 +4,21 @@ import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig; import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
import net.knarcraft.bookswithoutborders.state.BookDirectory; import net.knarcraft.bookswithoutborders.state.BookDirectory;
import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.ClickEvent;
import net.md_5.bungee.api.chat.ComponentBuilder;
import net.md_5.bungee.api.chat.HoverEvent;
import net.md_5.bungee.api.chat.hover.content.Text;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@@ -19,6 +29,8 @@ import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig
*/ */
public final class BookFileHelper { public final class BookFileHelper {
private final static int booksPerPage = 10;
private BookFileHelper() { private BookFileHelper() {
} }
@@ -106,27 +118,143 @@ public final class BookFileHelper {
* *
* @param sender <p>The sender to display the books to</p> * @param sender <p>The sender to display the books to</p>
* @param listPublic <p>Whether to display public books</p> * @param listPublic <p>Whether to display public books</p>
* @param command <p>The base command causing this to be called</p>
* @param page <p>The page of the book list to display</p>
*/ */
public static void printBooks(CommandSender sender, boolean listPublic) { public static void printBooks(@NotNull CommandSender sender, boolean listPublic, @NotNull String command, int page) {
List<String> availableBooks = BooksWithoutBorders.getAvailableBooks(sender, listPublic); List<String> availableBooks = BooksWithoutBorders.getAvailableBooks(sender, listPublic);
BookFileHelper.printFiles(sender, availableBooks);
Map<Character, Integer> firstInstances;
if (listPublic) {
firstInstances = BooksWithoutBorders.getLetterIndex(null);
} else if (sender instanceof Player player) {
firstInstances = BooksWithoutBorders.getLetterIndex(player.getUniqueId());
} else {
firstInstances = new HashMap<>();
}
int totalPages = (int) Math.ceil((double) availableBooks.size() / booksPerPage);
if (page > totalPages) {
sender.sendMessage(ChatColor.GRAY + "No such page");
} else {
showBookMenu(sender, command, page, totalPages, availableBooks, firstInstances);
}
} }
/** /**
* Prints a list of files * Shows a menu listing available books
* *
* @param sender <p>The command sender to show the list to</p> * @param sender <p>The sender wanting to see the book menu</p>
* @param fileList <p>The files to list</p> * @param command <p>The main command used to trigger display of the book menu</p>
* @param page <p>The currently selected page</p>
* @param totalPages <p>The total amount of pages</p>
* @param availableBooks <p>All books available to the sender</p>
* @param firstInstances <p>The map between a character, and the index of the first instance of that character in the book list</p>
*/ */
public static void printFiles(CommandSender sender, List<String> fileList) { private static void showBookMenu(@NotNull CommandSender sender, @NotNull String command, int page,
BooksWithoutBorders.sendSuccessMessage(sender, "Available Books:"); int totalPages, @NotNull List<String> availableBooks,
if (fileList == null) { @NotNull Map<Character, Integer> firstInstances) {
return; ComponentBuilder headerComponent = new ComponentBuilder();
// Display links to first page where a letter can be found
for (int characterIndex = 0; characterIndex <= 25; characterIndex++) {
char character = (char) ('a' + characterIndex);
if (firstInstances.containsKey(character)) {
int pageIndex = (firstInstances.get(character) / booksPerPage) + 1;
headerComponent.append(character + "").color(
ChatColor.AQUA).event(new ClickEvent(ClickEvent.Action.RUN_COMMAND,
"/" + command + " page" + pageIndex)).event(new HoverEvent(
HoverEvent.Action.SHOW_TEXT, new Text("To page " + pageIndex)));
} else {
headerComponent.append(character + "", ComponentBuilder.FormatRetention.NONE).color(ChatColor.GRAY);
}
} }
int listSize = fileList.size();
for (int fileIndex = 0; fileIndex < listSize; fileIndex++) { sender.spigot().sendMessage(headerComponent.create());
sender.sendMessage(ChatColor.GRAY + "[" + (fileIndex + 1) + "] " + fileList.get(fileIndex));
// Print the previous page button
ComponentBuilder previousComponent = new ComponentBuilder();
if (page > 1) {
String fullCommand = "/" + command + " page" + (page - 1);
HoverEvent prevPagePreview = new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text("To page " + (page - 1)));
ClickEvent prevPageClick = new ClickEvent(ClickEvent.Action.RUN_COMMAND, fullCommand);
previousComponent.append("Previous [<]").event(prevPagePreview).event(prevPageClick);
} else {
previousComponent.append("Previous [<]").color(ChatColor.GRAY);
} }
sender.spigot().sendMessage(previousComponent.create());
// Print the main list of all book indexes and titles
int startIndex = (page - 1) * booksPerPage;
for (int bookIndex = startIndex; bookIndex < Math.min(startIndex + booksPerPage, availableBooks.size()); bookIndex++) {
ComponentBuilder bookComponent = new ComponentBuilder();
bookComponent.append("[" + (bookIndex + 1) + "]").color(ChatColor.GOLD).event(
new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/" + command + " " +
(bookIndex + 1))).event(new HoverEvent(HoverEvent.Action.SHOW_TEXT,
new Text("Select book by index")));
bookComponent.append(" ", ComponentBuilder.FormatRetention.NONE);
bookComponent.append(getNiceName(availableBooks.get(bookIndex))).color(ChatColor.WHITE).event(
new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/" + command + " " +
availableBooks.get(bookIndex))).event(
new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text("Select book by path")));
sender.spigot().sendMessage(bookComponent.create());
}
// Print the next page button
ComponentBuilder nextComponent = new ComponentBuilder();
if (page < totalPages) {
String fullCommand = "/" + command + " page" + (page + 1);
HoverEvent nextPagePreview = new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text("To page " + (page + 1)));
ClickEvent nextPageClick = new ClickEvent(ClickEvent.Action.RUN_COMMAND, fullCommand);
nextComponent.append("Next [>]").event(nextPagePreview).event(nextPageClick);
} else {
nextComponent.append("Next [>]").color(ChatColor.GRAY);
}
nextComponent.append(" Page " + page + " of " + totalPages + " ",
ComponentBuilder.FormatRetention.NONE).color(ChatColor.GREEN);
nextComponent.append("[Page Command]", ComponentBuilder.FormatRetention.NONE).event(new HoverEvent(
HoverEvent.Action.SHOW_TEXT, new Text("/" + command + " page" + page))).event(
new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/" + command + " page" + page));
sender.spigot().sendMessage(nextComponent.create());
}
/**
* Gets a nice name from a book's path
*
* @param bookPath <p>The path of a book</p>
* @return <p>The prettified book name</p>
*/
@NotNull
private static String getNiceName(@NotNull String bookPath) {
bookPath = ChatColor.translateAlternateColorCodes('&', bookPath.substring(0, bookPath.length() - 4));
String[] parts = bookPath.split(",");
return parts[0].replace("_", " ") + ChatColor.RESET + " by " + parts[1] + ChatColor.RESET;
}
/**
* Gets a map between characters, and the first instance of a book's title starting with that character
*
* @param books <p>The books to look through</p>
* @return <p>The map of the first index containing each character</p>
*/
@NotNull
public static Map<Character, Integer> populateLetterIndices(@NotNull List<String> books) {
List<Character> firstCharacter = new ArrayList<>(books.size());
for (String bookIdentifier : books) {
firstCharacter.add(BookFormatter.stripColor(bookIdentifier).toLowerCase().charAt(0));
}
Map<Character, Integer> firstEncounter = new HashMap<>();
for (int characterIndex = 0; characterIndex <= 25; characterIndex++) {
char character = (char) ('a' + characterIndex);
int index = Collections.binarySearch(firstCharacter, character);
if (index >= 0) {
firstEncounter.put(character, index);
}
}
return firstEncounter;
} }
/** /**
@@ -163,6 +291,9 @@ public final class BookFileHelper {
} }
} }
// Sort the book list
Comparator<String> bookComparator = Comparator.naturalOrder();
fileList.sort((a, b) -> bookComparator.compare(BookFormatter.stripColor(a), BookFormatter.stripColor(b)));
return fileList; return fileList;
} }

View File

@@ -3,6 +3,7 @@ package net.knarcraft.bookswithoutborders.utility;
import net.knarcraft.knarlib.property.ColorConversion; import net.knarcraft.knarlib.property.ColorConversion;
import net.knarcraft.knarlib.util.ColorHelper; import net.knarcraft.knarlib.util.ColorHelper;
import org.bukkit.inventory.meta.BookMeta; import org.bukkit.inventory.meta.BookMeta;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -114,4 +115,15 @@ public final class BookFormatter {
return bookMeta; return bookMeta;
} }
/**
* Strips the color from the given input
*
* @param input <p>The input to strip</p>
* @return <p>The color stripped input</p>
*/
@NotNull
public static String stripColor(@NotNull String input) {
return ColorHelper.stripColorCodes(input, ColorConversion.RGB);
}
} }

View File

@@ -8,6 +8,8 @@ import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.BookMeta; import org.bukkit.inventory.meta.BookMeta;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File; import java.io.File;
import java.util.UUID; import java.util.UUID;
@@ -31,7 +33,8 @@ public final class BookHelper {
* @param author <p>The author string</p> * @param author <p>The author string</p>
* @return <p>The author string, converted if it was a UUID</p> * @return <p>The author string, converted if it was a UUID</p>
*/ */
public static String authorFromUUID(String author) { @NotNull
public static String authorFromUUID(@NotNull String author) {
try { try {
UUID authorID = UUID.fromString(author); UUID authorID = UUID.fromString(author);
Player player = Bukkit.getPlayer(authorID); Player player = Bukkit.getPlayer(authorID);
@@ -50,7 +53,7 @@ public final class BookHelper {
* @param sender <p>The command sender trying to get the directory</p> * @param sender <p>The command sender trying to get the directory</p>
* @return <p>The path of the directory, or null if not possible to get</p> * @return <p>The path of the directory, or null if not possible to get</p>
*/ */
public static File getBookDirectoryPath(BookDirectory bookDirectory, CommandSender sender) { public static File getBookDirectoryPath(@NotNull BookDirectory bookDirectory, @NotNull CommandSender sender) {
String bookFolderString = getBookDirectoryPathString(bookDirectory, sender); String bookFolderString = getBookDirectoryPathString(bookDirectory, sender);
if (bookFolderString == null) { if (bookFolderString == null) {
return null; return null;
@@ -65,7 +68,8 @@ public final class BookHelper {
* @param sender <p>The command sender trying to get the directory</p> * @param sender <p>The command sender trying to get the directory</p>
* @return <p>The path of the directory, or null if not possible to get</p> * @return <p>The path of the directory, or null if not possible to get</p>
*/ */
public static String getBookDirectoryPathString(BookDirectory bookDirectory, CommandSender sender) { @Nullable
public static String getBookDirectoryPathString(@NotNull BookDirectory bookDirectory, @NotNull CommandSender sender) {
String folder = null; String folder = null;
String bookFolder = BooksWithoutBordersConfig.getBookFolder(); String bookFolder = BooksWithoutBordersConfig.getBookFolder();
if (bookDirectory == BookDirectory.PUBLIC) { if (bookDirectory == BookDirectory.PUBLIC) {
@@ -81,7 +85,7 @@ public final class BookHelper {
* *
* @param bookItem <p>The book item to increase the generation of</p> * @param bookItem <p>The book item to increase the generation of</p>
*/ */
public static void increaseGeneration(ItemStack bookItem) { public static void increaseGeneration(@NotNull ItemStack bookItem) {
BookMeta bookMeta = (BookMeta) bookItem.getItemMeta(); BookMeta bookMeta = (BookMeta) bookItem.getItemMeta();
if (BooksWithoutBordersConfig.changeGenerationOnCopy() && bookMeta != null) { if (BooksWithoutBordersConfig.changeGenerationOnCopy() && bookMeta != null) {
bookMeta.setGeneration(BookHelper.getNextGeneration(bookMeta.getGeneration())); bookMeta.setGeneration(BookHelper.getNextGeneration(bookMeta.getGeneration()));
@@ -98,7 +102,8 @@ public final class BookHelper {
* @param currentGeneration <p>The current generation of the book</p> * @param currentGeneration <p>The current generation of the book</p>
* @return <p>The next generation of the book</p> * @return <p>The next generation of the book</p>
*/ */
public static BookMeta.Generation getNextGeneration(BookMeta.Generation currentGeneration) { @NotNull
public static BookMeta.Generation getNextGeneration(@Nullable BookMeta.Generation currentGeneration) {
if (currentGeneration == null) { if (currentGeneration == null) {
return BookMeta.Generation.COPY_OF_ORIGINAL; return BookMeta.Generation.COPY_OF_ORIGINAL;
} }
@@ -116,7 +121,8 @@ public final class BookHelper {
* @param player <p>The player trying to do something with the book</p> * @param player <p>The player trying to do something with the book</p>
* @return <p>The book file</p> * @return <p>The book file</p>
*/ */
public static String getBookFile(BookMeta book, Player player, boolean isPublic) { @NotNull
public static String getBookFile(@NotNull BookMeta book, @NotNull Player player, boolean isPublic) {
String titleAuthorSeparator = BooksWithoutBordersConfig.getTitleAuthorSeparator(); String titleAuthorSeparator = BooksWithoutBordersConfig.getTitleAuthorSeparator();
String bookName; String bookName;
if (book.hasTitle()) { if (book.hasTitle()) {
@@ -145,7 +151,7 @@ public final class BookHelper {
* @param book <p>The book to check</p> * @param book <p>The book to check</p>
* @return <p>True if the player is not the book's author</p> * @return <p>True if the player is not the book's author</p>
*/ */
public static boolean isNotAuthor(Player player, BookMeta book) { public static boolean isNotAuthor(@NotNull Player player, @NotNull BookMeta book) {
if (isAuthor(player.getName(), book.getAuthor())) { if (isAuthor(player.getName(), book.getAuthor())) {
return false; return false;
} else { } else {
@@ -162,7 +168,7 @@ public final class BookHelper {
* @param author <p>The author to check</p> * @param author <p>The author to check</p>
* @return <p>True if the player is the author</p> * @return <p>True if the player is the author</p>
*/ */
private static boolean isAuthor(String playerName, String author) { private static boolean isAuthor(@NotNull String playerName, @Nullable String author) {
playerName = InputCleaningHelper.cleanString(playerName); playerName = InputCleaningHelper.cleanString(playerName);
return author != null && playerName.equalsIgnoreCase(InputCleaningHelper.cleanString(author)); return author != null && playerName.equalsIgnoreCase(InputCleaningHelper.cleanString(author));
} }

View File

@@ -9,6 +9,8 @@ import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.BookMeta; import org.bukkit.inventory.meta.BookMeta;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
@@ -31,7 +33,9 @@ public final class BookLoader {
* @param directory <p>The directory to save the book in</p> * @param directory <p>The directory to save the book in</p>
* @return <p>The loaded book</p> * @return <p>The loaded book</p>
*/ */
public static ItemStack loadBook(CommandSender sender, String fileName, String isSigned, String directory) { @Nullable
public static ItemStack loadBook(@NotNull CommandSender sender, @NotNull String fileName, @NotNull String isSigned,
@NotNull String directory) {
return loadBook(sender, fileName, isSigned, directory, 1); return loadBook(sender, fileName, isSigned, directory, 1);
} }
@@ -45,7 +49,9 @@ public final class BookLoader {
* @param numCopies <p>The number of copies to load</p> * @param numCopies <p>The number of copies to load</p>
* @return <p>The loaded book</p> * @return <p>The loaded book</p>
*/ */
public static ItemStack loadBook(CommandSender sender, String fileName, String isSigned, String directory, int numCopies) { @Nullable
public static ItemStack loadBook(@NotNull CommandSender sender, @NotNull String fileName, @NotNull String isSigned,
@NotNull String directory, int numCopies) {
BookDirectory bookDirectory = BookDirectory.getFromString(directory); BookDirectory bookDirectory = BookDirectory.getFromString(directory);
//Find the filename if a book index is given //Find the filename if a book index is given
@@ -96,8 +102,13 @@ public final class BookLoader {
book = new ItemStack(Material.WRITABLE_BOOK); book = new ItemStack(Material.WRITABLE_BOOK);
} }
if (bookMetadata == null) {
BooksWithoutBorders.sendErrorMessage(sender, "Unable to create blank book metadata!");
return null;
}
//Load the book from the given file //Load the book from the given file
BookToFromTextHelper.bookFromFile(file, bookMetadata); bookMetadata = BookToFromTextHelper.bookFromFile(file, bookMetadata);
if (bookMetadata == null) { if (bookMetadata == null) {
BooksWithoutBorders.sendErrorMessage(sender, "File was blank!!"); BooksWithoutBorders.sendErrorMessage(sender, "File was blank!!");
return null; return null;

View File

@@ -6,6 +6,8 @@ import net.knarcraft.knarlib.util.FileHelper;
import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.inventory.meta.BookMeta; import org.bukkit.inventory.meta.BookMeta;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.File; import java.io.File;
@@ -37,7 +39,7 @@ public final class BookToFromTextHelper {
* @param bookMetadata <p>Metadata about the book to save</p> * @param bookMetadata <p>Metadata about the book to save</p>
* @throws IOException <p>If unable to save the book</p> * @throws IOException <p>If unable to save the book</p>
*/ */
public static void bookToYml(String path, String fileName, BookMeta bookMetadata) throws IOException { public static void bookToYml(@NotNull String path, @NotNull String fileName, @NotNull BookMeta bookMetadata) throws IOException {
FileConfiguration bookYml = YamlConfiguration.loadConfiguration(new File(path, "blank")); FileConfiguration bookYml = YamlConfiguration.loadConfiguration(new File(path, "blank"));
if (bookMetadata.hasTitle()) { if (bookMetadata.hasTitle()) {
@@ -58,7 +60,7 @@ public final class BookToFromTextHelper {
bookYml.set("Lore", bookMetadata.getLore()); bookYml.set("Lore", bookMetadata.getLore());
} }
bookYml.save(path + fileName + ".yml"); bookYml.save(path + fileName.replace("§", "&") + ".yml");
} }
/** /**
@@ -68,7 +70,8 @@ public final class BookToFromTextHelper {
* @param bookMetadata <p>The book metadata to use for saving the book</p> * @param bookMetadata <p>The book metadata to use for saving the book</p>
* @return <p>The book metadata of the loaded book</p> * @return <p>The book metadata of the loaded book</p>
*/ */
public static BookMeta bookFromFile(File file, BookMeta bookMetadata) { @Nullable
public static BookMeta bookFromFile(@NotNull File file, @NotNull BookMeta bookMetadata) {
if (file.getName().endsWith(".txt")) { if (file.getName().endsWith(".txt")) {
return bookFromTXT(file.getName(), file, bookMetadata); return bookFromTXT(file.getName(), file, bookMetadata);
} else if (file.getName().endsWith(".yml")) { } else if (file.getName().endsWith(".yml")) {
@@ -86,8 +89,8 @@ public final class BookToFromTextHelper {
* @param bookMetadata <p>Metadata about the book to save</p> * @param bookMetadata <p>Metadata about the book to save</p>
* @throws IOException <p>If unable to save the book</p> * @throws IOException <p>If unable to save the book</p>
*/ */
public static void bookToTXT(String folderPath, String fileName, BookMeta bookMetadata) throws IOException { public static void bookToTXT(@NotNull String folderPath, @NotNull String fileName, @NotNull BookMeta bookMetadata) throws IOException {
FileWriter fileWriter = new FileWriter(folderPath + fileName + ".txt", StandardCharsets.UTF_8); FileWriter fileWriter = new FileWriter(folderPath + fileName.replace("§", "&") + ".txt", StandardCharsets.UTF_8);
PrintWriter printWriter = new PrintWriter(fileWriter); PrintWriter printWriter = new PrintWriter(fileWriter);
List<String> pages = bookMetadata.getPages(); List<String> pages = bookMetadata.getPages();
@@ -112,7 +115,8 @@ public final class BookToFromTextHelper {
* @param bookMetadata <p>Metadata which will be altered with the book's contents</p> * @param bookMetadata <p>Metadata which will be altered with the book's contents</p>
* @return <p>Metadata for the loaded book</p> * @return <p>Metadata for the loaded book</p>
*/ */
private static BookMeta bookFromYml(File file, BookMeta bookMetadata) { @Nullable
private static BookMeta bookFromYml(@NotNull File file, @NotNull BookMeta bookMetadata) {
try { try {
FileConfiguration bookYml = YamlConfiguration.loadConfiguration(file); FileConfiguration bookYml = YamlConfiguration.loadConfiguration(file);
@@ -135,7 +139,8 @@ public final class BookToFromTextHelper {
* @param bookMetadata <p>Metadata which will be altered with the book's contents</p> * @param bookMetadata <p>Metadata which will be altered with the book's contents</p>
* @return <p>Metadata for the loaded book</p> * @return <p>Metadata for the loaded book</p>
*/ */
private static BookMeta bookFromTXT(String fileName, File file, BookMeta bookMetadata) { @Nullable
private static BookMeta bookFromTXT(@NotNull String fileName, @NotNull File file, @NotNull BookMeta bookMetadata) {
String author; String author;
String title; String title;
String titleAuthorSeparator = BooksWithoutBordersConfig.getTitleAuthorSeparator(); String titleAuthorSeparator = BooksWithoutBordersConfig.getTitleAuthorSeparator();
@@ -188,7 +193,8 @@ public final class BookToFromTextHelper {
* @return <p>A string list where each string is the text on one page</p> * @return <p>A string list where each string is the text on one page</p>
* @throws IOException <p>If unable to read from the file</p> * @throws IOException <p>If unable to read from the file</p>
*/ */
private static List<String> readTextFile(File file) throws IOException { @Nullable
private static List<String> readTextFile(@NotNull File file) throws IOException {
List<String> rawPages = new ArrayList<>(); List<String> rawPages = new ArrayList<>();
BufferedReader bufferedReader = FileHelper.getBufferedReaderFromInputStream(new FileInputStream(file)); BufferedReader bufferedReader = FileHelper.getBufferedReaderFromInputStream(new FileInputStream(file));

View File

@@ -1,7 +1,11 @@
package net.knarcraft.bookswithoutborders.utility; package net.knarcraft.bookswithoutborders.utility;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/** /**
* Helper class for cleaning input and names * Helper class for cleaning input and names
@@ -58,4 +62,24 @@ public final class InputCleaningHelper {
return fileName; return fileName;
} }
/**
* Parses a page number for a string like "page1"
*
* @param input <p>The input to parse</p>
* @return <p>The page number, or 0 if not valid</p>
*/
public static int parsePageNumber(@NotNull String input) {
try {
Pattern pattern = Pattern.compile("page([0-9])+");
Matcher matcher = pattern.matcher(input);
if (matcher.matches()) {
return Integer.parseInt(matcher.group(1));
} else {
return 0;
}
} catch (NumberFormatException exception) {
return 0;
}
}
} }