Adds an option which mimics the vanilla book copying behavior

This commit is contained in:
Kristian Knarvik 2022-08-10 00:16:33 +02:00
parent a7c284ade2
commit f243bf32e7
7 changed files with 251 additions and 67 deletions

View File

@ -6,6 +6,7 @@ import net.knarcraft.bookswithoutborders.utility.BookHelper;
import net.knarcraft.bookswithoutborders.utility.EconomyHelper; import net.knarcraft.bookswithoutborders.utility.EconomyHelper;
import net.knarcraft.bookswithoutborders.utility.InventoryHelper; import net.knarcraft.bookswithoutborders.utility.InventoryHelper;
import net.knarcraft.bookswithoutborders.utility.TabCompletionHelper; import net.knarcraft.bookswithoutborders.utility.TabCompletionHelper;
import org.bukkit.Material;
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;
@ -40,34 +41,109 @@ public class CommandCopy implements TabExecutor {
return false; return false;
} }
ItemStack heldBook = InventoryHelper.getHeldBook(player, true);
//TODO: Rewrite this so the resulting book becomes a COPY, or COPY OF COPY //TODO: Rewrite this so the resulting book becomes a COPY, or COPY OF COPY
try { try {
ItemStack heldBook = InventoryHelper.getHeldBook(player, true);
int copies = Integer.parseInt(args[0]); int copies = Integer.parseInt(args[0]);
if (copies > 0) { if (copies <= 0) {
if (BooksWithoutBordersConfig.getAuthorOnlyCopy() && !player.hasPermission("bookswithoutborders.bypassAuthorOnlyCopy")) { 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;
}
}
/**
* Performs the actual copying
*
* @param copies <p>The number of copies to be made</p>
* @param player <p>The player requesting the copies</p>
* @param heldBook <p>The book to be copied</p>
* @return <p>True if the copying was successful</p>
*/
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()))) { if (BookHelper.isNotAuthor(player, (BookMeta) Objects.requireNonNull(heldBook.getItemMeta()))) {
return false; return false;
} }
} }
//Make sure the player can pay for the copying
if (BooksWithoutBordersConfig.booksHavePrice() && if (BooksWithoutBordersConfig.booksHavePrice() &&
!player.hasPermission("bookswithoutborders.bypassBookPrice") && !player.hasPermission("bookswithoutborders.bypassBookPrice") &&
EconomyHelper.cannotPayForBookPrinting(player, copies)) { EconomyHelper.cannotPayForBookPrinting(player, copies)) {
return false; 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); heldBook.setAmount(heldBook.getAmount() + copies);
BooksWithoutBorders.sendSuccessMessage(player, "Book copied!"); BooksWithoutBorders.sendSuccessMessage(player, "Book copied!");
return true; return true;
} }
} catch (NumberFormatException ignored) {
} }
BooksWithoutBorders.sendErrorMessage(player, "Book not copied!");
BooksWithoutBorders.sendErrorMessage(player, "Number specified was invalid!"); /**
* Tries to take payment from a player
*
* @param player <p>The player to take the payment from</p>
* @param copies <p>The number of copies to create for the player</p>
* @return <p>True if the payment failed</p>
*/
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 <p>The book to copy</p>
* @param player <p>The player copying the book</p>
* @param copies <p>The number of copies requested</p>
* @return <p>True if the book was successfully copied</p>
*/
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; 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 @Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias,

View File

@ -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 //Load books available to the player
try { try {
Integer.parseInt(bookIdentifier); Integer.parseInt(bookIdentifier);
@ -76,29 +90,8 @@ public class CommandGive implements TabExecutor {
} catch (NumberFormatException ignored) { } 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 { try {
ItemStack newBook = BookLoader.loadBook(sender, bookToLoad, isSigned, folder, Integer.parseInt(copies)); return loadAndGiveBook(bookIdentifier, sender, receivingPlayer, isSigned, folder, 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;
}
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
BooksWithoutBorders.sendErrorMessage(sender, "Invalid number of book copies specified!"); BooksWithoutBorders.sendErrorMessage(sender, "Invalid number of book copies specified!");
return false; return false;
@ -152,4 +145,31 @@ public class CommandGive implements TabExecutor {
return new ArrayList<>(); return new ArrayList<>();
} }
/**
* Loads a book and gives it to the correct player
*
* @param bookIdentifier <p>The file name specified by the user</p>
* @param sender <p>The player trying to give the book</p>
* @param receivingPlayer <p>The player which is the receiver of the book</p>
* @param isSigned <p>The value given for if the given book should be signed or not</p>
* @param folder <p>The folder containing the book to load</p>
* @param copies <p>The number of copies the player wants to give</p>
* @return <p>True if the book was successfully given</p>
*/
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;
}
}
} }

