Starts adding string translations and cleanup

This commit is contained in:
2025-08-07 16:00:37 +02:00
parent 2146f00014
commit b1da544109
12 changed files with 563 additions and 149 deletions

View File

@@ -24,16 +24,22 @@ import net.knarcraft.bookswithoutborders.command.CommandSetLore;
import net.knarcraft.bookswithoutborders.command.CommandSetTitle; import net.knarcraft.bookswithoutborders.command.CommandSetTitle;
import net.knarcraft.bookswithoutborders.command.CommandUnSign; import net.knarcraft.bookswithoutborders.command.CommandUnSign;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig; import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
import net.knarcraft.bookswithoutborders.config.BwBCommand;
import net.knarcraft.bookswithoutborders.config.StaticMessage;
import net.knarcraft.bookswithoutborders.config.Translatable;
import net.knarcraft.bookswithoutborders.handler.BookshelfHandler; import net.knarcraft.bookswithoutborders.handler.BookshelfHandler;
import net.knarcraft.bookswithoutborders.listener.BookEventListener; import net.knarcraft.bookswithoutborders.listener.BookEventListener;
import net.knarcraft.bookswithoutborders.listener.BookshelfListener; import net.knarcraft.bookswithoutborders.listener.BookshelfListener;
import net.knarcraft.bookswithoutborders.listener.PlayerEventListener; import net.knarcraft.bookswithoutborders.listener.PlayerEventListener;
import net.knarcraft.bookswithoutborders.listener.SignEventListener; import net.knarcraft.bookswithoutborders.listener.SignEventListener;
import net.knarcraft.bookswithoutborders.utility.BookFileHelper; import net.knarcraft.bookswithoutborders.utility.BookFileHelper;
import net.knarcraft.knarlib.formatting.StringFormatter;
import net.knarcraft.knarlib.formatting.Translator;
import net.knarcraft.knarlib.property.ColorConversion;
import net.knarcraft.knarlib.util.UpdateChecker; import net.knarcraft.knarlib.util.UpdateChecker;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.command.PluginCommand; import org.bukkit.command.PluginCommand;
import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@@ -69,17 +75,16 @@ public class BooksWithoutBorders extends JavaPlugin {
private static Map<Character, Integer> publicLetterIndex; private static Map<Character, Integer> publicLetterIndex;
private static Map<UUID, Map<Character, Integer>> playerLetterIndex; private static Map<UUID, Map<Character, Integer>> playerLetterIndex;
private static BooksWithoutBorders booksWithoutBorders; private static BooksWithoutBorders booksWithoutBorders;
private static ConsoleCommandSender consoleSender;
private static BookshelfHandler bookshelfHandler; private static BookshelfHandler bookshelfHandler;
private static StringFormatter stringFormatter;
/** /**
* Gets the console sender for printing to the console * Gets the string formatter
* *
* @return <p>The console's console sender</p> * @return <p>The string formatter</p>
*/ */
@NotNull public static StringFormatter getStringFormatter() {
public static ConsoleCommandSender getConsoleSender() { return stringFormatter;
return consoleSender;
} }
/** /**
@@ -163,12 +168,18 @@ public class BooksWithoutBorders extends JavaPlugin {
PluginDescriptionFile pluginDescriptionFile = this.getDescription(); PluginDescriptionFile pluginDescriptionFile = this.getDescription();
String pluginVersion = pluginDescriptionFile.getVersion(); String pluginVersion = pluginDescriptionFile.getVersion();
Translator translator = new Translator();
translator.registerMessageCategory(Translatable.PREFIX);
stringFormatter = new StringFormatter(this.getDescription().getName(), translator);
stringFormatter.setColorConversion(ColorConversion.RGB);
stringFormatter.setSuccessColor(ChatColor.of("#A9FF84"));
stringFormatter.setErrorColor(ChatColor.of("#FF84A9"));
booksWithoutBorders = this; booksWithoutBorders = this;
consoleSender = this.getServer().getConsoleSender();
playerBooksList = new HashMap<>(); playerBooksList = new HashMap<>();
playerLetterIndex = new HashMap<>(); playerLetterIndex = new HashMap<>();
BooksWithoutBordersConfig.initialize(this); BooksWithoutBordersConfig.initialize(this, translator);
@Nullable List<String> files = BookFileHelper.listFiles(consoleSender, true); @Nullable List<String> files = BookFileHelper.listFiles(this.getServer().getConsoleSender(), true);
if (files != null) { if (files != null) {
publicBooksList = files; publicBooksList = files;
publicLetterIndex = BookFileHelper.populateLetterIndices(files); publicLetterIndex = BookFileHelper.populateLetterIndices(files);
@@ -207,29 +218,29 @@ public class BooksWithoutBorders extends JavaPlugin {
* Registers all commands used by this plugin * Registers all commands used by this plugin
*/ */
private void registerCommands() { private void registerCommands() {
registerCommand("giveBook", new CommandGive()); registerCommand(BwBCommand.GIVE_BOOK.toString(), new CommandGive());
registerCommand("givePublicBook", new CommandGivePublic()); registerCommand(BwBCommand.GIVE_PUBLIC_BOOK.toString(), new CommandGivePublic());
registerCommand("decryptBook", new CommandDecrypt()); registerCommand(BwBCommand.DECRYPT_BOOK.toString(), new CommandDecrypt());
registerCommand("groupEncryptBook", new CommandGroupEncrypt()); registerCommand(BwBCommand.GROUP_ENCRYPT_BOOK.toString(), new CommandGroupEncrypt());
registerCommand("deleteBook", new CommandDelete()); registerCommand(BwBCommand.DELETE_BOOK.toString(), new CommandDelete());
registerCommand("deletePublicBook", new CommandDeletePublic()); registerCommand(BwBCommand.DELETE_PUBLIC_BOOK.toString(), new CommandDeletePublic());
registerCommand("copyBook", new CommandCopy()); registerCommand(BwBCommand.COPY_BOOK.toString(), new CommandCopy());
registerCommand("unSignBook", new CommandUnSign()); registerCommand(BwBCommand.UNSIGN_BOOK.toString(), new CommandUnSign());
registerCommand("encryptBook", new CommandEncrypt()); registerCommand(BwBCommand.ENCRYPT_BOOK.toString(), new CommandEncrypt());
registerCommand("setBookPrice", new CommandSetBookPrice()); registerCommand(BwBCommand.SET_BOOK_PRICE.toString(), new CommandSetBookPrice());
registerCommand("setLore", new CommandSetLore()); registerCommand(BwBCommand.SET_LORE.toString(), new CommandSetLore());
registerCommand("savePublicBook", new CommandSavePublic()); registerCommand(BwBCommand.SAVE_PUBLIC_BOOK.toString(), new CommandSavePublic());
registerCommand("saveBook", new CommandSave()); registerCommand(BwBCommand.SAVE_BOOK.toString(), new CommandSave());
registerCommand("setBookAuthor", new CommandSetAuthor()); registerCommand(BwBCommand.SET_BOOK_AUTHOR.toString(), new CommandSetAuthor());
registerCommand("setTitle", new CommandSetTitle()); registerCommand(BwBCommand.SET_TITLE.toString(), new CommandSetTitle());
registerCommand("loadBook", new CommandLoad()); registerCommand(BwBCommand.LOAD_BOOK.toString(), new CommandLoad());
registerCommand("loadPublicBook", new CommandLoadPublic()); registerCommand(BwBCommand.LOAD_PUBLIC_BOOK.toString(), new CommandLoadPublic());
registerCommand("booksWithoutBorders", new CommandBooksWithoutBorders()); registerCommand(BwBCommand.BOOKS_WITHOUT_BORDERS.toString(), new CommandBooksWithoutBorders());
registerCommand("reload", new CommandReload()); registerCommand(BwBCommand.RELOAD.toString(), new CommandReload());
registerCommand("formatBook", new CommandFormat()); registerCommand(BwBCommand.FORMAT_BOOK.toString(), new CommandFormat());
registerCommand("setBookGeneration", new CommandSetGeneration()); registerCommand(BwBCommand.SET_BOOK_GENERATION.toString(), new CommandSetGeneration());
registerCommand("clearBook", new CommandClear()); registerCommand(BwBCommand.CLEAR_BOOK.toString(), new CommandClear());
registerCommand("setBookshelfData", new CommandSetBookshelfData()); registerCommand(BwBCommand.SET_BOOKSHELF_DATA.toString(), new CommandSetBookshelfData());
} }
/** /**
@@ -243,7 +254,7 @@ public class BooksWithoutBorders extends JavaPlugin {
if (pluginCommand != null) { if (pluginCommand != null) {
pluginCommand.setExecutor(executor); pluginCommand.setExecutor(executor);
} else { } else {
sendErrorMessage(consoleSender, "Failed to register command " + commandName); getLogger().log(Level.SEVERE, "Failed to register command " + commandName);
} }
} }
@@ -256,10 +267,11 @@ public class BooksWithoutBorders extends JavaPlugin {
//Initialize Item Factory //Initialize Item Factory
try { try {
itemFactory = this.getServer().getItemFactory(); itemFactory = this.getServer().getItemFactory();
} catch (java.lang.NoSuchMethodError nsmE) { } catch (java.lang.NoSuchMethodError noSuchMethodError) {
sendErrorMessage(consoleSender, "Warning! [BooksWithoutBorders] failed to initialize!"); getLogger().log(Level.SEVERE, """
sendErrorMessage(consoleSender, "Please confirm the correct version of [BooksWithoutBorders] is"); Warning! [BooksWithoutBorders] failed to initialize!
sendErrorMessage(consoleSender, "being run for this version of bukkit!"); Please confirm the correct version of [BooksWithoutBorders] is
being run for this version of spigot!""");
return false; return false;
} }
@@ -304,22 +316,22 @@ public class BooksWithoutBorders extends JavaPlugin {
if (!fileTest.exists()) { if (!fileTest.exists()) {
try { try {
if (!fileTest.mkdir()) { if (!fileTest.mkdir()) {
sendErrorMessage(consoleSender, "Saving failed! Aborting..."); getLogger().log(Level.SEVERE, StaticMessage.BOOK_SAVING_FAILED.toString());
return false; return false;
} }
} catch (Exception exception) { } catch (Exception exception) {
BooksWithoutBorders.getInstance().getLogger().log(Level.SEVERE, "Unable to create necessary folders"); getLogger().log(Level.SEVERE, StaticMessage.BOOK_FOLDER_CREATE_FAILED.toString());
return false; return false;
} }
} }
if (!encryptedFileTest.exists()) { if (!encryptedFileTest.exists()) {
try { try {
if (!encryptedFileTest.mkdir()) { if (!encryptedFileTest.mkdir()) {
sendErrorMessage(consoleSender, "Saving failed! Aborting..."); getLogger().log(Level.SEVERE, StaticMessage.BOOK_SAVING_FAILED.toString());
return false; return false;
} }
} catch (Exception exception) { } catch (Exception exception) {
BooksWithoutBorders.getInstance().getLogger().log(Level.SEVERE, "Unable to create necessary folders"); getLogger().log(Level.SEVERE, StaticMessage.BOOK_FOLDER_CREATE_FAILED.toString());
return false; return false;
} }
} }

View File

@@ -2,7 +2,12 @@ package net.knarcraft.bookswithoutborders.command;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders; import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig; import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
import net.knarcraft.bookswithoutborders.config.BwBCommand;
import net.knarcraft.bookswithoutborders.config.Permission;
import net.knarcraft.bookswithoutborders.config.StaticMessage;
import net.knarcraft.bookswithoutborders.config.Translatable;
import net.knarcraft.bookswithoutborders.utility.EconomyHelper; import net.knarcraft.bookswithoutborders.utility.EconomyHelper;
import net.knarcraft.knarlib.formatting.StringFormatter;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
@@ -13,10 +18,7 @@ import org.jetbrains.annotations.NotNull;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.logging.Level;
import static net.knarcraft.bookswithoutborders.BooksWithoutBorders.sendErrorMessage;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getCommandColor;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getSuccessColor;
/** /**
* Command executor for the books without borders (bwb) command * Command executor for the books without borders (bwb) command
@@ -24,29 +26,68 @@ import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig
public class CommandBooksWithoutBorders implements TabExecutor { public class CommandBooksWithoutBorders implements TabExecutor {
@Override @Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
sender.sendMessage(getCommandColor() + "[] denote optional parameters"); @NotNull String[] arguments) {
sender.sendMessage(getCommandColor() + "<> denote required parameters"); StringFormatter stringFormatter = BooksWithoutBorders.getStringFormatter();
sender.sendMessage(getCommandColor() + "{} denote required permission"); String header = stringFormatter.replacePlaceholders(Translatable.NEUTRAL_COMMANDS_HEADER,
sender.sendMessage(getCommandColor() + "In some cases, commands with required parameters can be called with no parameters"); List.of("{bookPrice}", "{commands}"), List.of(getBookPrice(), getCommands(sender)));
if (sender instanceof Player) { sender.sendMessage(header);
showPlayerCommands(sender);
} else {
showConsoleCommands(sender);
}
return true; return true;
} }
/**
* Gets the list of commands
*
* @param sender <p>The command sender trying to see available commands</p>
* @return <p>The string representation of all commands</p>
*/
@NotNull
private String getCommands(@NotNull CommandSender sender) {
if (sender instanceof Player) {
return showPlayerCommands(sender);
} else {
return showConsoleCommands(sender);
}
}
/**
* Gets the price of duplicating a book
*
* @return <p>The book price</p>
*/
@NotNull
private String getBookPrice() {
if (!BooksWithoutBordersConfig.booksHavePrice()) {
return "";
}
StringFormatter stringFormatter = BooksWithoutBorders.getStringFormatter();
Material bookPriceType = BooksWithoutBordersConfig.getBookPriceType();
double bookPriceQuantity = BooksWithoutBordersConfig.getBookPriceQuantity();
if (bookPriceType != Material.AIR) {
return stringFormatter.replacePlaceholders(Translatable.NEUTRAL_COMMANDS_BOOK_PRICE_ITEM,
List.of("{quantity}", "{type}"),
List.of(String.valueOf((int) bookPriceQuantity), bookPriceType.toString()));
} else {
return stringFormatter.replacePlaceholder(Translatable.NEUTRAL_COMMANDS_BOOK_PRICE_ECO,
"{price}", EconomyHelper.getEconomy().format(bookPriceQuantity));
}
}
/** /**
* Shows all commands available to the console * Shows all commands available to the console
* *
* @param sender <p>The console which sent the command</p> * @param sender <p>The console which sent the command</p>
*/ */
private void showConsoleCommands(@NotNull CommandSender sender) { @NotNull
sender.sendMessage(getCommandColor() + "Commands:"); private String showConsoleCommands(@NotNull CommandSender sender) {
showCommandInfo("deletePublicBook", sender); StringBuilder builder = new StringBuilder();
showCommandInfo("givePublicBook", sender); for (BwBCommand command : BwBCommand.values()) {
showCommandInfo("reload", sender); if (!command.requiresPlayer()) {
builder.append(showCommandInfo(command.toString(), sender));
}
}
return builder.toString();
} }
/** /**
@@ -54,44 +95,13 @@ public class CommandBooksWithoutBorders implements TabExecutor {
* *
* @param sender <p>The player which sent the command</p> * @param sender <p>The player which sent the command</p>
*/ */
private void showPlayerCommands(@NotNull CommandSender sender) { @NotNull
//Lists all commands private String showPlayerCommands(@NotNull CommandSender sender) {
Material bookPriceType = BooksWithoutBordersConfig.getBookPriceType(); StringBuilder builder = new StringBuilder();
double bookPriceQuantity = BooksWithoutBordersConfig.getBookPriceQuantity(); for (BwBCommand command : BwBCommand.values()) {
if (BooksWithoutBordersConfig.booksHavePrice()) { builder.append(showCommandInfo(command.toString(), sender));
if (bookPriceType != Material.AIR) {
sendErrorMessage(sender, "[" + (int) bookPriceQuantity + " " + bookPriceType.toString() +
"(s) are required to create a book]");
} else {
sendErrorMessage(sender, "[" + EconomyHelper.getEconomy().format(bookPriceQuantity) +
" is required to create a book]");
} }
} return builder.toString();
sender.sendMessage(getCommandColor() + "Commands:");
showCommandInfo("booksWithoutBorders", sender);
showCommandInfo("copyBook", sender);
showCommandInfo("decryptBook", sender);
showCommandInfo("deleteBook", sender);
showCommandInfo("deletePublicBook", sender);
showCommandInfo("encryptBook", sender);
showCommandInfo("formatBook", sender);
showCommandInfo("giveBook", sender);
showCommandInfo("givePublicBook", sender);
showCommandInfo("groupEncryptBook", sender);
showCommandInfo("loadBook", sender);
showCommandInfo("loadPublicBook", sender);
showCommandInfo("reload", sender);
showCommandInfo("saveBook", sender);
showCommandInfo("savePublicBook", sender);
showCommandInfo("setAuthor", sender);
showCommandInfo("setBookGeneration", sender);
showCommandInfo("setBookPrice", sender);
showCommandInfo("setLore", sender);
showCommandInfo("setTitle", sender);
showCommandInfo("unsignBook", sender);
showCommandInfo("clearBook", sender);
showCommandInfo("setBookshelfData", sender);
} }
/** /**
@@ -100,22 +110,32 @@ public class CommandBooksWithoutBorders implements TabExecutor {
* @param command <p>The command to get information about</p> * @param command <p>The command to get information about</p>
* @param sender <p>The sender asking to see command info</p> * @param sender <p>The sender asking to see command info</p>
*/ */
private void showCommandInfo(@NotNull String command, @NotNull CommandSender sender) { @NotNull
private String showCommandInfo(@NotNull String command, @NotNull CommandSender sender) {
PluginCommand pluginCommand = BooksWithoutBorders.getInstance().getCommand(command); PluginCommand pluginCommand = BooksWithoutBorders.getInstance().getCommand(command);
if (pluginCommand != null) { if (pluginCommand == null) {
BooksWithoutBorders.getInstance().getLogger().log(Level.SEVERE, StringFormatter.replacePlaceholder(
StaticMessage.COMMAND_NOT_REGISTERED.toString(), "{command}", command));
return "";
}
String permission = pluginCommand.getPermission(); String permission = pluginCommand.getPermission();
if (permission == null || sender.hasPermission(permission)) { if (permission != null && !sender.hasPermission(permission)) {
String commandInfo = "\n" + getCommandColor() + pluginCommand.getUsage().replace("<command>", return "";
pluginCommand.getName()) + ": " + getSuccessColor() + pluginCommand.getDescription(); }
if (sender.hasPermission("bookswithoutborders.admin")) {
StringFormatter stringFormatter = BooksWithoutBorders.getStringFormatter();
String commandDescription = stringFormatter.replacePlaceholders(Translatable.NEUTRAL_COMMANDS_COMMAND,
List.of("{usage}", "{description}"), List.of(pluginCommand.getUsage().replace("<command>",
pluginCommand.getName()), pluginCommand.getDescription()));
if (sender.hasPermission(Permission.ADMIN.toString())) {
if (permission == null) { if (permission == null) {
permission = "None"; permission = stringFormatter.getUnFormattedColoredMessage(Translatable.NEUTRAL_COMMANDS_COMMAND_NO_PERMISSION_REQUIRED);
}
commandInfo += getCommandColor() + " {" + permission + "}";
}
sender.sendMessage(commandInfo);
} }
commandDescription += stringFormatter.replacePlaceholder(Translatable.NEUTRAL_COMMANDS_COMMAND_PERMISSION, "{permission}", permission);
} }
return commandDescription;
} }
@Override @Override

