diff --git a/src/main/java/net/knarcraft/bookswithoutborders/command/CommandCopy.java b/src/main/java/net/knarcraft/bookswithoutborders/command/CommandCopy.java index 0e8eb7d..3fd3e54 100644 --- a/src/main/java/net/knarcraft/bookswithoutborders/command/CommandCopy.java +++ b/src/main/java/net/knarcraft/bookswithoutborders/command/CommandCopy.java @@ -6,6 +6,7 @@ import net.knarcraft.bookswithoutborders.utility.BookHelper; import net.knarcraft.bookswithoutborders.utility.EconomyHelper; import net.knarcraft.bookswithoutborders.utility.InventoryHelper; import net.knarcraft.bookswithoutborders.utility.TabCompletionHelper; +import org.bukkit.Material; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.command.TabExecutor; @@ -40,33 +41,108 @@ public class CommandCopy implements TabExecutor { return false; } - ItemStack heldBook = InventoryHelper.getHeldBook(player, true); //TODO: Rewrite this so the resulting book becomes a COPY, or COPY OF COPY + try { + ItemStack heldBook = InventoryHelper.getHeldBook(player, true); int copies = Integer.parseInt(args[0]); - if (copies > 0) { - if (BooksWithoutBordersConfig.getAuthorOnlyCopy() && !player.hasPermission("bookswithoutborders.bypassAuthorOnlyCopy")) { - if (BookHelper.isNotAuthor(player, (BookMeta) Objects.requireNonNull(heldBook.getItemMeta()))) { - return false; - } - } - - if (BooksWithoutBordersConfig.booksHavePrice() && - !player.hasPermission("bookswithoutborders.bypassBookPrice") && - EconomyHelper.cannotPayForBookPrinting(player, copies)) { - return false; - } - - heldBook.setAmount(heldBook.getAmount() + copies); - BooksWithoutBorders.sendSuccessMessage(player, "Book copied!"); - return true; + if (copies <= 0) { + throw new NumberFormatException("Number of copies must be larger than 0"); } + return performCopy(copies, player, heldBook); } catch (NumberFormatException ignored) { + BooksWithoutBorders.sendErrorMessage(player, "Book not copied!"); + BooksWithoutBorders.sendErrorMessage(player, "Number specified was invalid!"); + return false; } - BooksWithoutBorders.sendErrorMessage(player, "Book not copied!"); - BooksWithoutBorders.sendErrorMessage(player, "Number specified was invalid!"); - return false; + } + + /** + * Performs the actual copying + * + * @param copies

The number of copies to be made

+ * @param player

The player requesting the copies

+ * @param heldBook

The book to be copied

+ * @return

True if the copying was successful

