25 Commits
1.2.2 ... 1.3.0

Author SHA1 Message Date
d2403d247b Adds bypassauthoronlysave permission to README 2022-08-11 01:16:19 +02:00
c6c018ee88 Adds permission to bypass author only save 2022-08-11 01:14:50 +02:00
8b47aeb8f2 Prevents some unintended usage of UUID for filenames 2022-08-11 00:53:29 +02:00
396c0a9d41 Updates version to 1.3.0, as a lot of changes have been performed 2022-08-10 18:39:21 +02:00
c995a4fc0f Stores author name as UUID if storing own books
This change basically always stores the player's own books under their UUID, thus preventing being denied access to their own books if they change their username. The UUID is converted back to the username on the fly, so it shouldn't be noticeable for the players.
2022-08-10 18:36:06 +02:00
40512dd771 Adds an option to only allow saving own books 2022-08-10 14:52:05 +02:00
5d340af6f2 Changes player folders to UUIDs to prevent problems if users change their names 2022-08-10 13:44:11 +02:00
70ad6390db Updates README a bit 2022-08-10 12:25:41 +02:00
2a5c5b310d Adds missing permission to README 2022-08-10 12:18:58 +02:00
94375eee4b Adds a command for changing book generation, and updates README #5 2022-08-10 02:49:44 +02:00
1eb9c370bc Fixes some messages not being sent using the correct methods 2022-08-10 01:04:54 +02:00
1100f181be Treats null book generation as the original 2022-08-10 00:55:30 +02:00
7467645bcd Fixes an incorrect boolean 2022-08-10 00:35:34 +02:00
11108011a5 Removes duplicate payment 2022-08-10 00:33:56 +02:00
ce249a93b3 Increases book generation for all loaded books #4 2022-08-10 00:26:23 +02:00
7c229fb459 Fixes root node of the new config options 2022-08-10 00:19:46 +02:00
f243bf32e7 Adds an option which mimics the vanilla book copying behavior 2022-08-10 00:16:33 +02:00
a7c284ade2 Adds an option for only allowing un-signing by the author 2022-08-09 17:21:55 +02:00
0d4d87373c Fixes redundancy in book filename generation, and saves Generation 2022-08-09 16:56:38 +02:00
542cd03bdc Fixes an inconsistency where "," is always used as separator for unsigned books 2022-08-09 16:07:37 +02:00
5e85dfd3e4 Displays no permission required as None instead of null 2022-08-05 14:15:30 +02:00
af0b0fd12e Updates version to 1.2.3 2022-08-05 12:03:46 +02:00
a963733734 Builds against 1.19.1 2022-08-05 12:01:08 +02:00
7dd201d0d0 Merge branch 'master' of https://git.knarcraft.net/EpicKnarvik97/Books-Without-Borders 2022-07-19 18:35:52 +02:00
a95d737414 Fixes an exception when trying to save a book without holding a book 2022-07-19 18:08:10 +02:00
23 changed files with 760 additions and 255 deletions

155
README.md
View File