View File

@ -38,6 +38,7 @@ public class BooksWithoutBordersConfig {
private static boolean useYml; private static boolean useYml;
private static boolean adminDecrypt; private static boolean adminDecrypt;
private static boolean formatBooks; private static boolean formatBooks;
private static boolean changeGenerationOnCopy;
/** /**
* Initializes the books without borders settings class * Initializes the books without borders settings class
@ -194,6 +195,15 @@ public class BooksWithoutBordersConfig {
return bookDuplicateLimit; return bookDuplicateLimit;
} }
/**
* Gets whether books should change their generation during copy
*
* @return <p>True if books should change their generation</p>
*/
public static boolean changeGenerationOnCopy() {
return changeGenerationOnCopy;
}
/** /**
* Gets the separator used to split book title from book author * 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.ADMIN_AUTO_DECRYPT.getConfigNode(), adminDecrypt);
config.set(ConfigOption.AUTHOR_ONLY_COPY.getConfigNode(), authorOnlyCopy); config.set(ConfigOption.AUTHOR_ONLY_COPY.getConfigNode(), authorOnlyCopy);
config.set(ConfigOption.AUTHOR_ONLY_UNSIGN.getConfigNode(), authorOnlyUnsign); config.set(ConfigOption.AUTHOR_ONLY_UNSIGN.getConfigNode(), authorOnlyUnsign);
config.set(ConfigOption.CHANGE_GENERATION_ON_COPY.getConfigNode(), changeGenerationOnCopy);
//Handles old book and quill settings //Handles old book and quill settings
if (config.contains("Options.Require_book_and_quill_to_create_book")) { if (config.contains("Options.Require_book_and_quill_to_create_book")) {
@ -296,34 +307,26 @@ public class BooksWithoutBordersConfig {
BooksWithoutBorders.getInstance().reloadConfig(); BooksWithoutBorders.getInstance().reloadConfig();
Configuration config = BooksWithoutBorders.getInstance().getConfig(); Configuration config = BooksWithoutBorders.getInstance().getConfig();
try { try {
useYml = config.getBoolean(ConfigOption.USE_YAML.getConfigNode(), useYml = getBoolean(config, ConfigOption.USE_YAML);
(Boolean) ConfigOption.USE_YAML.getDefaultValue()); bookDuplicateLimit = getInt(config, ConfigOption.MAX_DUPLICATES);
bookDuplicateLimit = config.getInt(ConfigOption.MAX_DUPLICATES.getConfigNode(), titleAuthorSeparator = getString(config, ConfigOption.TITLE_AUTHOR_SEPARATOR);
(Integer) ConfigOption.MAX_DUPLICATES.getDefaultValue()); loreSeparator = getString(config, ConfigOption.LORE_LINE_SEPARATOR);
titleAuthorSeparator = config.getString(ConfigOption.TITLE_AUTHOR_SEPARATOR.getConfigNode(), adminDecrypt = getBoolean(config, ConfigOption.ADMIN_AUTO_DECRYPT);
(String) ConfigOption.TITLE_AUTHOR_SEPARATOR.getDefaultValue()); authorOnlyCopy = getBoolean(config, ConfigOption.AUTHOR_ONLY_COPY);
loreSeparator = config.getString(ConfigOption.LORE_LINE_SEPARATOR.getConfigNode(), authorOnlyUnsign = getBoolean(config, ConfigOption.AUTHOR_ONLY_UNSIGN);
(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());
firstBooks = config.getStringList(ConfigOption.BOOKS_FOR_NEW_PLAYERS.getConfigNode()); firstBooks = config.getStringList(ConfigOption.BOOKS_FOR_NEW_PLAYERS.getConfigNode());
welcomeMessage = config.getString(ConfigOption.MESSAGE_FOR_NEW_PLAYERS.getConfigNode(), welcomeMessage = getString(config, ConfigOption.MESSAGE_FOR_NEW_PLAYERS);
(String) ConfigOption.MESSAGE_FOR_NEW_PLAYERS.getDefaultValue()); formatBooks = getBoolean(config, ConfigOption.FORMAT_AFTER_SIGNING);
formatBooks = config.getBoolean(ConfigOption.FORMAT_AFTER_SIGNING.getConfigNode(), changeGenerationOnCopy = getBoolean(config, ConfigOption.CHANGE_GENERATION_ON_COPY);
(Boolean) ConfigOption.FORMAT_AFTER_SIGNING.getDefaultValue());
//Convert string into material //Convert string into material
String paymentMaterial = config.getString(ConfigOption.PRICE_ITEM_TYPE.getConfigNode(), String paymentMaterial = getString(config, ConfigOption.PRICE_ITEM_TYPE);
(String) ConfigOption.PRICE_ITEM_TYPE.getDefaultValue());
if (paymentMaterial.equalsIgnoreCase("Economy")) { if (paymentMaterial.equalsIgnoreCase("Economy")) {
if (EconomyHelper.setupEconomy()) { if (EconomyHelper.setupEconomy()) {
bookPriceType = Material.AIR; bookPriceType = Material.AIR;
} else { } 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; bookPriceType = null;
} }
} else if (!paymentMaterial.trim().isEmpty()) { } else if (!paymentMaterial.trim().isEmpty()) {
@ -332,8 +335,7 @@ public class BooksWithoutBordersConfig {
bookPriceType = material; bookPriceType = material;
} }
} }
bookPriceQuantity = config.getDouble(ConfigOption.PRICE_QUANTITY.getConfigNode(), bookPriceQuantity = getDouble(config, ConfigOption.PRICE_QUANTITY);
(Double) ConfigOption.PRICE_QUANTITY.getDefaultValue());
//Make sure titleAuthorSeparator is a valid value //Make sure titleAuthorSeparator is a valid value
titleAuthorSeparator = cleanString(titleAuthorSeparator); titleAuthorSeparator = cleanString(titleAuthorSeparator);
@ -352,4 +354,48 @@ public class BooksWithoutBordersConfig {
return true; return true;
} }
/**
* Gets the double value of the given config option
*
* @param config <p>The configuration to read from</p>
* @param configOption <p>The configuration option to get the value for</p>
* @return <p>The value of the option</p>
*/
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 <p>The configuration to read from</p>
* @param configOption <p>The configuration option to get the value for</p>
* @return <p>The value of the option</p>
*/
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 <p>The configuration to read from</p>
* @param configOption <p>The configuration option to get the value for</p>
* @return <p>The value of the option</p>
*/
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 <p>The configuration to read from</p>
* @param configOption <p>The configuration option to get the value for</p>
* @return <p>The value of the option</p>
*/
private static boolean getBoolean(Configuration config, ConfigOption configOption) {
return config.getBoolean(configOption.getConfigNode(), (Boolean) configOption.getDefaultValue());
}
} }