View File

@@ -1,6 +1,7 @@
package net.knarcraft.bookswithoutborders.command; package net.knarcraft.bookswithoutborders.command;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders; import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.Translatable;
import net.knarcraft.bookswithoutborders.utility.InventoryHelper; import net.knarcraft.bookswithoutborders.utility.InventoryHelper;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
@@ -23,7 +24,7 @@ public class CommandClear implements TabExecutor {
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
@NotNull String[] args) { @NotNull String[] args) {
if (!(sender instanceof Player player)) { if (!(sender instanceof Player player)) {
BooksWithoutBorders.sendErrorMessage(sender, "This command can only be used by a player!"); BooksWithoutBorders.getStringFormatter().displayErrorMessage(sender, Translatable.ERROR_PLAYER_ONLY);
return false; return false;
} }

View File

@@ -2,10 +2,13 @@ package net.knarcraft.bookswithoutborders.command;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders; import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig; import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
import net.knarcraft.bookswithoutborders.config.Permission;
import net.knarcraft.bookswithoutborders.config.Translatable;
import net.knarcraft.bookswithoutborders.utility.BookHelper; 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.TabCompletionTypeHelper; import net.knarcraft.bookswithoutborders.utility.TabCompletionTypeHelper;
import net.knarcraft.knarlib.formatting.StringFormatter;
import net.knarcraft.knarlib.util.TabCompletionHelper; import net.knarcraft.knarlib.util.TabCompletionHelper;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.command.Command; import org.bukkit.command.Command;
@@ -28,18 +31,21 @@ public class CommandCopy implements TabExecutor {
@Override @Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
@NotNull String[] arguments) { @NotNull String[] arguments) {
StringFormatter stringFormatter = BooksWithoutBorders.getStringFormatter();
if (!(sender instanceof Player player)) { if (!(sender instanceof Player player)) {
BooksWithoutBorders.sendErrorMessage(sender, "This command can only be used by a player!"); stringFormatter.displayErrorMessage(sender, Translatable.ERROR_PLAYER_ONLY);
return false; return false;
} }
if (InventoryHelper.notHoldingOneWrittenBookCheck(player, "You must be holding a written book to copy it!", if (InventoryHelper.notHoldingOneWrittenBookCheck(player,
"You cannot copy two books at once!")) { stringFormatter.getUnFormattedColoredMessage(Translatable.ERROR_NOT_HOLDING_BOOK_COPY),
stringFormatter.getUnFormattedColoredMessage(Translatable.ERROR_ONLY_ONE_BOOK_COPY))) {
return false; return false;
} }
if (arguments.length < 1) { if (arguments.length < 1) {
BooksWithoutBorders.sendErrorMessage(player, "You must specify the number of copies to be made!"); stringFormatter.displayErrorMessage(player, Translatable.ERROR_COPY_COUNT_NOT_SPECIFIED);
return false; return false;
} }
@@ -47,12 +53,12 @@ public class CommandCopy implements TabExecutor {
ItemStack heldBook = InventoryHelper.getHeldBook(player, true); ItemStack heldBook = InventoryHelper.getHeldBook(player, true);
int copies = Integer.parseInt(arguments[0]); int copies = Integer.parseInt(arguments[0]);
if (copies <= 0) { if (copies <= 0) {
throw new NumberFormatException("Number of copies must be larger than 0"); stringFormatter.displayErrorMessage(player, Translatable.ERROR_COPY_NEGATIVE_AMOUNT);
return false;
} }
return performCopy(copies, player, heldBook); return performCopy(copies, player, heldBook);
} catch (NumberFormatException ignored) { } catch (NumberFormatException ignored) {
BooksWithoutBorders.sendErrorMessage(player, "Book not copied!"); stringFormatter.displayErrorMessage(player, Translatable.ERROR_COPY_INVALID_AMOUNT);
BooksWithoutBorders.sendErrorMessage(player, "Number specified was invalid!");
return false; return false;
} }
} }
@@ -68,7 +74,7 @@ public class CommandCopy implements TabExecutor {
private boolean performCopy(int copies, @NotNull Player player, @NotNull ItemStack heldBook) { private boolean performCopy(int copies, @NotNull Player player, @NotNull ItemStack heldBook) {
//Make sure the player owns the book if authorOnlyCopy is enabled //Make sure the player owns the book if authorOnlyCopy is enabled
if (BooksWithoutBordersConfig.getAuthorOnlyCopy() && if (BooksWithoutBordersConfig.getAuthorOnlyCopy() &&
!player.hasPermission("bookswithoutborders.bypassAuthorOnlyCopy")) { !player.hasPermission(Permission.BYPASS_AUTHOR_ONLY_COPY.toString())) {
if (BookHelper.isNotAuthor(player, (BookMeta) Objects.requireNonNull(heldBook.getItemMeta()))) { if (BookHelper.isNotAuthor(player, (BookMeta) Objects.requireNonNull(heldBook.getItemMeta()))) {
return false; return false;
} }
@@ -84,7 +90,7 @@ public class CommandCopy implements TabExecutor {
} }
heldBook.setAmount(heldBook.getAmount() + copies); heldBook.setAmount(heldBook.getAmount() + copies);
BooksWithoutBorders.sendSuccessMessage(player, "Book copied!"); BooksWithoutBorders.getStringFormatter().displaySuccessMessage(player, Translatable.SUCCESS_COPY);
return true; return true;
} }
} }
@@ -98,7 +104,7 @@ public class CommandCopy implements TabExecutor {
*/ */
private boolean paymentUnSuccessful(@NotNull Player player, int copies) { private boolean paymentUnSuccessful(@NotNull Player player, int copies) {
return BooksWithoutBordersConfig.booksHavePrice() && return BooksWithoutBordersConfig.booksHavePrice() &&
!player.hasPermission("bookswithoutborders.bypassBookPrice") && !player.hasPermission(Permission.BYPASS_BOOK_PRICE.toString()) &&
EconomyHelper.cannotPayForBookPrinting(player, copies); EconomyHelper.cannotPayForBookPrinting(player, copies);
} }
@@ -114,14 +120,13 @@ public class CommandCopy implements TabExecutor {
//Copy the vanilla behavior of refusing copying any further //Copy the vanilla behavior of refusing copying any further
if (bookMeta.getGeneration() == BookMeta.Generation.COPY_OF_COPY || if (bookMeta.getGeneration() == BookMeta.Generation.COPY_OF_COPY ||
bookMeta.getGeneration() == BookMeta.Generation.TATTERED) { bookMeta.getGeneration() == BookMeta.Generation.TATTERED) {
BooksWithoutBorders.sendErrorMessage(player, "You cannot copy this book any further. " + BooksWithoutBorders.getStringFormatter().displayErrorMessage(player, Translatable.ERROR_BOOK_COPIED_TOO_FAR);
"You must have the original or a direct copy.");
return false; return false;
} }
//Make sure the player can fit the book in their inventory //Make sure the player can fit the book in their inventory
int nextAvailableSlot = player.getInventory().firstEmpty(); int nextAvailableSlot = player.getInventory().firstEmpty();
if (nextAvailableSlot == -1) { if (nextAvailableSlot == -1) {
BooksWithoutBorders.sendErrorMessage(player, "You need an available slot in your inventory."); BooksWithoutBorders.getStringFormatter().displayErrorMessage(player, Translatable.ERROR_INVENTORY_FULL);
return false; return false;
} }
//Make sure the player can pay for the copying //Make sure the player can pay for the copying

View File

@@ -2,18 +2,18 @@ package net.knarcraft.bookswithoutborders.config;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders; import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.utility.EconomyHelper; import net.knarcraft.bookswithoutborders.utility.EconomyHelper;
import net.knarcraft.knarlib.formatting.Translator;
import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.ChatColor;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.configuration.Configuration; import org.bukkit.configuration.Configuration;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.nio.file.FileSystems; import java.nio.file.FileSystems;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import static net.knarcraft.bookswithoutborders.BooksWithoutBorders.sendErrorMessage;
import static net.knarcraft.bookswithoutborders.BooksWithoutBorders.sendSuccessMessage;
import static net.knarcraft.bookswithoutborders.utility.InputCleaningHelper.cleanString; import static net.knarcraft.bookswithoutborders.utility.InputCleaningHelper.cleanString;
/** /**
@@ -44,15 +44,18 @@ public class BooksWithoutBordersConfig {
private static boolean changeGenerationOnCopy; private static boolean changeGenerationOnCopy;
private static boolean enableBookshelfPeeking; private static boolean enableBookshelfPeeking;
private static Translator translator;
/** /**
* Initializes the books without borders settings class * Initializes the books without borders settings class
* *
* @param booksWithoutBorders <p>The books without borders object used for getting required data</p> * @param booksWithoutBorders <p>The books without borders object used for getting required data</p>
*/ */
public static void initialize(@NotNull BooksWithoutBorders booksWithoutBorders) { public static void initialize(@NotNull BooksWithoutBorders booksWithoutBorders, @NotNull Translator translator) {
if (isInitialized) { if (isInitialized) {
throw new IllegalArgumentException("Settings class initialized twice. This should not happen!"); throw new IllegalArgumentException("Settings class initialized twice. This should not happen!");
} }
BooksWithoutBordersConfig.translator = translator;
isInitialized = true; isInitialized = true;
bookFolder = booksWithoutBorders.getDataFolder().getAbsolutePath() + getSlash() + "Books" + getSlash(); bookFolder = booksWithoutBorders.getDataFolder().getAbsolutePath() + getSlash() + "Books" + getSlash();
loadConfig(); loadConfig();
@@ -275,7 +278,7 @@ public class BooksWithoutBordersConfig {
* Saves the config * Saves the config
*/ */
public static void saveConfigValues() { public static void saveConfigValues() {
ConsoleCommandSender consoleSender = BooksWithoutBorders.getInstance().getServer().getConsoleSender(); Logger logger = BooksWithoutBorders.getInstance().getLogger();
Configuration config = BooksWithoutBorders.getInstance().getConfig(); Configuration config = BooksWithoutBorders.getInstance().getConfig();
config.set(ConfigOption.USE_YAML.getConfigNode(), useYml); config.set(ConfigOption.USE_YAML.getConfigNode(), useYml);
config.set(ConfigOption.MAX_DUPLICATES.getConfigNode(), bookDuplicateLimit); config.set(ConfigOption.MAX_DUPLICATES.getConfigNode(), bookDuplicateLimit);
@@ -306,9 +309,8 @@ public class BooksWithoutBordersConfig {
//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")) {
sendSuccessMessage(consoleSender, "[BooksWithoutBorders] Found old config setting " + logger.log(Level.INFO, "[BooksWithoutBorders] Found old config setting " +
"\"Require_book_and_quill_to_create_book\""); "\"Require_book_and_quill_to_create_book\"\nUpdating to \"Price_to_create_book\" settings");
sendSuccessMessage(consoleSender, "Updating to \"Price_to_create_book\" settings");
if (config.getBoolean("Options.Require_book_and_quill_to_create_book")) { if (config.getBoolean("Options.Require_book_and_quill_to_create_book")) {
bookPriceType = Material.WRITABLE_BOOK; bookPriceType = Material.WRITABLE_BOOK;
@@ -327,7 +329,7 @@ public class BooksWithoutBordersConfig {
* @return <p>True if the config was loaded successfully</p> * @return <p>True if the config was loaded successfully</p>
*/ */
public static boolean loadConfig() { public static boolean loadConfig() {
ConsoleCommandSender consoleSender = BooksWithoutBorders.getInstance().getServer().getConsoleSender(); Logger logger = BooksWithoutBorders.getInstance().getLogger();
BooksWithoutBorders.getInstance().reloadConfig(); BooksWithoutBorders.getInstance().reloadConfig();
Configuration config = BooksWithoutBorders.getInstance().getConfig(); Configuration config = BooksWithoutBorders.getInstance().getConfig();
try { try {
@@ -345,6 +347,8 @@ public class BooksWithoutBordersConfig {
formatBooks = getBoolean(config, ConfigOption.FORMAT_AFTER_SIGNING); formatBooks = getBoolean(config, ConfigOption.FORMAT_AFTER_SIGNING);
changeGenerationOnCopy = getBoolean(config, ConfigOption.CHANGE_GENERATION_ON_COPY); changeGenerationOnCopy = getBoolean(config, ConfigOption.CHANGE_GENERATION_ON_COPY);
enableBookshelfPeeking = getBoolean(config, ConfigOption.ENABLE_BOOKSHELF_PEEKING); enableBookshelfPeeking = getBoolean(config, ConfigOption.ENABLE_BOOKSHELF_PEEKING);
String language = config.getString("language", "en");
translator.loadLanguages(BooksWithoutBorders.getInstance().getDataFolder(), "en", language);
//Convert string into material //Convert string into material
String paymentMaterial = getString(config, ConfigOption.PRICE_ITEM_TYPE); String paymentMaterial = getString(config, ConfigOption.PRICE_ITEM_TYPE);
@@ -352,8 +356,7 @@ public class BooksWithoutBordersConfig {
if (EconomyHelper.setupEconomy()) { if (EconomyHelper.setupEconomy()) {
bookPriceType = Material.AIR; bookPriceType = Material.AIR;
} else { } else {
sendErrorMessage(consoleSender, logger.log(Level.SEVERE, "BooksWithoutBorders failed to hook into Vault! Book price not set!");
"BooksWithoutBorders failed to hook into Vault! Book price not set!");
bookPriceType = null; bookPriceType = null;
} }
} else if (!paymentMaterial.trim().isEmpty()) { } else if (!paymentMaterial.trim().isEmpty()) {
@@ -368,14 +371,14 @@ public class BooksWithoutBordersConfig {
//Make sure titleAuthorSeparator is a valid value //Make sure titleAuthorSeparator is a valid value
titleAuthorSeparator = cleanString(titleAuthorSeparator); titleAuthorSeparator = cleanString(titleAuthorSeparator);
if (titleAuthorSeparator.length() != 1) { if (titleAuthorSeparator.length() != 1) {
sendErrorMessage(consoleSender, "Title-Author_Separator is set to an invalid value!"); logger.log(Level.SEVERE, "Title-Author_Separator is set to an invalid value!\n" +
sendErrorMessage(consoleSender, "Reverting to default value of \",\""); "Reverting to default value of \",\"");
titleAuthorSeparator = ","; titleAuthorSeparator = ",";
config.set("Options.Title-Author_Separator", titleAuthorSeparator); config.set("Options.Title-Author_Separator", titleAuthorSeparator);
} }
} catch (Exception e) { } catch (Exception e) {
sendErrorMessage(consoleSender, "Warning! Config.yml failed to load!"); logger.log(Level.SEVERE, "Warning! Config.yml failed to load!\n" +
sendErrorMessage(consoleSender, "Try Looking for settings that are missing values!"); "Try Looking for settings that are missing values!");
return false; return false;
} }

View File

@@ -0,0 +1,160 @@
package net.knarcraft.bookswithoutborders.config;
import org.jetbrains.annotations.NotNull;
/**
* A representation of a BwB command
*/
public enum BwBCommand {
/**
* The help command
*/
BOOKS_WITHOUT_BORDERS("booksWithoutBorders", false),
/**
* Clears the contents of a book
*/
CLEAR_BOOK("clearBook", true),
/**
* Copies the held book
*/
COPY_BOOK("copyBook", true),
/**
* Decrypts the held encrypted book
*/
DECRYPT_BOOK("decryptBook", true),
/**
* Deletes a book from a player's private collection
*/
DELETE_BOOK("deleteBook", true),
/**
* Deletes a book from the public collection
*/
DELETE_PUBLIC_BOOK("deletePublicBook", false),
/**
* Encrypts the held book
*/
ENCRYPT_BOOK("encryptBook", true),
/**
* Executes formatting codes in the held book
*/
FORMAT_BOOK("formatBook", true),
/**
* Gives a book from a player's private collection to another player
*/
GIVE_BOOK("giveBook", true),
/**
* Gives a book from the public collection to a player
*/
GIVE_PUBLIC_BOOK("givePublicBook", false),
/**
* Encrypts a book for specific group
*/
GROUP_ENCRYPT_BOOK("groupEncryptBook", true),
/**
* Loads a book from a player's private collection
*/
LOAD_BOOK("loadBook", true),
/**
* Loads a book from the public collection
*/
LOAD_PUBLIC_BOOK("loadPublicBook", true),
/**
* Reloads the plugin's configuration and the book lists
*/
RELOAD("reload", false),
/**
* Saves a book to a player's private collection
*/
SAVE_BOOK("saveBook", true),
/**
* Saves a book to the public collection
*/
SAVE_PUBLIC_BOOK("savePublicBook", true),
/**
* Sets the author of the held book
*/
SET_BOOK_AUTHOR("setBookAuthor", true),
/**
* Sets the generation of the held book
*/
SET_BOOK_GENERATION("setBookGeneration", true),
/**
* Sets the price of copying, loading and giving books
*/
SET_BOOK_PRICE("setBookPrice", false),
/**
* Sets the name/lore for the chiseled bookshelf in front of the player, displayed when peeking the bookshelf
*/
SET_BOOKSHELF_DATA("setBookshelfData", true),
/**
* Sets the lore of the held book/item
*/
SET_LORE("setLore", true),
/**
* Sets the book title of the held signed book, or the display name of the held item
*/
SET_TITLE("setTitle", true),
/**
* Un-signs the held signed book
*/
UNSIGN_BOOK("unsignBook", true),
;
private final @NotNull String commandName;
private final boolean requiresPlayer;
/**
* Instantiates a new command
*
* @param commandName <p>The name of the command</p>
* @param requiresPlayer <p>Whether the command requires to be run by a player</p>
*/
BwBCommand(@NotNull String commandName, boolean requiresPlayer) {
this.commandName = commandName;
this.requiresPlayer = requiresPlayer;
}
/**
* Gets whether this command can only be used by a player
*
* @return <p>True if this command must be run by a player</p>
*/
public boolean requiresPlayer() {
return this.requiresPlayer;
}
/**
* Return name instead of enum when displayed as a string
*
* @return <p>The command name</p>
*/
@Override
@NotNull
public String toString() {
return this.commandName;
}
}

View File

@@ -0,0 +1,59 @@
package net.knarcraft.bookswithoutborders.config;
import org.jetbrains.annotations.NotNull;
/**
* A representation of a BwB permission
*/
public enum Permission {
/**
* The permission for bypassing paying the set book printing price
*/
BYPASS_BOOK_PRICE("bypassBookPrice"),
/**
* The admin permission, giving most/all other permissions
*/
ADMIN("admin"),
/**
* The permission for bypassing the setting making sure a book can only be copied by its author
*/
BYPASS_AUTHOR_ONLY_COPY("bypassAuthorOnlyCopy"),
;
private final @NotNull String node;
/**
* Instantiates a new permission
*
* @param node <p>The permission's permission node</p>
*/
Permission(@NotNull String node) {
this.node = node;
}
/**
* Gets the node of this permission
*
* @return <p>The permission node</p>
*/
@NotNull
public String getNode() {
return "bookswithoutborders." + this.node;
}
/**
* Return node instead of enum when displayed as a string
*
* @return <p>The permission node string</p>
*/
@Override
@NotNull
public String toString() {
return getNode();
}
}

View File

@@ -0,0 +1,31 @@
package net.knarcraft.bookswithoutborders.config;
import org.jetbrains.annotations.NotNull;
/**
* Messages shown in the console that cannot be altered by users
*/
public enum StaticMessage {
BOOK_SAVING_FAILED("Saving failed! Aborting..."),
BOOK_FOLDER_CREATE_FAILED("Unable to create necessary folders"),
COMMAND_NOT_REGISTERED("Command {command} has not been registered!");
private final @NotNull String messageString;
/**
* Instantiates a new static message
*
* @param messageString <p>The message string</p>
*/
StaticMessage(@NotNull String messageString) {
this.messageString = messageString;
}
@Override
@NotNull
public String toString() {
return this.messageString;
}
}

View File

@@ -0,0 +1,97 @@
package net.knarcraft.bookswithoutborders.config;
import net.knarcraft.knarlib.formatting.TranslatableMessage;
import org.jetbrains.annotations.NotNull;
/**
* An enum representing all translatable messages
*/
public enum Translatable implements TranslatableMessage {
/**
* The prefix to display in messages
*/
PREFIX,
/**
* The success message displayed when the copy command succeeds
*/
SUCCESS_COPY,
/**
* The error to display when the console attempts to run a player-only command
*/
ERROR_PLAYER_ONLY,
/**
* The error displayed when running the copy command without holding a written book
*/
ERROR_NOT_HOLDING_BOOK_COPY,
/**
* The error displayed when running the copy command while holding one book in each hand
*/
ERROR_ONLY_ONE_BOOK_COPY,
/**
* The error displayed when running the copy command without specifying the amount of copies
*/
ERROR_COPY_COUNT_NOT_SPECIFIED,
/**
* The error displayed when supplying the copy command with a negative number
*/
ERROR_COPY_NEGATIVE_AMOUNT,
/**
* The error displayed when supplying the copy command with a non-number
*/
ERROR_COPY_INVALID_AMOUNT,
/**
* The error displayed when trying to copy a tattered book or a copy of a copy
*/
ERROR_BOOK_COPIED_TOO_FAR,
/**
* The error displayed when trying to receive a book with a full inventory
*/
ERROR_INVENTORY_FULL,
/**
* The header displayed before printing all commands
*/
NEUTRAL_COMMANDS_HEADER,
/**
* The format used when displaying a book's economy price
*/
NEUTRAL_COMMANDS_BOOK_PRICE_ECO,
/**
* The format used when displaying a book's item price
*/
NEUTRAL_COMMANDS_BOOK_PRICE_ITEM,
/**
* The format used when displaying one command entry
*/
NEUTRAL_COMMANDS_COMMAND,
/**
* The string used when displaying that a command requires no permission
*/
NEUTRAL_COMMANDS_COMMAND_NO_PERMISSION_REQUIRED,
/**
* The format used when displaying a command's required permission
*/
NEUTRAL_COMMANDS_COMMAND_PERMISSION,
;
@Override
public @NotNull TranslatableMessage[] getAllMessages() {
return Translatable.values();
}
}

View File

@@ -7,7 +7,6 @@ import net.knarcraft.bookswithoutborders.encryption.SubstitutionCipher;
import net.knarcraft.bookswithoutborders.state.EncryptionStyle; import net.knarcraft.bookswithoutborders.state.EncryptionStyle;
import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.ChatColor;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.command.ConsoleCommandSender;
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;
@@ -19,6 +18,7 @@ import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getBookFolder; import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getBookFolder;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getSlash; import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getSlash;
@@ -238,19 +238,18 @@ public final class EncryptionHelper {
} }
if (deleteEncryptedFile) { if (deleteEncryptedFile) {
ConsoleCommandSender consoleSender = BooksWithoutBorders.getConsoleSender(); Logger logger = BooksWithoutBorders.getInstance().getLogger();
try { try {
if (!file.delete()) { if (!file.delete()) {
BooksWithoutBorders.sendErrorMessage(consoleSender, "Book encryption data failed to delete upon decryption!"); logger.log(Level.SEVERE, "Book encryption data failed to delete upon decryption!\n" +
BooksWithoutBorders.sendErrorMessage(consoleSender, "File location:" + file.getPath()); "File location:" + file.getPath());
} }
} catch (Exception e) { } catch (Exception e) {
BooksWithoutBorders.sendErrorMessage(consoleSender, "Book encryption data failed to delete upon decryption!"); logger.log(Level.SEVERE, "Book encryption data failed to delete upon decryption!\nFile location:" + file.getPath());
BooksWithoutBorders.sendErrorMessage(consoleSender, "File location:" + file.getPath());
} }
} }
ItemStack newBook = new ItemStack(Material.WRITTEN_BOOK);//Book(book.getAuthor(), book.getTitle(), pages, 1, 387); ItemStack newBook = new ItemStack(Material.WRITTEN_BOOK);
newBook.setItemMeta(bookMetadata); newBook.setItemMeta(bookMetadata);
newBook.setAmount(InventoryHelper.getHeldBook(player, true).getAmount()); newBook.setAmount(InventoryHelper.getHeldBook(player, true).getAmount());
return newBook; return newBook;
@@ -273,7 +272,7 @@ public final class EncryptionHelper {
if (!dirTest.exists()) { if (!dirTest.exists()) {
try { try {
if (!dirTest.mkdir()) { if (!dirTest.mkdir()) {
BooksWithoutBorders.sendErrorMessage(BooksWithoutBorders.getConsoleSender(), "Unable to create encryption group folder!"); BooksWithoutBorders.getInstance().getLogger().log(Level.SEVERE, "Unable to create encryption group folder!");
return null; return null;
} }
} catch (Exception exception) { } catch (Exception exception) {

View File

@@ -1,4 +1,6 @@
Options: Options:
# The language to use. Only "en" is built-in, but custom languages can be added
Language: "en"
# Whether to use YAML for saved books instead of just storing them as text # Whether to use YAML for saved books instead of just storing them as text
Save_Books_in_Yaml_Format: true Save_Books_in_Yaml_Format: true
# The maximum number of duplicates of a saved book allowed # The maximum number of duplicates of a saved book allowed

View File

@@ -0,0 +1,25 @@
en:
PREFIX: "[BwB]"
SUCCESS_COPY: "Book copied!"
ERROR_PLAYER_ONLY: "This command can only be used by a player!"
ERROR_NOT_HOLDING_BOOK_COPY: "You must be holding a written book to copy it!"
ERROR_ONLY_ONE_BOOK_COPY: "You cannot copy two books at once!"
ERROR_COPY_COUNT_NOT_SPECIFIED: "You must specify the number of copies to be made!"
ERROR_COPY_NEGATIVE_AMOUNT: "Number of copies must be larger than 0!"
ERROR_COPY_INVALID_AMOUNT: |
Book not copied!
Number specified was invalid!
ERROR_BOOK_COPIED_TOO_FAR: "You cannot copy this book any further. You must have the original or a direct copy."
ERROR_INVENTORY_FULL: "You need an available slot in your inventory."
NEUTRAL_COMMANDS_HEADER: |
&e[] denote optional parameters
<> denote required parameters
{} denote required permission
In some cases, commands with required parameters can be called with no parameters
{bookPrice}&eCommands:
{commands}
NEUTRAL_COMMANDS_BOOK_PRICE_ECO: "\n&c[{price} is required to create a book]"
NEUTRAL_COMMANDS_BOOK_PRICE_ITEM: "&c[{quantity} {type} (s) are required to create a book]\n"
NEUTRAL_COMMANDS_COMMAND: "\n \n&e{usage}: &a{description}"
NEUTRAL_COMMANDS_COMMAND_NO_PERMISSION_REQUIRED: "None"
NEUTRAL_COMMANDS_COMMAND_PERMISSION: " &7{{permission}}"