@ -1,10 +1,9 @@
# Books Without Borders # Books Without Borders
This is an attempt at a rewrite of the Books Without Borders plugin. This rewrite uses the source code given This is a rewrite of the Books Without Borders plugin. This rewrite originally used the source code given
at [the original bukkit page](https://dev.bukkit.org/projects/books-without-borders). I'm not planning any new features at [the original bukkit page](https://dev.bukkit.org/projects/books-without-borders). While the old plugin still worked
at this time. The only goal is to make it 1.17.1 compliant, but I'll make the code more maintainable along the way. the last time I checked, this plugin does not use any depreciated function calls, making sure it works for the
While the original version still works, it's using a lot of depreciated function calls which will most likely break in foreseeable future.
the future.
## Books without Borders! ## Books without Borders!
@ -13,90 +12,97 @@ Books without Borders has got your back!
### Features ### Features
* Export written books and book and quills to .txt or .yml files - Export written books and book and quills to .txt or .yml files
* Import books from files as written books or unsigned books - Import books from files as written books or unsigned books
* Text files can be any length, and the import process fits the content to the correct page length - Text files can be any length, and the import process fits the content to the correct page length
* Books can be saved privately, or to a directory visible server wide - Books can be saved privately, or to a directory visible server wide
* Encrypt books to prevent other players from reading them - Encrypt books to prevent other players from reading them
* Give, encrypt, or decrypt held books with signs - Give, encrypt, or decrypt held books with signs
* Give players books via command blocks - Give players books via command blocks
* Unsign or copy held books with a simple command - Unsign or copy held books with a simple command
* Give first time players a single book or a set of books when they join - Give first time players a single book or a set of books when they join
* Configurable option to require certain items or pay via Vault compatible economy to create books via command - Configurable option to require certain items or pay via Vault compatible economy to create books via command
* Add lore to any item with a simple command - Add lore to any item with a simple command
* Supports adding and saving color to title, lore, and book contents - Supports adding and saving color to title, lore, and book contents
* Color and formatting codes can be manually turned into formatting using /formatbook - Color and formatting codes can be manually turned into formatting using /formatbook
* Formatting and color codes can be turned into formatting once any book is signed. This is enabled through a config - Formatting and color codes can be turned into formatting once any book is signed. This is enabled through a config
value value
- Change generation of books. Create tattered books for your RPG server!
- Optionally, make it impossible to duplicate the original version of a book
#### Group encryption #### Group encryption
* Group encryption allows every player with the bookswithoutborders.decrypt.\<group> permission to decrypt the encrypted - Group encryption allows every player with the bookswithoutborders.decrypt.\<group> permission to decrypt the encrypted
book without using a password. book without using a password.
### Commands: ### Commands:
An in-game description of available commands is available through the /bwb command. An in-game description of available commands is available through the /bwb command.
* /bookswithoutborders - Displays information about commands (and permissions if the user has bookswithoutborders.admin) - /bookswithoutborders - Displays information about commands (and permissions if the user has bookswithoutborders.admin)
* /copybook <# of copies> - Copies the book the player is holding - /copybook <# of copies> - Copies the book the player is holding
* /decryptbook <key> - Decrypts the book the player is holding. "key" is required and MUST be IDENTICAL to the key used - /decryptbook <key> - Decrypts the book the player is holding. "key" is required and MUST be IDENTICAL to the key used
to encrypt the held book to encrypt the held book
* /deletebook <file name or number> - Deletes the specified file in the player's directory - /deletebook <file name or number> - Deletes the specified file in the player's directory
* /deletepublicbook <file name or number> - Same as deletebook, but deletes files in the public directory - /deletepublicbook <file name or number> - Same as deletebook, but deletes files in the public directory
* encryptbook <key> \[encryption style] - Encrypts the book the player is holding. "key" is required and can be any - encryptbook <key> \[encryption style] - Encrypts the book the player is holding. "key" is required and can be any
phrase or number excluding spaces. "style" is not required. Possible values are "DNA" or "" phrase or number excluding spaces. "style" is not required. Possible values are "DNA" or ""
* /formatbook - Formats the held written book (converts color and formatting codes to the corresponding formatted text) - /formatbook - Formats the held written book (converts color and formatting codes to the corresponding formatted text)
* /givebook <file name or number> <playername> \[# of copies (num)] \[signed (true/false)] - Gives the selected player a - /givebook <file name or number> <playername> \[# of copies (num)] \[signed (true/false)] - Gives the selected player a
book from your personal directory book from your personal directory
* /givepublicbook <file name or number> <playername> \[# of copies (num)] \[signed (true/false)] - Same as givebook, but - /givepublicbook <file name or number> <playername> \[# of copies (num)] \[signed (true/false)] - Same as givebook, but
uses books from the public directory uses books from the public directory
* /loadbook <file name or number> \[# of copies] \[signed (true/false)] - Creates a book from the specified file and - /loadbook <file name or number> \[# of copies] \[signed (true/false)] - Creates a book from the specified file and
gives it to the player. If no file is specified, a list of available files is returned. If true is specified, the book gives it to the player. If no file is specified, a list of available files is returned. If true is specified, the book
will be signed, if false it will be unsigned will be signed, if false it will be unsigned
* /loadpublicbook <file name or number> \[# of copies] \[signed (true/false)] - Same as loadbook, but views files in the - /loadpublicbook <file name or number> \[# of copies] \[signed (true/false)] - Same as loadbook, but views files in the
public directory public directory
* /reload - Reloads BwB's configuration file - /reload - Reloads BwB's configuration file
* /savebook \[overwrite (true/false)] - Saves the book the player is holding to a text file in a private directory. If - /savebook \[overwrite (true/false)] - Saves the book the player is holding to a text file in a private directory. If
true is specified, a book of the same name by the same author will be overwritten by the new book true is specified, a book of the same name by the same author will be overwritten by the new book
* /savepublicbook \[overwrite (true/false)] - Same as savebook, but saves files in the public directory - /savepublicbook \[overwrite (true/false)] - Same as savebook, but saves files in the public directory
* /setbookauthor <author> - Sets the author of the book the player is holding - /setbookauthor <author> - Sets the author of the book the player is holding
* /setbookprice <item/eco> <quantity> - Sets the per-book price to create a book via commands. If "Item", the item in - /setbookgeneration <generation> - Sets the generation (ORIGINAL, COPY_OF_ORIGINAL, COPY_OF_COPY, TATTERED)
- /setbookprice <item/eco> <quantity> - Sets the per-book price to create a book via commands. If "Item", the item in
the player's hand in the amount of <quantity> will be the price. If "Eco", a Vault based economy will be used for the player's hand in the amount of <quantity> will be the price. If "Eco", a Vault based economy will be used for
price. If neither <Item/Eco> nor <quantity> are specified, the current price to create books will be removed. price. If neither <Item/Eco> nor <quantity> are specified, the current price to create books will be removed.
* /setlore <new lore> - Sets the lore of the item the player is holding. Insert the lore_line_separator character to - /setlore <new lore> - Sets the lore of the item the player is holding. Insert the lore_line_separator character to
force a new line ("~" by default) force a new line ("~" by default)
* /settitle <title> - Sets the title of the book/item the player is holding - /settitle <title> - Sets the title of the book/item the player is holding
* /unsignbook - Un-signs the book the player is holding - /unsignbook - Un-signs the book the player is holding
### Permissions: ### Permissions:
* bookswithoutborders.* - Grants all permissions - bookswithoutborders.* - Grants all permissions
* bookswithoutborders.admin - Grants all permissions - bookswithoutborders.admin - Grants all permissions
* bookswithoutborders.use - Allows player to use commands to save/load/delete in their personal directory - bookswithoutborders.use - Allows player to use commands to save/load/delete in their personal directory
* bookswithoutborders.alterbooks - Allows player to change books' data such as lore/title/author/formatting and - bookswithoutborders.alterbooks - Allows player to change books' data such as lore/title/author/formatting and
unsigning books unsigning books
* bookswithoutborders.format - Allows a player to format a book - bookswithoutborders.reload - Allows player to reload this plugin
* bookswithoutborders.save - Allows a player to save books to their personal directory - bookswithoutborders.format - Allows a player to format a book
* bookswithoutborders.load - Allows player to load books from their personal directory - bookswithoutborders.save - Allows a player to save books to their personal directory
* bookswithoutborders.delete - Allows player to delete books from their personal directory - bookswithoutborders.load - Allows player to load books from their personal directory
* bookswithoutborders.unsign - Allows player to un-sign books - bookswithoutborders.delete - Allows player to delete books from their personal directory
* bookswithoutborders.copy - Allows player to copy books - bookswithoutborders.unsign - Allows player to un-sign books
* bookswithoutborders.loadpublic - Allows player to load from the public directory - bookswithoutborders.copy - Allows player to copy books
* bookswithoutborders.savepublic - Allows player to save to the public directory - bookswithoutborders.loadpublic - Allows player to load from the public directory
* bookswithoutborders.encrypt - Allows player to encrypt books - bookswithoutborders.savepublic - Allows player to save to the public directory
* bookswithoutborders.groupencrypt - Allows player to use group-based encryption - bookswithoutborders.encrypt - Allows player to encrypt books
* bookswithoutborders.decrypt - Allows player to decrypt books - bookswithoutborders.groupencrypt - Allows player to use group-based encryption
* bookswithoutborders.decrypt.agroup - Allows player to decrypt books group-encrypted for group "agroup" - bookswithoutborders.decrypt - Allows player to decrypt books
* bookswithoutborders.signs - Allows player to create signs that give/encrypt/decrypt books - bookswithoutborders.decrypt.agroup - Allows player to decrypt books group-encrypted for group "agroup"
* bookswithoutborders.give - Allows player to give another player one of their privately saved books - bookswithoutborders.signs - Allows player to create signs that give/encrypt/decrypt books
* bookswithoutborders.givepublic - Allows a player to give another player a book from the public directory - bookswithoutborders.give - Allows player to give another player one of their privately saved books
* bookswithoutborders.settitle - Allows player to set the title of the currently held book - bookswithoutborders.givepublic - Allows a player to give another player a book from the public directory
* bookswithoutborders.setauthor - Allows player to set the author of the currently held book - bookswithoutborders.settitle - Allows player to set the title of the currently held book
* bookswithoutborders.setlore - Allows player to set the lore of the currently held item - bookswithoutborders.setauthor - Allows player to set the author of the currently held book
* bookswithoutborders.bypassauthoronlycopy - Allows player to ignore Author_Only_Copy config setting - bookswithoutborders.setlore - Allows player to set the lore of the currently held item
* bookswithoutborders.bypassbookprice - Allows player to ignore Price_to_create_book config setting - bookswithoutborders.bypassauthoronlycopy - Allows player to ignore Author_Only_Copy config setting
* bookswithoutborders-setbookprice - Allows player to set the cost of creating a book - bookswithoutborders.bypassauthoronlyunsign - Allows player to ignore Author_Only_Unsign config setting
- bookswithoutborders.bypassauthoronlysave - Allows player to ignore Author_Only_Save config setting
- bookswithoutborders.bypassbookprice - Allows player to ignore Price_to_create_book config setting
- bookswithoutborders.setbookprice - Allows player to set the cost of creating a book
- bookswithoutborders.setgeneration - Allows player to change the generation of a book (Original, Copy, Copy of Copy)
### Signs ### Signs
@ -116,4 +122,23 @@ fourth line can be empty or contain "dna" for dna-based encryption.
#### Decrypt sign #### Decrypt sign
The **_decrypt_** sign must have **\[Decrypt]** on its second line. The third line must contain the decryption key The **_decrypt_** sign must have **\[Decrypt]** on its second line. The third line must contain the decryption key
### Configuration options:
- Save_Books_in_Yaml_Format - Whether to use YAML for saved books instead of just storing them as text
- Max_Number_of_Duplicates - The maximum number of duplicates of a saved book allowed
- Author_Separator - The separator used to separate the book title and the book author
- Lore_line_separator - The separator used to denote a new line in the book/item lore
- Books_for_new_players - A list of books given to new players the first time they join the server
- Message_for_new_players - An optional message displayed to new players the first time they join the server
- Price_to_create_book.Item_type - The item type used as currency for copying books. Use "Economy" to use money instead
of items
- Price_to_create_book.Required_quantity - The quantity of currency required to pay for each book produced
- Admin_Auto_Decrypt - Whether any admin can decrypt any book regardless of the group it was encrypted for
- Author_Only_Copy - Whether to only allow the author of a book to create copies
- Author_Only_Unsign - Whether to only allow the author of a book to unsign it
- Author_Only_Save - Whether to only allow saving a player's own books with /savebook
- Format_Book_After_Signing - Whether to automatically format every book when it's signed
- Change_Generation_On_Copy - 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 or tattered book cannot be copied further.

View File

@ -6,7 +6,7 @@
<groupId>net.knarcraft</groupId> <groupId>net.knarcraft</groupId>
<artifactId>BooksWithoutBorders</artifactId> <artifactId>BooksWithoutBorders</artifactId>
<version>1.2.2</version> <version>1.3.0</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<licenses> <licenses>
@ -75,7 +75,7 @@
<dependency> <dependency>
<groupId>org.spigotmc</groupId> <groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId> <artifactId>spigot-api</artifactId>
<version>1.18.1-R0.1-SNAPSHOT</version> <version>1.19.1-R0.1-SNAPSHOT</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>

View File

@ -17,6 +17,7 @@ import net.knarcraft.bookswithoutborders.command.CommandSave;
import net.knarcraft.bookswithoutborders.command.CommandSavePublic; import net.knarcraft.bookswithoutborders.command.CommandSavePublic;
import net.knarcraft.bookswithoutborders.command.CommandSetAuthor; import net.knarcraft.bookswithoutborders.command.CommandSetAuthor;
import net.knarcraft.bookswithoutborders.command.CommandSetBookPrice; import net.knarcraft.bookswithoutborders.command.CommandSetBookPrice;
import net.knarcraft.bookswithoutborders.command.CommandSetGeneration;
import net.knarcraft.bookswithoutborders.command.CommandSetLore; 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;
@ -31,6 +32,7 @@ import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender; 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.inventory.ItemFactory; 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;
@ -41,6 +43,7 @@ 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.UUID;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getBookFolder; import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getBookFolder;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getErrorColor; import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getErrorColor;
@ -53,7 +56,7 @@ import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig
public class BooksWithoutBorders extends JavaPlugin { public class BooksWithoutBorders extends JavaPlugin {
private static ItemFactory itemFactory; private static ItemFactory itemFactory;
private static Map<String, List<String>> playerBooksList; private static Map<UUID, List<String>> playerBooksList;
private static List<String> publicBooksList; private static List<String> publicBooksList;
private static BooksWithoutBorders booksWithoutBorders; private static BooksWithoutBorders booksWithoutBorders;
private static ConsoleCommandSender consoleSender; private static ConsoleCommandSender consoleSender;
@ -77,13 +80,15 @@ public class BooksWithoutBorders extends JavaPlugin {
public static List<String> getAvailableBooks(CommandSender sender, boolean getPublic) { public static List<String> getAvailableBooks(CommandSender sender, boolean getPublic) {
if (getPublic) { if (getPublic) {
return new ArrayList<>(publicBooksList); return new ArrayList<>(publicBooksList);
} else { } else if (sender instanceof Player player) {
String senderName = sender.getName(); UUID playerUUID = player.getUniqueId();
if (!playerBooksList.containsKey(senderName)) { if (!playerBooksList.containsKey(playerUUID)) {
List<String> newFiles = FileHelper.listFiles(sender, false); List<String> newFiles = FileHelper.listFiles(sender, false);
playerBooksList.put(senderName, newFiles); playerBooksList.put(playerUUID, newFiles);
} }
return playerBooksList.get(senderName); return playerBooksList.get(playerUUID);
} else {
return new ArrayList<>();
} }
} }
@ -97,8 +102,8 @@ public class BooksWithoutBorders extends JavaPlugin {
List<String> newFiles = FileHelper.listFiles(sender, updatePublic); List<String> newFiles = FileHelper.listFiles(sender, updatePublic);
if (updatePublic) { if (updatePublic) {
publicBooksList = newFiles; publicBooksList = newFiles;
} else { } else if (sender instanceof Player player) {
playerBooksList.put(sender.getName(), newFiles); playerBooksList.put(player.getUniqueId(), newFiles);
} }
} }
@ -167,6 +172,7 @@ public class BooksWithoutBorders extends JavaPlugin {
registerCommand("booksWithoutBorders", new CommandBooksWithoutBorders()); registerCommand("booksWithoutBorders", new CommandBooksWithoutBorders());
registerCommand("reload", new CommandReload()); registerCommand("reload", new CommandReload());
registerCommand("formatBook", new CommandFormat()); registerCommand("formatBook", new CommandFormat());
registerCommand("setBookGeneration", new CommandSetGeneration());
} }
/** /**

View File

@ -84,6 +84,7 @@ public class CommandBooksWithoutBorders implements TabExecutor {
showCommandInfo("saveBook", sender); showCommandInfo("saveBook", sender);
showCommandInfo("savePublicBook", sender); showCommandInfo("savePublicBook", sender);
showCommandInfo("setAuthor", sender); showCommandInfo("setAuthor", sender);
showCommandInfo("setBookGeneration", sender);
showCommandInfo("setBookPrice", sender); showCommandInfo("setBookPrice", sender);
showCommandInfo("setLore", sender); showCommandInfo("setLore", sender);
showCommandInfo("setTitle", sender); showCommandInfo("setTitle", sender);
@ -104,6 +105,9 @@ public class CommandBooksWithoutBorders implements TabExecutor {
String commandInfo = "\n" + getCommandColor() + pluginCommand.getUsage().replace("<command>", String commandInfo = "\n" + getCommandColor() + pluginCommand.getUsage().replace("<command>",
pluginCommand.getName()) + ": " + getSuccessColor() + pluginCommand.getDescription(); pluginCommand.getName()) + ": " + getSuccessColor() + pluginCommand.getDescription();
if (sender.hasPermission("bookswithoutborders.admin")) { if (sender.hasPermission("bookswithoutborders.admin")) {
if (permission == null) {
permission = "None";
}
commandInfo += getCommandColor() + " {" + permission + "}"; commandInfo += getCommandColor() + " {" + permission + "}";
} }
sender.sendMessage(commandInfo); sender.sendMessage(commandInfo);

View File

@ -2,10 +2,11 @@ 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.utility.BookHelper;
import net.knarcraft.bookswithoutborders.utility.EconomyHelper; import net.knarcraft.bookswithoutborders.utility.EconomyHelper;
import net.knarcraft.bookswithoutborders.utility.InputCleaningHelper;
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,54 +41,105 @@ public class CommandCopy implements TabExecutor {
return false; return false;
} }
ItemStack heldBook = InventoryHelper.getHeldBook(player, true);
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");
if (!isAuthor(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;
} }
return performCopy(copies, player, heldBook);
} catch (NumberFormatException ignored) { } 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;
} }
/** /**
* Checks whether the given player is the author of a given book * Performs the actual copying
* *
* @param player <p>The player to check</p> * @param copies <p>The number of copies to be made</p>
* @param book <p>The book to check</p> * @param player <p>The player requesting the copies</p>
* @return <p>True if the player is the book's author</p> * @param heldBook <p>The book to be copied</p>
* @return <p>True if the copying was successful</p>
*/ */
private boolean isAuthor(Player player, BookMeta book) { private boolean performCopy(int copies, Player player, ItemStack heldBook) {
String author = book.getAuthor(); //Make sure the player owns the book if authorOnlyCopy is enabled
String playerName = InputCleaningHelper.cleanString(player.getName()); if (BooksWithoutBordersConfig.getAuthorOnlyCopy() &&
if (author != null && playerName.equalsIgnoreCase(InputCleaningHelper.cleanString(author))) { !player.hasPermission("bookswithoutborders.bypassAuthorOnlyCopy")) {
return true; if (BookHelper.isNotAuthor(player, (BookMeta) Objects.requireNonNull(heldBook.getItemMeta()))) {
return false;
}
} }
BooksWithoutBorders.sendErrorMessage(player, "You must be the author of this book to use this command!"); BookMeta bookMeta = (BookMeta) heldBook.getItemMeta();
return false; if (BooksWithoutBordersConfig.changeGenerationOnCopy() && bookMeta != null) {
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 <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 ||
bookMeta.getGeneration() == BookMeta.Generation.TATTERED) {
BooksWithoutBorders.sendErrorMessage(player, "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) {
BooksWithoutBorders.sendErrorMessage(player, "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, @NotNull String[] args) { public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias,
@NotNull String[] args) {
int argumentCount = args.length; int argumentCount = args.length;
if (argumentCount == 1) { if (argumentCount == 1) {
return TabCompletionHelper.filterMatchingStartsWith(TabCompletionHelper.getNumbers(1, 20), args[0]); return TabCompletionHelper.filterMatchingStartsWith(TabCompletionHelper.getNumbers(1, 20), args[0]);

View File

@ -2,6 +2,7 @@ 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.utility.BookHelper;
import net.knarcraft.bookswithoutborders.utility.EncryptionHelper; import net.knarcraft.bookswithoutborders.utility.EncryptionHelper;
import net.knarcraft.bookswithoutborders.utility.InventoryHelper; import net.knarcraft.bookswithoutborders.utility.InventoryHelper;
import org.bukkit.command.Command; import org.bukkit.command.Command;
@ -47,12 +48,6 @@ public class CommandDecrypt implements TabExecutor {
//Warning: admin decrypt only allows decrypting files created by the same player. Not sure if intended //Warning: admin decrypt only allows decrypting files created by the same player. Not sure if intended
if (args.length == 0 && BooksWithoutBordersConfig.getAdminDecrypt() && player.hasPermission("bookswithoutborders.admin")) { if (args.length == 0 && BooksWithoutBordersConfig.getAdminDecrypt() && player.hasPermission("bookswithoutborders.admin")) {
String path = getBookFolder() + "Encrypted" + getSlash(); String path = getBookFolder() + "Encrypted" + getSlash();
String fileName;
if (bookMetadata.hasTitle()) {
fileName = bookMetadata.getTitle() + BooksWithoutBordersConfig.getTitleAuthorSeparator() + bookMetadata.getAuthor();
} else {
fileName = "Untitled," + player.getName();
}
File encryptedDirectory = new File(path); File encryptedDirectory = new File(path);
String[] encryptedFiles = encryptedDirectory.list(); String[] encryptedFiles = encryptedDirectory.list();
@ -64,7 +59,7 @@ public class CommandDecrypt implements TabExecutor {
//Get the "encryption key" from the filename //Get the "encryption key" from the filename
String key = ""; String key = "";
for (String encryptedFile : encryptedFiles) { for (String encryptedFile : encryptedFiles) {
if (encryptedFile.contains(fileName)) { if (encryptedFile.contains(BookHelper.getBookFile(bookMetadata, player, true))) {
key = encryptedFile.substring(encryptedFile.indexOf("[") + 1, encryptedFile.indexOf("]")); key = encryptedFile.substring(encryptedFile.indexOf("[") + 1, encryptedFile.indexOf("]"));
break; break;
} }

View File

@ -1,8 +1,9 @@
package net.knarcraft.bookswithoutborders.command; package net.knarcraft.bookswithoutborders.command;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders; import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.state.BookDirectory;
import net.knarcraft.bookswithoutborders.utility.BookHelper;
import net.knarcraft.bookswithoutborders.utility.FileHelper; import net.knarcraft.bookswithoutborders.utility.FileHelper;
import net.knarcraft.bookswithoutborders.utility.InputCleaningHelper;
import net.knarcraft.bookswithoutborders.utility.TabCompletionHelper; import net.knarcraft.bookswithoutborders.utility.TabCompletionHelper;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
@ -14,9 +15,6 @@ import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getBookFolder;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getSlash;
/** /**
* Command executor for the delete command * Command executor for the delete command
*/ */
@ -82,13 +80,9 @@ public class CommandDelete implements TabExecutor {
} }
//Get the file to be deleted //Get the file to be deleted
File file; String bookDirectory = BookHelper.getBookDirectoryPathString(
if (isPublic) { isPublic ? BookDirectory.PUBLIC : BookDirectory.PLAYER, sender);
file = FileHelper.getBookFile(getBookFolder() + fileName); File file = FileHelper.getBookFile(bookDirectory + fileName);
} else {
file = FileHelper.getBookFile(getBookFolder() +
InputCleaningHelper.cleanString(sender.getName()) + getSlash() + fileName);
}
//Send message if no such file could be found //Send message if no such file could be found
if (file == null) { if (file == null) {

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

@ -2,7 +2,9 @@ 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.state.BookDirectory;
import net.knarcraft.bookswithoutborders.state.ItemSlot; import net.knarcraft.bookswithoutborders.state.ItemSlot;
import net.knarcraft.bookswithoutborders.utility.BookHelper;
import net.knarcraft.bookswithoutborders.utility.BookToFromTextHelper; import net.knarcraft.bookswithoutborders.utility.BookToFromTextHelper;
import net.knarcraft.bookswithoutborders.utility.FileHelper; import net.knarcraft.bookswithoutborders.utility.FileHelper;
import net.knarcraft.bookswithoutborders.utility.InventoryHelper; import net.knarcraft.bookswithoutborders.utility.InventoryHelper;
@ -19,12 +21,9 @@ import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getBookFolder;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getCommandColor; import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getCommandColor;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getErrorColor; import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getErrorColor;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getSlash; import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getTitleAuthorSeparator;
import static net.knarcraft.bookswithoutborders.utility.InputCleaningHelper.cleanString;
import static net.knarcraft.bookswithoutborders.utility.InputCleaningHelper.fixName;
/** /**
* Command executor for the save command * Command executor for the save command
@ -51,7 +50,7 @@ public class CommandSave implements TabExecutor {
} }
ItemSlot holdingSlot = InventoryHelper.getHeldSlotBook(player, false, false, false, false); ItemSlot holdingSlot = InventoryHelper.getHeldSlotBook(player, false, false, false, false);
if (holdingSlot != ItemSlot.NONE) { if (holdingSlot != null && holdingSlot != ItemSlot.NONE) {
ItemStack holdingItem = InventoryHelper.getHeldItem(player, holdingSlot == ItemSlot.MAIN_HAND); ItemStack holdingItem = InventoryHelper.getHeldItem(player, holdingSlot == ItemSlot.MAIN_HAND);
boolean duplicate = args.length == 1 && Boolean.parseBoolean(args[0]); boolean duplicate = args.length == 1 && Boolean.parseBoolean(args[0]);
saveBook(player, holdingItem, duplicate, savePublic); saveBook(player, holdingItem, duplicate, savePublic);
@ -77,32 +76,30 @@ public class CommandSave implements TabExecutor {
return; return;
} }
String savePath; //Only allow saving of own books if enabled
if (saveToPublicFolder) { if (BooksWithoutBordersConfig.getAuthorOnlySave() && !saveToPublicFolder &&
savePath = getBookFolder(); (!player.hasPermission("bookswithoutborders.bypassAuthorOnlySave") &&
} else { BookHelper.isNotAuthor(player, book))) {
savePath = getBookFolder() + cleanString(player.getName()) + getSlash(); return;
} }
String savePath = BookHelper.getBookDirectoryPathString(
saveToPublicFolder ? BookDirectory.PUBLIC : BookDirectory.PLAYER, player);
//Generate book filename //Generate book filename
String fileName; String fileName = BookHelper.getBookFile(book, player, saveToPublicFolder);
if (!book.hasTitle()) {
fileName = "Untitled," + player.getName();
} else {
fileName = book.getTitle() + BooksWithoutBordersConfig.getTitleAuthorSeparator() + book.getAuthor();
}
fileName = cleanString(fileName);
fileName = fixName(fileName, false);
//Make sure the used folders exist //Make sure the used folders exist
File file = new File(savePath); File file = new File(savePath);
if (!file.exists() && !file.mkdir()) { if (!file.exists() && !file.mkdir()) {
BooksWithoutBorders.sendErrorMessage(player, "Saving Failed! If this continues to happen, consult server admin!"); BooksWithoutBorders.sendErrorMessage(player, "Saving Failed! If this continues to happen, consult" +
" a server admin!");
return; return;
} }
File[] foundFiles = file.listFiles(); File[] foundFiles = file.listFiles();
if (foundFiles == null) { if (foundFiles == null) {
BooksWithoutBorders.sendErrorMessage(player, "Saving Failed! If this continues to happen, consult server admin!"); BooksWithoutBorders.sendErrorMessage(player, "Saving Failed! If this continues to happen, consult" +
" a server admin!");
return; return;
} }
@ -113,21 +110,24 @@ public class CommandSave implements TabExecutor {
if (foundDuplicates > 0) { if (foundDuplicates > 0) {
//TODO: Decide if this makes sense or needs to be changed //TODO: Decide if this makes sense or needs to be changed
//Skip duplicate book //Skip duplicate book
if (!fileName.contains("Untitled") && !overwrite) { if (!fileName.contains("Untitled" + getTitleAuthorSeparator()) && !overwrite) {
BooksWithoutBorders.sendErrorMessage(player, "Book is already saved!"); BooksWithoutBorders.sendErrorMessage(player, "Book is already saved!");
BooksWithoutBorders.sendErrorMessage(player, "Use " + getCommandColor() + "/savebook true " + getErrorColor() + "to overwrite!"); BooksWithoutBorders.sendErrorMessage(player, "Use " + getCommandColor() + "/savebook true " +
getErrorColor() + "to overwrite!");
return; return;
} }
//Skip if duplicate limit is reached //Skip if duplicate limit is reached
if (foundDuplicates > BooksWithoutBordersConfig.getBookDuplicateLimit()) { if (foundDuplicates > BooksWithoutBordersConfig.getBookDuplicateLimit()) {
BooksWithoutBorders.sendErrorMessage(player, "Maximum amount of " + fileName + " duplicates reached!"); BooksWithoutBorders.sendErrorMessage(player, "Maximum amount of " + fileName +
BooksWithoutBorders.sendErrorMessage(player, "Use " + getCommandColor() + "/savebook true " + getErrorColor() + "to overwrite!"); " duplicates reached!");
BooksWithoutBorders.sendErrorMessage(player, "Use " + getCommandColor() + "/savebook true " +
getErrorColor() + "to overwrite!");
return; return;
} }
//Alter duplicated filename //Alter duplicated filename
if (fileName.contains("Untitled") && !overwrite) { if (fileName.contains("Untitled" + getTitleAuthorSeparator()) && !overwrite) {
fileName = "(" + foundDuplicates + ")" + fileName; fileName = "(" + foundDuplicates + ")" + fileName;
} }
} }
@ -149,7 +149,8 @@ public class CommandSave implements TabExecutor {
} }
@Override @Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) { public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias,
@NotNull String[] args) {
return new ArrayList<>(); return new ArrayList<>();
} }

View File

@ -0,0 +1,74 @@
package net.knarcraft.bookswithoutborders.command;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.utility.InventoryHelper;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.BookMeta;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
/**
* Command executor for the set generation command
*/
public class CommandSetGeneration implements TabExecutor {
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
@NotNull String[] args) {
if (!(sender instanceof Player player)) {
BooksWithoutBorders.sendErrorMessage(sender, "This command can only be used by a player!");
return false;
}
if (InventoryHelper.notHoldingOneWrittenBookCheck(player, "You must be holding a written book to" +
" change its generation!", "You cannot change the generation of two books at once!")) {
return false;
}
if (args.length < 1) {
BooksWithoutBorders.sendErrorMessage(player, "You must specify the new generation for your book!");
return false;
}
BookMeta.Generation generation;
try {
generation = BookMeta.Generation.valueOf(args[0]);
} catch (IllegalArgumentException exception) {
BooksWithoutBorders.sendErrorMessage(player, "Invalid book generation specified!");
return false;
}
ItemStack heldBook = InventoryHelper.getHeldBook(player, true);
BookMeta bookMeta = (BookMeta) heldBook.getItemMeta();
if (bookMeta != null) {
bookMeta.setGeneration(generation);
heldBook.setItemMeta(bookMeta);
return true;
} else {
BooksWithoutBorders.sendErrorMessage(player, "Unable to get book metadata!");
return false;
}
}
@Nullable
@Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
@NotNull String[] args) {
if (args.length == 1) {
List<String> generations = new ArrayList<>();
for (BookMeta.Generation generation : BookMeta.Generation.values()) {
generations.add(generation.name());
}
return generations;
}
return new ArrayList<>();
}
}

View File

@ -1,7 +1,9 @@
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.BooksWithoutBordersConfig;
import net.knarcraft.bookswithoutborders.state.ItemSlot; import net.knarcraft.bookswithoutborders.state.ItemSlot;
import net.knarcraft.bookswithoutborders.utility.BookHelper;
import net.knarcraft.bookswithoutborders.utility.InventoryHelper; import net.knarcraft.bookswithoutborders.utility.InventoryHelper;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.command.Command; import org.bukkit.command.Command;
@ -14,6 +16,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.Objects;
/** /**
* Command executor for the unsign command * Command executor for the unsign command
@ -48,6 +51,13 @@ public class CommandUnSign implements TabExecutor {
//Get the old book //Get the old book
BookMeta oldBook = InventoryHelper.getHeldBookMetadata(player, mainHand); BookMeta oldBook = InventoryHelper.getHeldBookMetadata(player, mainHand);
//Only allow the owner to un-sign the book
if (BooksWithoutBordersConfig.getAuthorOnlyUnsign() && !player.hasPermission("bookswithoutborders.bypassAuthorOnlyUnsign")) {
if (BookHelper.isNotAuthor(player, Objects.requireNonNull(oldBook))) {
return;
}
}
//UnSign the book //UnSign the book
ItemStack newBook = new ItemStack(Material.WRITABLE_BOOK); ItemStack newBook = new ItemStack(Material.WRITABLE_BOOK);
newBook.setItemMeta(oldBook); newBook.setItemMeta(oldBook);

View File

@ -34,9 +34,12 @@ public class BooksWithoutBordersConfig {
private static Material bookPriceType = null; private static Material bookPriceType = null;
private static double bookPriceQuantity; private static double bookPriceQuantity;
private static boolean authorOnlyCopy; private static boolean authorOnlyCopy;
private static boolean authorOnlyUnsign;
private static boolean authorOnlySave;
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
@ -106,6 +109,24 @@ public class BooksWithoutBordersConfig {
return authorOnlyCopy; return authorOnlyCopy;
} }
/**
* Gets whether only the author of a book should be able to unsign it
*
* @return <p>Whether only the book author can unsign it</p>
*/
public static boolean getAuthorOnlyUnsign() {
return authorOnlyUnsign;
}
/**
* Gets whether a player can only save their own books with /savebook
*
* @return <p>Whether a player can only save their own books</p>
*/
public static boolean getAuthorOnlySave() {
return authorOnlySave;
}
/** /**
* Gets whether to use YML, not TXT, for saving books * Gets whether to use YML, not TXT, for saving books
* *
@ -184,6 +205,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
* *
@ -257,10 +287,14 @@ public class BooksWithoutBordersConfig {
config.set(ConfigOption.PRICE_QUANTITY.getConfigNode(), bookPriceQuantity); config.set(ConfigOption.PRICE_QUANTITY.getConfigNode(), bookPriceQuantity);
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_SAVE.getConfigNode(), authorOnlySave);
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")) {
sendSuccessMessage(consoleSender, "[BooksWithoutBorders] Found old config setting \"Require_book_and_quill_to_create_book\""); sendSuccessMessage(consoleSender, "[BooksWithoutBorders] Found old config setting " +
"\"Require_book_and_quill_to_create_book\"");
sendSuccessMessage(consoleSender, "Updating 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")) {
@ -284,32 +318,27 @@ 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()); authorOnlySave = getBoolean(config, ConfigOption.AUTHOR_ONLY_SAVE);
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());
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()) {
@ -318,8 +347,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);
@ -338,4 +366,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

@ -55,6 +55,21 @@ public enum ConfigOption {
*/ */
AUTHOR_ONLY_COPY("Options.Author_Only_Copy", false), AUTHOR_ONLY_COPY("Options.Author_Only_Copy", false),
/**
* Whether only the book author should be able to unsign a book
*/
AUTHOR_ONLY_UNSIGN("Options.Author_Only_Unsign", false),
/**
* Whether a player can only save their own books with /savebook
*/
AUTHOR_ONLY_SAVE("Options.Author_Only_Save", false),
/**
* Whether to turn Original into Copy when copying books
*/
CHANGE_GENERATION_ON_COPY("Options.Change_Generation_On_Copy", false),
/** /**
* Whether to automatically format every signed book * Whether to automatically format every signed book
*/ */

View File

@ -2,6 +2,8 @@ package net.knarcraft.bookswithoutborders.listener;
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.state.BookDirectory;
import net.knarcraft.bookswithoutborders.utility.BookHelper;
import net.knarcraft.bookswithoutborders.utility.BookLoader; import net.knarcraft.bookswithoutborders.utility.BookLoader;
import net.knarcraft.bookswithoutborders.utility.InputCleaningHelper; import net.knarcraft.bookswithoutborders.utility.InputCleaningHelper;
import net.knarcraft.bookswithoutborders.utility.InventoryHelper; import net.knarcraft.bookswithoutborders.utility.InventoryHelper;
@ -17,16 +19,13 @@ import org.bukkit.inventory.meta.BookMeta;
import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.ItemMeta;
import java.io.File; import java.io.File;
import java.util.logging.Level;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getBookFolder;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getSlash;
/** /**
* A listener for listening to player-related events such as joining or holding a book * A listener for listening to player-related events such as joining or holding a book
*/ */
public class PlayerEventListener implements Listener { public class PlayerEventListener implements Listener {
private final String slash = getSlash();
private final BooksWithoutBorders booksWithoutBorders = BooksWithoutBorders.getInstance(); private final BooksWithoutBorders booksWithoutBorders = BooksWithoutBorders.getInstance();
@EventHandler @EventHandler
@ -58,6 +57,16 @@ public class PlayerEventListener implements Listener {
public void onPlayerJoin(PlayerJoinEvent event) { public void onPlayerJoin(PlayerJoinEvent event) {
Player player = event.getPlayer(); Player player = event.getPlayer();
//If a book directory exists with this player's name, move it to this player's UUID
String bookFolder = BooksWithoutBordersConfig.getBookFolder();
File file = new File(bookFolder + InputCleaningHelper.cleanString(player.getName()));
if (file.exists()) {
if (!file.renameTo(new File(bookFolder + player.getUniqueId()))) {
BooksWithoutBorders.getInstance().getLogger().log(Level.WARNING, "Unable to migrate player book " +
"directory for player " + player.getName());
}
}
//Handle new players //Handle new players
if (!player.hasPlayedBefore()) { if (!player.hasPlayedBefore()) {
boolean sendMessage = true; boolean sendMessage = true;
@ -151,13 +160,14 @@ public class PlayerEventListener implements Listener {
fileName = oldBook.getTitle() + BooksWithoutBordersConfig.getTitleAuthorSeparator() + oldBook.getAuthor(); fileName = oldBook.getTitle() + BooksWithoutBordersConfig.getTitleAuthorSeparator() + oldBook.getAuthor();
} }
String cleanPlayerName = InputCleaningHelper.cleanString(player.getName()); String playerFolderPath = BookHelper.getBookDirectoryPathString(BookDirectory.PLAYER, player);
String publicFolderPath = BookHelper.getBookDirectoryPathString(BookDirectory.PUBLIC, player);
String[] possiblePaths = new String[]{ String[] possiblePaths = new String[]{
getBookFolder() + fileName + ".yml", publicFolderPath + fileName + ".yml",
getBookFolder() + fileName + ".txt", publicFolderPath + fileName + ".txt",
getBookFolder() + cleanPlayerName + slash + fileName + ".yml", playerFolderPath + fileName + ".yml",
getBookFolder() + cleanPlayerName + slash + fileName + ".txt" playerFolderPath + fileName + ".txt"
}; };
for (String path : possiblePaths) { for (String path : possiblePaths) {

View File

@ -89,6 +89,9 @@ public class SignEventListener implements Listener {
return; return;
} }
ItemStack heldItem = playerInventory.getItem(hand); ItemStack heldItem = playerInventory.getItem(hand);
if (heldItem == null) {
return;
}
Material heldItemType = heldItem.getType(); Material heldItemType = heldItem.getType();
if (event.getAction() == Action.RIGHT_CLICK_BLOCK && (Tag.SIGNS.isTagged(clickedBlockType) || if (event.getAction() == Action.RIGHT_CLICK_BLOCK && (Tag.SIGNS.isTagged(clickedBlockType) ||
@ -182,7 +185,8 @@ public class SignEventListener implements Listener {
*/ */
private void generateGiveSign(SignChangeEvent event, String[] lines, Player player) { private void generateGiveSign(SignChangeEvent event, String[] lines, Player player) {
if (lines[2].length() > 13 || lines[3].length() > 13) { if (lines[2].length() > 13 || lines[3].length() > 13) {
player.sendMessage(ChatColor.RED + "[Give] signs' 3rd and 4th lines must be 13 characters or less!"); BooksWithoutBorders.sendErrorMessage(player,
"[Give] signs' 3rd and 4th lines must be 13 characters or less!");
markGiveSignValidity(event, false); markGiveSignValidity(event, false);
return; return;
} }
@ -262,7 +266,7 @@ public class SignEventListener implements Listener {
player.getInventory().setItem(hand, newBook); player.getInventory().setItem(hand, newBook);
player.closeInventory(); player.closeInventory();
player.sendMessage(ChatColor.GREEN + "Book auto-decrypted!"); BooksWithoutBorders.sendSuccessMessage(player, "Book auto-decrypted!");
} }
/** /**
@ -313,9 +317,9 @@ public class SignEventListener implements Listener {
if (newBook != null) { if (newBook != null) {
player.getInventory().addItem(newBook); player.getInventory().addItem(newBook);
player.sendMessage(ChatColor.GREEN + "Received book!"); BooksWithoutBorders.sendSuccessMessage(player, "Received book!");
} else { } else {
player.sendMessage(ChatColor.RED + "Book failed to load!"); BooksWithoutBorders.sendErrorMessage(player, "Book failed to load!");
} }
} }

View File

@ -0,0 +1,170 @@
package net.knarcraft.bookswithoutborders.utility;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
import net.knarcraft.bookswithoutborders.state.BookDirectory;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.BookMeta;
import java.io.File;
import java.util.UUID;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getSlash;
import static net.knarcraft.bookswithoutborders.utility.InputCleaningHelper.cleanString;
import static net.knarcraft.bookswithoutborders.utility.InputCleaningHelper.fixName;
/**
* Helper class for getting abstract book information
*/
public final class BookHelper {
private BookHelper() {
}
/**
* Converts the author of a book from UUID if necessary
*
* @param author <p>The author string</p>
* @return <p>The author string, converted if it was a UUID</p>
*/
public static String authorFromUUID(String author) {
try {
UUID authorID = UUID.fromString(author);
Player player = Bukkit.getPlayer(authorID);
if (player != null) {
author = player.getName();
}
} catch (IllegalArgumentException ignored) {
}
return author;
}
/**
* Gets the file path of the selected book directory
*
* @param bookDirectory <p>The book directory to get (ENCRYPTED is not supported here)</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>
*/
public static File getBookDirectoryPath(BookDirectory bookDirectory, CommandSender sender) {
String bookFolderString = getBookDirectoryPathString(bookDirectory, sender);
if (bookFolderString == null) {
return null;
}
return new File(bookFolderString);
}
/**
* Gets the string path of the selected book directory
*
* @param bookDirectory <p>The book directory to get (ENCRYPTED is not supported here)</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>
*/
public static String getBookDirectoryPathString(BookDirectory bookDirectory, CommandSender sender) {
String folder = null;
String bookFolder = BooksWithoutBordersConfig.getBookFolder();
if (bookDirectory == BookDirectory.PUBLIC) {
folder = bookFolder;
} else if (bookDirectory == BookDirectory.PLAYER && sender instanceof Player player) {
folder = bookFolder + player.getUniqueId() + getSlash();
}
return folder;
}
/**
* 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.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) {
if (currentGeneration == null) {
return BookMeta.Generation.COPY_OF_ORIGINAL;
}
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
*
* @param book <p>The book to get the file of</p>
* @param player <p>The player trying to do something with the book</p>
* @return <p>The book file</p>
*/
public static String getBookFile(BookMeta book, Player player, boolean isPublic) {
String titleAuthorSeparator = BooksWithoutBordersConfig.getTitleAuthorSeparator();
String bookName;
if (book.hasTitle()) {
bookName = book.getTitle();
} else {
bookName = "Untitled";
}
String authorName;
if ((!book.hasAuthor() || isAuthor(player.getName(), book.getAuthor())) && !isPublic) {
//Store as unique id to account for name changes
authorName = player.getUniqueId().toString();
} else if (!book.hasAuthor()) {
authorName = player.getName();
} else {
authorName = book.getAuthor();
}
return fixName(cleanString(bookName + titleAuthorSeparator + authorName), false);
}
/**
* Checks whether the given player is the author of a given book
*
* @param player <p>The player to check</p>
* @param book <p>The book to check</p>
* @return <p>True if the player is not the book's author</p>
*/
public static boolean isNotAuthor(Player player, BookMeta book) {
if (isAuthor(player.getName(), book.getAuthor())) {
return false;
} else {
BooksWithoutBorders.sendErrorMessage(player,
"You must be the author of this book to use this command!");
return true;
}
}
/**
* Gets whether the given player name is equal to the given book author
*
* @param playerName <p>The player name to check</p>
* @param author <p>The author to check</p>
* @return <p>True if the player is the author</p>
*/
private static boolean isAuthor(String playerName, String author) {
playerName = InputCleaningHelper.cleanString(playerName);
return author != null && playerName.equalsIgnoreCase(InputCleaningHelper.cleanString(author));
}
}

View File

@ -64,7 +64,8 @@ public final class BookLoader {
} }
//Make sure the player can pay for the book //Make sure the player can pay for the book
if (BooksWithoutBordersConfig.booksHavePrice() && !sender.hasPermission("bookswithoutborders.bypassBookPrice") && if (BooksWithoutBordersConfig.booksHavePrice() &&
!sender.hasPermission("bookswithoutborders.bypassBookPrice") &&
(bookDirectory == BookDirectory.PUBLIC || bookDirectory == BookDirectory.PLAYER) && (bookDirectory == BookDirectory.PUBLIC || bookDirectory == BookDirectory.PLAYER) &&
EconomyHelper.cannotPayForBookPrinting((Player) sender, numCopies)) { EconomyHelper.cannotPayForBookPrinting((Player) sender, numCopies)) {
return null; return null;
@ -98,6 +99,8 @@ public final class BookLoader {
//Set the metadata and amount to the new book //Set the metadata and amount to the new book
book.setItemMeta(bookMetadata); book.setItemMeta(bookMetadata);
//Increase book generation if enabled
BookHelper.increaseGeneration(book);
book.setAmount(numCopies); book.setAmount(numCopies);
return book; return book;
@ -113,16 +116,15 @@ public final class BookLoader {
* @return <p>A file or null if it does not exist</p> * @return <p>A file or null if it does not exist</p>
*/ */
private static File getFullPath(CommandSender sender, String fileName, BookDirectory bookDirectory, String directory) { private static File getFullPath(CommandSender sender, String fileName, BookDirectory bookDirectory, String directory) {
File file = null; File file;
String slash = BooksWithoutBordersConfig.getSlash(); String slash = BooksWithoutBordersConfig.getSlash();
String bookFolder = BooksWithoutBordersConfig.getBookFolder(); String bookFolder = BooksWithoutBordersConfig.getBookFolder();
if (bookDirectory == BookDirectory.PUBLIC) { if (bookDirectory == BookDirectory.ENCRYPTED) {
file = FileHelper.getBookFile(bookFolder + fileName);
} else if (bookDirectory == BookDirectory.PLAYER) {
file = FileHelper.getBookFile(bookFolder + InputCleaningHelper.cleanString(sender.getName()) + slash + fileName);
} else if (bookDirectory == BookDirectory.ENCRYPTED) {
file = FileHelper.getBookFile(bookFolder + "Encrypted" + slash + directory + slash + fileName); file = FileHelper.getBookFile(bookFolder + "Encrypted" + slash + directory + slash + fileName);
} else {
file = FileHelper.getBookFile(BookHelper.getBookDirectoryPathString(bookDirectory, sender) + fileName);
} }
if (file == null || !file.isFile()) { if (file == null || !file.isFile()) {
BooksWithoutBorders.sendErrorMessage(sender, "Incorrect file name!"); BooksWithoutBorders.sendErrorMessage(sender, "Incorrect file name!");
return null; return null;

View File

@ -14,6 +14,7 @@ import java.io.PrintWriter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import static net.knarcraft.bookswithoutborders.utility.BookHelper.authorFromUUID;
import static net.knarcraft.bookswithoutborders.utility.InputCleaningHelper.fixName; import static net.knarcraft.bookswithoutborders.utility.InputCleaningHelper.fixName;
/** /**
@ -41,6 +42,11 @@ public final class BookToFromTextHelper {
if (bookMetadata.hasAuthor()) { if (bookMetadata.hasAuthor()) {
bookYml.set("Author", bookMetadata.getAuthor()); bookYml.set("Author", bookMetadata.getAuthor());
} }
BookMeta.Generation generation = bookMetadata.getGeneration();
if (generation == null) {
generation = BookMeta.Generation.ORIGINAL;
}
bookYml.set("Generation", generation.name());
if (bookMetadata.hasPages()) { if (bookMetadata.hasPages()) {
bookYml.set("Pages", bookMetadata.getPages()); bookYml.set("Pages", bookMetadata.getPages());
} }
@ -81,8 +87,14 @@ public final class BookToFromTextHelper {
PrintWriter printWriter = new PrintWriter(fileWriter); PrintWriter printWriter = new PrintWriter(fileWriter);
List<String> pages = bookMetadata.getPages(); List<String> pages = bookMetadata.getPages();
BookMeta.Generation generation = bookMetadata.getGeneration();
if (generation == null) {
generation = BookMeta.Generation.ORIGINAL;
}
String generationString = ":" + generation.name();
//Save each page of the book as a text line //Save each page of the book as a text line
printWriter.println("[Book]"); printWriter.println("[Book]" + generationString);
for (String page : pages) { for (String page : pages) {
printWriter.println(page); printWriter.println(page);
} }
@ -100,8 +112,9 @@ public final class BookToFromTextHelper {
try { try {
FileConfiguration bookYml = YamlConfiguration.loadConfiguration(file); FileConfiguration bookYml = YamlConfiguration.loadConfiguration(file);
bookMetadata.setGeneration(BookMeta.Generation.valueOf(bookYml.getString("Generation", "ORIGINAL")));
bookMetadata.setTitle(bookYml.getString("Title", "Untitled")); bookMetadata.setTitle(bookYml.getString("Title", "Untitled"));
bookMetadata.setAuthor(bookYml.getString("Author", "Unknown")); bookMetadata.setAuthor(authorFromUUID(bookYml.getString("Author", "Unknown")));
bookMetadata.setPages(bookYml.getStringList("Pages")); bookMetadata.setPages(bookYml.getStringList("Pages"));
bookMetadata.setLore(bookYml.getStringList("Lore")); bookMetadata.setLore(bookYml.getStringList("Lore"));
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
@ -123,13 +136,16 @@ public final class BookToFromTextHelper {
String title; String title;
String titleAuthorSeparator = BooksWithoutBordersConfig.getTitleAuthorSeparator(); String titleAuthorSeparator = BooksWithoutBordersConfig.getTitleAuthorSeparator();
//Remove .txt extension
fileName = fileName.substring(0, fileName.length() - 4);
//Get title and author from the file name //Get title and author from the file name
if (fileName.contains(titleAuthorSeparator)) { if (fileName.contains(titleAuthorSeparator)) {
author = fileName.substring(fileName.indexOf(titleAuthorSeparator) + 1, fileName.length() - 4); String[] titleAuthor = fileName.split(titleAuthorSeparator);
title = fileName.substring(0, fileName.indexOf(titleAuthorSeparator)); title = titleAuthor[0];
author = titleAuthor[1];
} else { } else {
author = "Unknown"; author = "Unknown";
title = fileName.substring(0, fileName.length() - 4); title = fileName;
} }
//Replace underscores with spaces //Replace underscores with spaces
@ -144,11 +160,17 @@ public final class BookToFromTextHelper {
return null; return null;
} }
//Parse the generation from the book data
if (rawPages != null && rawPages.size() > 0 && rawPages.get(0).startsWith("Generation:")) {
bookMetadata.setGeneration(BookMeta.Generation.valueOf(rawPages.get(0).split(":")[1]));
rawPages.remove(0);
}
//Remove any empty pages //Remove any empty pages
List<String> pages = new ArrayList<>(InputCleaningHelper.cleanList(rawPages)); List<String> pages = new ArrayList<>(InputCleaningHelper.cleanList(rawPages));
//Update the metadata of the book with its new values //Update the metadata of the book with its new values
bookMetadata.setAuthor(author); bookMetadata.setAuthor(authorFromUUID(author));
bookMetadata.setTitle(title); bookMetadata.setTitle(title);
bookMetadata.setPages(pages); bookMetadata.setPages(pages);
@ -172,8 +194,11 @@ public final class BookToFromTextHelper {
bufferedReader.close(); bufferedReader.close();
return null; return null;
} }
if (firstLine.equalsIgnoreCase("[Book]")) { if (firstLine.toLowerCase().startsWith("[book]")) {
//Read every line directly as a page, as this is a saved book //Read every line directly as a page, as this is a saved book
if (firstLine.contains(":")) {
rawPages.add("Generation:" + firstLine.split(":")[1]);
}
String readLine; String readLine;
do { do {
readLine = bufferedReader.readLine(); readLine = bufferedReader.readLine();

View File

@ -198,11 +198,8 @@ public final class EncryptionHelper {
return null; return null;
} }
String fileName = (!bookMetadata.hasTitle()) ? "Untitled," + player.getName() : bookMetadata.getTitle() + String fileName = "[" + key + "]" + BookHelper.getBookFile(bookMetadata, player, true);
BooksWithoutBordersConfig.getTitleAuthorSeparator() + bookMetadata.getAuthor(); fileName = fixName(cleanString(fileName), false);
fileName = "[" + key + "]" + fileName;
fileName = cleanString(fileName);
fileName = fixName(fileName, false);
File file = new File(path + fileName + ".yml"); File file = new File(path + fileName + ".yml");
if (!file.isFile()) { if (!file.isFile()) {
@ -263,12 +260,8 @@ public final class EncryptionHelper {
return null; return null;
} }
} }
//Creates file //Generate file name
String fileName = (!bookMetadata.hasTitle()) ? "Untitled," + player.getName() : String fileName = BookHelper.getBookFile(bookMetadata, player, true);
bookMetadata.getTitle() + BooksWithoutBordersConfig.getTitleAuthorSeparator() + bookMetadata.getAuthor();
fileName = cleanString(fileName);
fileName = fixName(fileName, false);
List<String> newLore = new ArrayList<>(); List<String> newLore = new ArrayList<>();
newLore.add(ChatColor.GRAY + "[" + groupName + " encrypted]"); newLore.add(ChatColor.GRAY + "[" + groupName + " encrypted]");
@ -281,7 +274,8 @@ public final class EncryptionHelper {
bookMetadata.setLore(newLore); bookMetadata.setLore(newLore);
//Save file //Save file
File file = (BooksWithoutBordersConfig.getUseYml()) ? new File(path + fileName + ".yml") : new File(path + fileName + ".txt"); File file = (BooksWithoutBordersConfig.getUseYml()) ? new File(path + fileName + ".yml") :
new File(path + fileName + ".txt");
if (!file.isFile()) { if (!file.isFile()) {
try { try {
BookToFromTextHelper.bookToYml(path, fileName, bookMetadata); BookToFromTextHelper.bookToYml(path, fileName, bookMetadata);
@ -305,15 +299,13 @@ public final class EncryptionHelper {
*/ */
private static Boolean saveEncryptedBook(Player player, BookMeta bookMetaData, String key) { private static Boolean saveEncryptedBook(Player player, BookMeta bookMetaData, String key) {
String path = getBookFolder() + "Encrypted" + getSlash(); String path = getBookFolder() + "Encrypted" + getSlash();
String fileName = (!bookMetaData.hasTitle()) ? "Untitled," + player.getName() :
bookMetaData.getTitle() + BooksWithoutBordersConfig.getTitleAuthorSeparator() + bookMetaData.getAuthor();
fileName = "[" + key + "]" + fileName; String fileName = "[" + key + "]" + BookHelper.getBookFile(bookMetaData, player, true);
fileName = cleanString(fileName); fileName = fixName(cleanString(fileName), false);
fileName = fixName(fileName, false);
//cancels saving if file is already encrypted //cancels saving if file is already encrypted
File file = (BooksWithoutBordersConfig.getUseYml()) ? new File(path + fileName + ".yml") : new File(path + fileName + ".txt"); File file = (BooksWithoutBordersConfig.getUseYml()) ? new File(path + fileName + ".yml") :
new File(path + fileName + ".txt");
if (file.isFile()) { if (file.isFile()) {
return true; return true;
} }

View File

@ -1,6 +1,8 @@
package net.knarcraft.bookswithoutborders.utility; 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.state.BookDirectory;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
@ -15,8 +17,6 @@ import java.util.Objects;
import java.util.regex.Pattern; import java.util.regex.Pattern;
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.utility.InputCleaningHelper.cleanString;
/** /**
* Helper class for dealing with files * Helper class for dealing with files
@ -98,11 +98,9 @@ public final class FileHelper {
* @return <p>A list of available files</p> * @return <p>A list of available files</p>
*/ */
public static List<String> listFiles(CommandSender sender, Boolean listPublic) { public static List<String> listFiles(CommandSender sender, Boolean listPublic) {
File file; File file = BookHelper.getBookDirectoryPath(listPublic ? BookDirectory.PUBLIC : BookDirectory.PLAYER, sender);
if (listPublic) { if (file == null) {
file = new File(getBookFolder()); return new ArrayList<>();
} else {
file = new File(getBookFolder() + cleanString(sender.getName()) + getSlash());
} }
return FileHelper.listFiles(sender, file); return FileHelper.listFiles(sender, file);
} }
@ -152,8 +150,20 @@ public final class FileHelper {
} }
for (File foundFile : existingFiles) { for (File foundFile : existingFiles) {
if (foundFile.isFile()) { if (!foundFile.isFile()) {
fileList.add(foundFile.getName()); continue;
}
String fileName = foundFile.getName();
String separator = BooksWithoutBordersConfig.getTitleAuthorSeparator();
if (fileName.contains(separator)) {
//Convert the UUID into a username if necessary
String[] data = fileName.split(separator);
String extension = data[1].substring(data[1].length() - 4);
String userName = data[1].substring(0, data[1].length() - 4);
data[1] = BookHelper.authorFromUUID(userName) + extension;
fileList.add(String.join(separator, data));
} else {
fileList.add(fileName);
} }
} }

View File

@ -96,7 +96,7 @@ public final class InventoryHelper {
if (state == BookHoldingState.SIGNED_BOTH_HANDS || if (state == BookHoldingState.SIGNED_BOTH_HANDS ||
state == BookHoldingState.UNSIGNED_BOTH_HANDS || state == BookHoldingState.UNSIGNED_BOTH_HANDS ||
state == BookHoldingState.NONE) { state == BookHoldingState.NONE) {
return null; return ItemSlot.NONE;
} }
if (handMatters && typeMatters) { if (handMatters && typeMatters) {
if (mainHand && mainHandItem.getType() == requiredMaterial) { if (mainHand && mainHandItem.getType() == requiredMaterial) {

View File

@ -5,7 +5,7 @@ Options:
Max_Number_of_Duplicates: 5 Max_Number_of_Duplicates: 5
# The separator used to separate the book title and the book author # The separator used to separate the book title and the book author
Title-Author_Separator: "," Title-Author_Separator: ","
# The separator used to denote a new line in the book lore # The separator used to denote a new line in the book/item lore
Lore_line_separator: "~" Lore_line_separator: "~"
# A list of books given to new players the first time they join the server # A list of books given to new players the first time they join the server
Books_for_new_players: [ ] Books_for_new_players: [ ]
@ -21,6 +21,13 @@ Options:
Admin_Auto_Decrypt: false Admin_Auto_Decrypt: false
# Whether to only allow the author of a book to create copies # Whether to only allow the author of a book to create copies
Author_Only_Copy: false Author_Only_Copy: false
# Whether to only allow the author of a book to unsign it
Author_Only_Unsign: false
# Whether to only allow saving a player's own books with /savebook
Author_Only_Save: 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

@ -53,6 +53,10 @@ commands:
description: Encrypts the book the player is holding. "key" is required and can be any phrase or number excluding spaces. "style" is not required. Possible values are "DNA" or "" description: Encrypts the book the player is holding. "key" is required and can be any phrase or number excluding spaces. "style" is not required. Possible values are "DNA" or ""
usage: /<command> <key> [encryption style] usage: /<command> <key> [encryption style]
permission: bookswithoutborders.encrypt permission: bookswithoutborders.encrypt
setbookgeneration:
description: Sets the generation of the held book
usage: /<command> <generation>
permission: bookswithoutborders.setgeneration
setbookprice: setbookprice:
description: Sets the per-book-price to create a book via commands. If "Item", the item in the player's hand in the amount of [quantity] will be the price. If "Eco", a Vault based economy will be used for price. If neither <Item/Eco> or <quantity> are specified, the current price to create books will be removed. description: Sets the per-book-price to create a book via commands. If "Item", the item in the player's hand in the amount of [quantity] will be the price. If "Eco", a Vault based economy will be used for price. If neither <Item/Eco> or <quantity> are specified, the current price to create books will be removed.
usage: /<command> <item/eco> <quantity> usage: /<command> <item/eco> <quantity>
@ -88,7 +92,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
@ -111,8 +115,12 @@ permissions:
bookswithoutborders.give: true bookswithoutborders.give: true
bookswithoutborders.givepublic: true bookswithoutborders.givepublic: true
bookswithoutborders.bypassauthoronlycopy: true bookswithoutborders.bypassauthoronlycopy: true
bookswithoutborders.bypassauthoronlyunsign: true
bookswithoutborders.bypassauthoronlysave: true
bookswithoutborders.bypassbookprice: true bookswithoutborders.bypassbookprice: true
bookswithoutborders.setbookprice: true bookswithoutborders.setbookprice: true
bookswithoutborders.reload: true
bookswithoutborders.setgeneration: 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:
@ -127,6 +135,7 @@ permissions:
bookswithoutborders.setauthor: true bookswithoutborders.setauthor: true
bookswithoutborders.setlore: true bookswithoutborders.setlore: true
bookswithoutborders.format: true bookswithoutborders.format: true
bookswithoutborders.setgeneration: true
bookswithoutborders.format: bookswithoutborders.format:
description: Allows a player to format a book description: Allows a player to format a book
bookswithoutborders.save: bookswithoutborders.save:
@ -163,7 +172,15 @@ permissions:
description: Allows player to set the lore of the currently held item description: Allows player to set the lore of the currently held item
bookswithoutborders.bypassauthoronlycopy: bookswithoutborders.bypassauthoronlycopy:
description: Allows player to ignore Author_Only_Copy config setting description: Allows player to ignore Author_Only_Copy config setting
bookswithoutborders.bypassauthoronlyunsign:
description: Allows player to ignore Author_Only_Unsign config setting
bookswithoutborders.bypassauthoronlysave:
description: Allows player to ignore Author_Only_Save config setting
bookswithoutborders.bypassbookprice: bookswithoutborders.bypassbookprice:
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
bookswithoutborders.setgeneration:
description: Allows player to change the generation of a book (Original, Copy, Copy of Copy)