View File

@ -60,6 +60,11 @@ public enum ConfigOption {
*/ */
AUTHOR_ONLY_UNSIGN("Author_Only_Unsign", false), 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 * Whether to automatically format every signed book
*/ */

View File

@ -3,6 +3,7 @@ package net.knarcraft.bookswithoutborders.utility;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders; import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig; import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.BookMeta; import org.bukkit.inventory.meta.BookMeta;
import static net.knarcraft.bookswithoutborders.utility.InputCleaningHelper.cleanString; 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 <p>The book item to increase the generation of</p>
*/
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
*
* <p>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</p>
*
* @param currentGeneration <p>The current generation of the book</p>
* @return <p>The next generation of the book</p>
*/
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 * Gets the file name of the given book
* *

View File

@ -25,4 +25,7 @@ Options:
Author_Only_Unsign: false Author_Only_Unsign: false
# Whether to automatically format every book when it's signed # Whether to automatically format every book when it's signed
Format_Book_After_Signing: false 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

View File

@ -88,7 +88,7 @@ commands:
reload: reload:
description: Reloads BwB's configuration file description: Reloads BwB's configuration file
usage: /<command> usage: /<command>
permission: bookswithoutborders.admin permission: bookswithoutborders.reload
permissions: permissions:
bookswithoutborders.*: bookswithoutborders.*:
description: Grants all permissions description: Grants all permissions
@ -114,6 +114,7 @@ permissions:
bookswithoutborders.bypassauthoronlyunsign: true bookswithoutborders.bypassauthoronlyunsign: true
bookswithoutborders.bypassbookprice: true bookswithoutborders.bypassbookprice: true
bookswithoutborders.setbookprice: true bookswithoutborders.setbookprice: true
bookswithoutborders.reload: true
bookswithoutborders.use: bookswithoutborders.use:
description: Allows player to use commands to save/load/delete in their personal directory description: Allows player to use commands to save/load/delete in their personal directory
children: children:
@ -170,3 +171,5 @@ permissions:
description: Allows player to ignore Price_to_create_book config setting description: Allows player to ignore Price_to_create_book config setting
bookswithoutborders.setbookprice: bookswithoutborders.setbookprice:
description: Allows player to set the cost of creating a book description: Allows player to set the cost of creating a book
bookswithoutborders.reload:
description: Allows player to reload this plugin