+ */ + private boolean performCopy(int copies, Player player, ItemStack heldBook) { + //Make sure the player owns the book if authorOnlyCopy is enabled + if (BooksWithoutBordersConfig.getAuthorOnlyCopy() && + !player.hasPermission("bookswithoutborders.bypassAuthorOnlyCopy")) { + if (BookHelper.isNotAuthor(player, (BookMeta) Objects.requireNonNull(heldBook.getItemMeta()))) { + return false; + } + } + + //Make sure the player can pay for the copying + if (BooksWithoutBordersConfig.booksHavePrice() && + !player.hasPermission("bookswithoutborders.bypassBookPrice") && + EconomyHelper.cannotPayForBookPrinting(player, copies)) { + return false; + } + + BookMeta bookMeta = (BookMeta) heldBook.getItemMeta(); + if (BooksWithoutBordersConfig.changeGenerationOnCopy() && bookMeta != null && bookMeta.hasGeneration()) { + return copyNextGenerationBook(bookMeta, player, copies); + } else { + //Make sure the player can pay for the copying + if (paymentUnSuccessful(player, copies)) { + return false; + } + + heldBook.setAmount(heldBook.getAmount() + copies); + BooksWithoutBorders.sendSuccessMessage(player, "Book copied!"); + return true; + } + } + + /** + * Tries to take payment from a player + * + * @param player

The player to take the payment from

+ * @param copies

The number of copies to create for the player

+ * @return

True if the payment failed

+ */ + private boolean paymentUnSuccessful(Player player, int copies) { + return BooksWithoutBordersConfig.booksHavePrice() && + !player.hasPermission("bookswithoutborders.bypassBookPrice") && + EconomyHelper.cannotPayForBookPrinting(player, copies); + } + + /** + * Copies a book with the next generation relative to the input book + * + * @param bookMeta

The book to copy

+ * @param player

The player copying the book

+ * @param copies

The number of copies requested

+ * @return

True if the book was successfully copied

+ */ + private boolean copyNextGenerationBook(BookMeta bookMeta, Player player, int copies) { + //Copy the vanilla behavior of refusing copying any further + if (bookMeta.getGeneration() == BookMeta.Generation.COPY_OF_COPY) { + player.sendMessage("You cannot copy this book any further. You must have the original or a direct copy."); + return false; + } + //Make sure the player can fit the book in their inventory + int nextAvailableSlot = player.getInventory().firstEmpty(); + if (nextAvailableSlot == -1) { + player.sendMessage("You need an available slot in your inventory."); + return false; + } + //Make sure the player can pay for the copying + if (paymentUnSuccessful(player, copies)) { + return false; + } + + ItemStack itemStack = new ItemStack(Material.WRITTEN_BOOK); + itemStack.setItemMeta(bookMeta); + //Increase the generation of the book + BookHelper.increaseGeneration(itemStack); + itemStack.setAmount(copies); + + player.getInventory().addItem(itemStack); + return true; } @Override diff --git a/src/main/java/net/knarcraft/bookswithoutborders/command/CommandGive.java b/src/main/java/net/knarcraft/bookswithoutborders/command/CommandGive.java index e1ec2ae..b82e32d 100644 --- a/src/main/java/net/knarcraft/bookswithoutborders/command/CommandGive.java +++ b/src/main/java/net/knarcraft/bookswithoutborders/command/CommandGive.java @@ -69,6 +69,20 @@ public class CommandGive implements TabExecutor { } } + //Try and find the target player + Player receivingPlayer = booksWithoutBorders.getServer().getPlayer(receivingPlayerName); + if (receivingPlayer == null) { + BooksWithoutBorders.sendErrorMessage(sender, "Player not found!"); + return false; + } + + //Make sure the receiver is able to fit the book + if (receivingPlayer.getInventory().firstEmpty() == -1) { + BooksWithoutBorders.sendErrorMessage(sender, "Receiving player must have space in their inventory" + + " to receive books!"); + return false; + } + //Load books available to the player try { Integer.parseInt(bookIdentifier); @@ -76,29 +90,8 @@ public class CommandGive implements TabExecutor { } catch (NumberFormatException ignored) { } - Player receivingPlayer = booksWithoutBorders.getServer().getPlayer(receivingPlayerName); - if (receivingPlayer == null) { - BooksWithoutBorders.sendErrorMessage(sender, "Player not found!"); - return false; - } - - if (receivingPlayer.getInventory().firstEmpty() == -1) { - BooksWithoutBorders.sendErrorMessage(sender, "Receiving player must have space in their inventory to receive books!"); - return false; - } - - String bookToLoad = InputCleaningHelper.cleanString(bookIdentifier); try { - ItemStack newBook = BookLoader.loadBook(sender, bookToLoad, isSigned, folder, Integer.parseInt(copies)); - if (newBook != null) { - receivingPlayer.getInventory().addItem(newBook); - BooksWithoutBorders.sendSuccessMessage(sender, "Book sent!"); - BooksWithoutBorders.sendSuccessMessage(receivingPlayer, "Book received!"); - return true; - } else { - BooksWithoutBorders.sendErrorMessage(sender, "Book failed to load!"); - return false; - } + return loadAndGiveBook(bookIdentifier, sender, receivingPlayer, isSigned, folder, copies); } catch (NumberFormatException e) { BooksWithoutBorders.sendErrorMessage(sender, "Invalid number of book copies specified!"); return false; @@ -152,4 +145,31 @@ public class CommandGive implements TabExecutor { return new ArrayList<>(); } + /** + * Loads a book and gives it to the correct player + * + * @param bookIdentifier

The file name specified by the user

+ * @param sender

The player trying to give the book

+ * @param receivingPlayer

The player which is the receiver of the book

+ * @param isSigned

The value given for if the given book should be signed or not

+ * @param folder

The folder containing the book to load

+ * @param copies

The number of copies the player wants to give

+ * @return

True if the book was successfully given

+ */ + private boolean loadAndGiveBook(String bookIdentifier, CommandSender sender, Player receivingPlayer, + String isSigned, String folder, String copies) throws NumberFormatException { + String bookToLoad = InputCleaningHelper.cleanString(bookIdentifier); + ItemStack newBook = BookLoader.loadBook(sender, bookToLoad, isSigned, folder, Integer.parseInt(copies)); + if (newBook != null) { + //NOTE: As this method bypasses cost, it should also bypass the generation change + receivingPlayer.getInventory().addItem(newBook); + BooksWithoutBorders.sendSuccessMessage(sender, "Book sent!"); + BooksWithoutBorders.sendSuccessMessage(receivingPlayer, "Book received!"); + return true; + } else { + BooksWithoutBorders.sendErrorMessage(sender, "Book failed to load!"); + return false; + } + } + } diff --git a/src/main/java/net/knarcraft/bookswithoutborders/config/BooksWithoutBordersConfig.java b/src/main/java/net/knarcraft/bookswithoutborders/config/BooksWithoutBordersConfig.java index 27205d7..1170ad0 100644 --- a/src/main/java/net/knarcraft/bookswithoutborders/config/BooksWithoutBordersConfig.java +++ b/src/main/java/net/knarcraft/bookswithoutborders/config/BooksWithoutBordersConfig.java @@ -38,6 +38,7 @@ public class BooksWithoutBordersConfig { private static boolean useYml; private static boolean adminDecrypt; private static boolean formatBooks; + private static boolean changeGenerationOnCopy; /** * Initializes the books without borders settings class @@ -194,6 +195,15 @@ public class BooksWithoutBordersConfig { return bookDuplicateLimit; } + /** + * Gets whether books should change their generation during copy + * + * @return

True if books should change their generation

+ */ + public static boolean changeGenerationOnCopy() { + return changeGenerationOnCopy; + } + /** * Gets the separator used to split book title from book author * @@ -268,6 +278,7 @@ public class BooksWithoutBordersConfig { config.set(ConfigOption.ADMIN_AUTO_DECRYPT.getConfigNode(), adminDecrypt); config.set(ConfigOption.AUTHOR_ONLY_COPY.getConfigNode(), authorOnlyCopy); config.set(ConfigOption.AUTHOR_ONLY_UNSIGN.getConfigNode(), authorOnlyUnsign); + config.set(ConfigOption.CHANGE_GENERATION_ON_COPY.getConfigNode(), changeGenerationOnCopy); //Handles old book and quill settings if (config.contains("Options.Require_book_and_quill_to_create_book")) { @@ -296,34 +307,26 @@ public class BooksWithoutBordersConfig { BooksWithoutBorders.getInstance().reloadConfig(); Configuration config = BooksWithoutBorders.getInstance().getConfig(); try { - useYml = config.getBoolean(ConfigOption.USE_YAML.getConfigNode(), - (Boolean) ConfigOption.USE_YAML.getDefaultValue()); - bookDuplicateLimit = config.getInt(ConfigOption.MAX_DUPLICATES.getConfigNode(), - (Integer) ConfigOption.MAX_DUPLICATES.getDefaultValue()); - titleAuthorSeparator = config.getString(ConfigOption.TITLE_AUTHOR_SEPARATOR.getConfigNode(), - (String) ConfigOption.TITLE_AUTHOR_SEPARATOR.getDefaultValue()); - loreSeparator = config.getString(ConfigOption.LORE_LINE_SEPARATOR.getConfigNode(), - (String) ConfigOption.LORE_LINE_SEPARATOR.getDefaultValue()); - adminDecrypt = config.getBoolean(ConfigOption.ADMIN_AUTO_DECRYPT.getConfigNode(), - (Boolean) ConfigOption.ADMIN_AUTO_DECRYPT.getDefaultValue()); - authorOnlyCopy = config.getBoolean(ConfigOption.AUTHOR_ONLY_COPY.getConfigNode(), - (Boolean) ConfigOption.AUTHOR_ONLY_COPY.getDefaultValue()); - authorOnlyUnsign = config.getBoolean(ConfigOption.AUTHOR_ONLY_UNSIGN.getConfigNode(), - (Boolean) ConfigOption.AUTHOR_ONLY_UNSIGN.getDefaultValue()); + useYml = getBoolean(config, ConfigOption.USE_YAML); + bookDuplicateLimit = getInt(config, ConfigOption.MAX_DUPLICATES); + titleAuthorSeparator = getString(config, ConfigOption.TITLE_AUTHOR_SEPARATOR); + loreSeparator = getString(config, ConfigOption.LORE_LINE_SEPARATOR); + adminDecrypt = getBoolean(config, ConfigOption.ADMIN_AUTO_DECRYPT); + authorOnlyCopy = getBoolean(config, ConfigOption.AUTHOR_ONLY_COPY); + authorOnlyUnsign = getBoolean(config, ConfigOption.AUTHOR_ONLY_UNSIGN); firstBooks = config.getStringList(ConfigOption.BOOKS_FOR_NEW_PLAYERS.getConfigNode()); - welcomeMessage = config.getString(ConfigOption.MESSAGE_FOR_NEW_PLAYERS.getConfigNode(), - (String) ConfigOption.MESSAGE_FOR_NEW_PLAYERS.getDefaultValue()); - formatBooks = config.getBoolean(ConfigOption.FORMAT_AFTER_SIGNING.getConfigNode(), - (Boolean) ConfigOption.FORMAT_AFTER_SIGNING.getDefaultValue()); + welcomeMessage = getString(config, ConfigOption.MESSAGE_FOR_NEW_PLAYERS); + formatBooks = getBoolean(config, ConfigOption.FORMAT_AFTER_SIGNING); + changeGenerationOnCopy = getBoolean(config, ConfigOption.CHANGE_GENERATION_ON_COPY); //Convert string into material - String paymentMaterial = config.getString(ConfigOption.PRICE_ITEM_TYPE.getConfigNode(), - (String) ConfigOption.PRICE_ITEM_TYPE.getDefaultValue()); + String paymentMaterial = getString(config, ConfigOption.PRICE_ITEM_TYPE); if (paymentMaterial.equalsIgnoreCase("Economy")) { if (EconomyHelper.setupEconomy()) { bookPriceType = Material.AIR; } else { - sendErrorMessage(consoleSender, "BooksWithoutBorders failed to hook into Vault! Book price not set!"); + sendErrorMessage(consoleSender, + "BooksWithoutBorders failed to hook into Vault! Book price not set!"); bookPriceType = null; } } else if (!paymentMaterial.trim().isEmpty()) { @@ -332,8 +335,7 @@ public class BooksWithoutBordersConfig { bookPriceType = material; } } - bookPriceQuantity = config.getDouble(ConfigOption.PRICE_QUANTITY.getConfigNode(), - (Double) ConfigOption.PRICE_QUANTITY.getDefaultValue()); + bookPriceQuantity = getDouble(config, ConfigOption.PRICE_QUANTITY); //Make sure titleAuthorSeparator is a valid value titleAuthorSeparator = cleanString(titleAuthorSeparator); @@ -352,4 +354,48 @@ public class BooksWithoutBordersConfig { return true; } + /** + * Gets the double value of the given config option + * + * @param config

The configuration to read from

+ * @param configOption

The configuration option to get the value for

+ * @return

The value of the option

+ */ + private static double getDouble(Configuration config, ConfigOption configOption) { + return config.getDouble(configOption.getConfigNode(), (Double) configOption.getDefaultValue()); + } + + /** + * Gets the integer value of the given config option + * + * @param config

The configuration to read from

+ * @param configOption

The configuration option to get the value for

+ * @return

The value of the option

+ */ + private static int getInt(Configuration config, ConfigOption configOption) { + return config.getInt(configOption.getConfigNode(), (Integer) configOption.getDefaultValue()); + } + + /** + * Gets the string value of the given config option + * + * @param config

The configuration to read from

+ * @param configOption

The configuration option to get the value for

+ * @return

The value of the option

+ */ + private static String getString(Configuration config, ConfigOption configOption) { + return config.getString(configOption.getConfigNode(), (String) configOption.getDefaultValue()); + } + + /** + * Gets the boolean value of the given config option + * + * @param config

The configuration to read from

+ * @param configOption

The configuration option to get the value for

+ * @return

The value of the option

+ */ + private static boolean getBoolean(Configuration config, ConfigOption configOption) { + return config.getBoolean(configOption.getConfigNode(), (Boolean) configOption.getDefaultValue()); + } + } diff --git a/src/main/java/net/knarcraft/bookswithoutborders/config/ConfigOption.java b/src/main/java/net/knarcraft/bookswithoutborders/config/ConfigOption.java index ef00ecc..10ad78f 100644 --- a/src/main/java/net/knarcraft/bookswithoutborders/config/ConfigOption.java +++ b/src/main/java/net/knarcraft/bookswithoutborders/config/ConfigOption.java @@ -60,6 +60,11 @@ public enum ConfigOption { */ AUTHOR_ONLY_UNSIGN("Author_Only_Unsign", false), + /** + * Whether to turn Original into Copy when copying books + */ + CHANGE_GENERATION_ON_COPY("Decrease_Generation_On_Copy", false), + /** * Whether to automatically format every signed book */ diff --git a/src/main/java/net/knarcraft/bookswithoutborders/utility/BookHelper.java b/src/main/java/net/knarcraft/bookswithoutborders/utility/BookHelper.java index abb8cea..140ee60 100644 --- a/src/main/java/net/knarcraft/bookswithoutborders/utility/BookHelper.java +++ b/src/main/java/net/knarcraft/bookswithoutborders/utility/BookHelper.java @@ -3,6 +3,7 @@ package net.knarcraft.bookswithoutborders.utility; import net.knarcraft.bookswithoutborders.BooksWithoutBorders; import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig; import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.BookMeta; import static net.knarcraft.bookswithoutborders.utility.InputCleaningHelper.cleanString; @@ -17,6 +18,36 @@ public final class BookHelper { } + /** + * Increases the generation of the given book, if necessary + * + * @param bookItem

The book item to increase the generation of

+ */ + public static void increaseGeneration(ItemStack bookItem) { + BookMeta bookMeta = (BookMeta) bookItem.getItemMeta(); + if (BooksWithoutBordersConfig.changeGenerationOnCopy() && bookMeta != null && bookMeta.hasGeneration()) { + bookMeta.setGeneration(BookHelper.getNextGeneration(bookMeta.getGeneration())); + bookItem.setItemMeta(bookMeta); + } + } + + /** + * Gets the next generation of the given book + * + *

If an original book is given, this will yield a copy of the original. If a copy of original is given, this + * will yield a copy of a copy. In all other cases, the generation will stay the same

+ * + * @param currentGeneration

The current generation of the book

+ * @return

The next generation of the book

+ */ + public static BookMeta.Generation getNextGeneration(BookMeta.Generation currentGeneration) { + return switch (currentGeneration) { + case ORIGINAL -> BookMeta.Generation.COPY_OF_ORIGINAL; + case COPY_OF_ORIGINAL -> BookMeta.Generation.COPY_OF_COPY; + default -> currentGeneration; + }; + } + /** * Gets the file name of the given book * diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index af60df1..6c3e068 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -25,4 +25,7 @@ Options: Author_Only_Unsign: false # Whether to automatically format every book when it's signed Format_Book_After_Signing: false + # Whether to display "COPY" or "COPY_OF_COPY" instead of "ORIGINAL" when a book is copied. This also uses the + # vanilla behavior where a copy of a copy cannot be copied further. + Change_Generation_On_Copy: false \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index ce1dab9..290da64 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -88,7 +88,7 @@ commands: reload: description: Reloads BwB's configuration file usage: / - permission: bookswithoutborders.admin + permission: bookswithoutborders.reload permissions: bookswithoutborders.*: description: Grants all permissions @@ -114,6 +114,7 @@ permissions: bookswithoutborders.bypassauthoronlyunsign: true bookswithoutborders.bypassbookprice: true bookswithoutborders.setbookprice: true + bookswithoutborders.reload: true bookswithoutborders.use: description: Allows player to use commands to save/load/delete in their personal directory children: @@ -169,4 +170,6 @@ permissions: bookswithoutborders.bypassbookprice: description: Allows player to ignore Price_to_create_book config setting bookswithoutborders.setbookprice: - description: Allows player to set the cost of creating a book \ No newline at end of file + description: Allows player to set the cost of creating a book + bookswithoutborders.reload: + description: Allows player to reload this plugin \ No newline at end of file