35 Commits
1.0 ... 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
0ce85af61b 1.2.2 Filtered tab completion and update notice 2022-02-19 20:06:35 +01:00
ed54ae84b5 Updates README with command and permission info 2022-01-18 14:46:55 +01:00
27d5980fa8 Updates version to 1.2.1 2022-01-17 18:15:34 +01:00
544f6f69fe Fixes a NullPointerException caused by getting the size of a null list 2022-01-17 18:13:48 +01:00
055d7fca60 Improves some command and permission descriptions 2022-01-17 13:10:53 +01:00
8f7cfc591f Updates version to 1.2 2022-01-17 12:03:58 +01:00
2aa25295af Improves some formatting and gets rid of BooksWithoutBordersSettings.java 2022-01-17 11:55:52 +01:00
d423b1e109 Adds a new option to format books when signed
Also cleans up config stuff a bit and moves config-related tasks to its own class
Moves book loading code to its own class
Adds a default config file to make the config file have comments
Adds missing command info about the formatBook command
Adds information about required permissions to the command info
2022-01-17 11:38:43 +01:00
7acaa9fc81 Adds a new /formatbook command 2022-01-17 00:47:10 +01:00
94589faba0 Updates to Minecraft 1.18 and Java 17 2022-01-17 00:46:52 +01:00
48 changed files with 1899 additions and 796 deletions

136
README.md
View File

@ -1,10 +1,9 @@
# Books Without Borders
This is an attempt at a rewrite of the Books Without Borders plugin. This rewrite uses 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 this time. The only goal is to make it 1.17.1 compliant, but I'll make the code more maintainable along the way.
While the original version still works, it's using a lot of depreciated function calls which will most likely break in
the future.
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). While the old plugin still worked
the last time I checked, this plugin does not use any depreciated function calls, making sure it works for the
foreseeable future.
## Books without Borders!
@ -13,40 +12,133 @@ Books without Borders has got your back!
### Features
* Export written books and book and quills to .txt or .yml files
* 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
* Books can be saved privately, or to a directory visible server wide
* Encrypt books to prevent other players from reading them
* Give, encrypt, or decrypt held books with signs
* Give players books via command blocks
* Unsign or copy held books with a simple command
* 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
* Add lore to any item with a simple command
* Supports adding and saving color to title, lore, and book contents
- Export written books and book and quills to .txt or .yml files
- 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
- Books can be saved privately, or to a directory visible server wide
- Encrypt books to prevent other players from reading them
- Give, encrypt, or decrypt held books with signs
- Give players books via command blocks
- Unsign or copy held books with a simple command
- 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
- Add lore to any item with a simple command
- Supports adding and saving color to title, lore, and book contents
- 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
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 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.
### Commands:
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)
- /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
to encrypt the held book
- /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
- 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 ""
- /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
book from your personal directory
- /givepublicbook <file name or number> <playername> \[# of copies (num)] \[signed (true/false)] - Same as givebook, but
uses books from the public directory
- /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
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
public directory
- /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
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
- /setbookauthor <author> - Sets the author of the book the player is holding
- /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
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
force a new line ("~" by default)
- /settitle <title> - Sets the title of the book/item the player is holding
- /unsignbook - Un-signs the book the player is holding
### Permissions:
- bookswithoutborders.* - Grants all permissions
- bookswithoutborders.admin - Grants all permissions
- 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
unsigning books
- bookswithoutborders.reload - Allows player to reload this plugin
- bookswithoutborders.format - Allows a player to format a book
- bookswithoutborders.save - Allows a player to save books to their personal directory
- bookswithoutborders.load - Allows player to load books from their personal directory
- bookswithoutborders.delete - Allows player to delete books from their personal directory
- bookswithoutborders.unsign - Allows player to un-sign books
- bookswithoutborders.copy - Allows player to copy books
- bookswithoutborders.loadpublic - Allows player to load from the public directory
- bookswithoutborders.savepublic - Allows player to save to the public directory
- bookswithoutborders.encrypt - Allows player to encrypt books
- bookswithoutborders.groupencrypt - Allows player to use group-based encryption
- bookswithoutborders.decrypt - Allows player to decrypt books
- bookswithoutborders.decrypt.agroup - Allows player to decrypt books group-encrypted for group "agroup"
- bookswithoutborders.signs - Allows player to create signs that give/encrypt/decrypt books
- bookswithoutborders.give - Allows player to give another player one of their privately saved books
- bookswithoutborders.givepublic - Allows a player to give another player a book from the public directory
- bookswithoutborders.settitle - Allows player to set the title of the currently held book
- bookswithoutborders.setauthor - Allows player to set the author of the currently held book
- bookswithoutborders.setlore - Allows player to set the lore of the currently held item
- bookswithoutborders.bypassauthoronlycopy - Allows player to ignore Author_Only_Copy config setting
- 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
This plugin supports several custom signs with special functionality. Each plugin sign must have [BwB] on its first
This plugin supports several custom signs with special functionality. Each plugin sign must have \[BwB] on its first
line.
#### Give sign
The **_give_** sign must have **[Give]** on its second line. The third and fourth line contains the book to be loaded.
The **_give_** sign must have **\[Give]** on its second line. The third and fourth line contains the book to be loaded.
This can either be a numerical id pointing to a publicly saved book, or the full text identifier of the book (book name,
author).
#### Encrypt sign
The **_encrypt_** sign must have **[Encrypt]** on its second line. The third line must contain the encryption key The
The **_encrypt_** sign must have **\[Encrypt]** on its second line. The third line must contain the encryption key The
fourth line can be empty or contain "dna" for dna-based encryption.
#### 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.

16
pom.xml
View File

@ -6,7 +6,7 @@
<groupId>net.knarcraft</groupId>
<artifactId>BooksWithoutBorders</artifactId>
<version>1.0</version>
<version>1.3.0</version>
<packaging>jar</packaging>
<licenses>
@ -20,7 +20,7 @@
<description>A continuation of the original Books Without Borders</description>
<properties>
<java.version>16</java.version>
<java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
@ -31,8 +31,8 @@
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
<plugin>
@ -75,7 +75,7 @@
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.17.1-R0.1-SNAPSHOT</version>
<version>1.19.1-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
@ -84,5 +84,11 @@
<version>1.7</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
<version>23.0.0</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

@ -6,6 +6,7 @@ import net.knarcraft.bookswithoutborders.command.CommandDecrypt;
import net.knarcraft.bookswithoutborders.command.CommandDelete;
import net.knarcraft.bookswithoutborders.command.CommandDeletePublic;
import net.knarcraft.bookswithoutborders.command.CommandEncrypt;
import net.knarcraft.bookswithoutborders.command.CommandFormat;
import net.knarcraft.bookswithoutborders.command.CommandGive;
import net.knarcraft.bookswithoutborders.command.CommandGivePublic;
import net.knarcraft.bookswithoutborders.command.CommandGroupEncrypt;
@ -16,25 +17,24 @@ import net.knarcraft.bookswithoutborders.command.CommandSave;
import net.knarcraft.bookswithoutborders.command.CommandSavePublic;
import net.knarcraft.bookswithoutborders.command.CommandSetAuthor;
import net.knarcraft.bookswithoutborders.command.CommandSetBookPrice;
import net.knarcraft.bookswithoutborders.command.CommandSetGeneration;
import net.knarcraft.bookswithoutborders.command.CommandSetLore;
import net.knarcraft.bookswithoutborders.command.CommandSetTitle;
import net.knarcraft.bookswithoutborders.command.CommandUnSign;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
import net.knarcraft.bookswithoutborders.listener.BookEventListener;
import net.knarcraft.bookswithoutborders.listener.PlayerEventListener;
import net.knarcraft.bookswithoutborders.listener.SignEventListener;
import net.knarcraft.bookswithoutborders.state.BookDirectory;
import net.knarcraft.bookswithoutborders.utility.BookToFromTextHelper;
import net.knarcraft.bookswithoutborders.utility.EconomyHelper;
import net.knarcraft.bookswithoutborders.utility.FileHelper;
import org.bukkit.Material;
import net.knarcraft.bookswithoutborders.utility.UpdateChecker;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.command.PluginCommand;
import org.bukkit.configuration.Configuration;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemFactory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.BookMeta;
import org.bukkit.plugin.PluginDescriptionFile;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
@ -43,28 +43,20 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import static net.knarcraft.bookswithoutborders.BooksWithoutBordersSettings.getBookFolder;
import static net.knarcraft.bookswithoutborders.BooksWithoutBordersSettings.getErrorColor;
import static net.knarcraft.bookswithoutborders.BooksWithoutBordersSettings.getSlash;
import static net.knarcraft.bookswithoutborders.BooksWithoutBordersSettings.getSuccessColor;
import static net.knarcraft.bookswithoutborders.utility.InputCleaningHelper.cleanString;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getBookFolder;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getErrorColor;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getSlash;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getSuccessColor;
/**
* The main Books Without Borders class
*/
public class BooksWithoutBorders extends JavaPlugin {
private static int bookDuplicateLimit;
private static String titleAuthorSeparator;
private static String loreSeparator;
private static List<String> firstBooks;
private static String welcomeMessage;
private static Material bookPriceType = null;
private static double bookPriceQuantity;
private static boolean authorOnlyCopy;
private static boolean useYml;
private static boolean adminDecrypt;
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 BooksWithoutBorders booksWithoutBorders;
private static ConsoleCommandSender consoleSender;
@ -88,13 +80,15 @@ public class BooksWithoutBorders extends JavaPlugin {
public static List<String> getAvailableBooks(CommandSender sender, boolean getPublic) {
if (getPublic) {
return new ArrayList<>(publicBooksList);
} else {
String senderName = sender.getName();
if (!playerBooksList.containsKey(senderName)) {
} else if (sender instanceof Player player) {
UUID playerUUID = player.getUniqueId();
if (!playerBooksList.containsKey(playerUUID)) {
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<>();
}
}
@ -108,18 +102,25 @@ public class BooksWithoutBorders extends JavaPlugin {
List<String> newFiles = FileHelper.listFiles(sender, updatePublic);
if (updatePublic) {
publicBooksList = newFiles;
} else {
playerBooksList.put(sender.getName(), newFiles);
} else if (sender instanceof Player player) {
playerBooksList.put(player.getUniqueId(), newFiles);
}
}
@Override
public void onEnable() {
FileConfiguration config = this.getConfig();
config.options().copyDefaults(true);
this.saveDefaultConfig();
//Get plugin info
PluginDescriptionFile pluginDescriptionFile = this.getDescription();
String pluginVersion = pluginDescriptionFile.getVersion();
booksWithoutBorders = this;
consoleSender = this.getServer().getConsoleSender();
playerBooksList = new HashMap<>();
firstBooks = new ArrayList<>();
BooksWithoutBordersSettings.initialize(this);
BooksWithoutBordersConfig.initialize(this);
publicBooksList = FileHelper.listFiles(consoleSender, true);
PluginManager pluginManager = this.getServer().getPluginManager();
@ -127,125 +128,15 @@ public class BooksWithoutBorders extends JavaPlugin {
if (getSlash() != null && initialize()) {
pluginManager.registerEvents(new PlayerEventListener(), this);
pluginManager.registerEvents(new SignEventListener(), this);
pluginManager.registerEvents(new BookEventListener(), this);
} else {
this.getPluginLoader().disablePlugin(this);
}
registerCommands();
}
/**
* Gets whether only the author of a book should be able to copy it
*
* @return <p>Whether only the book author can copy it</p>
*/
public static boolean getAuthorOnlyCopy() {
return authorOnlyCopy;
}
/**
* Gets whether to use YML, not TXT, for saving books
*
* @return <p>Whether to use YML for saving books</p>
*/
public static boolean getUseYml() {
return useYml;
}
/**
* Gets whether admins should be able to decrypt books without a password, and decrypt all group encrypted books
*
* @return <p>Whether admins can bypass the encryption password</p>
*/
public static boolean getAdminDecrypt() {
return adminDecrypt;
}
/**
* Sets the quantity of items/currency necessary for copying books
*
* @param newQuantity <p>The new quantity necessary for payment</p>
*/
public static void setBookPriceQuantity(double newQuantity) {
bookPriceQuantity = newQuantity;
}
/**
* Gets the quantity of items/currency necessary for copying books
*
* @return <p>The quantity necessary for payment</p>
*/
public static double getBookPriceQuantity() {
return bookPriceQuantity;
}
/**
* Sets the item type used for book pricing
*
* <p>This item is the one a player has to pay for copying books. AIR is used to denote economy. null is used if
* payment is disabled. Otherwise, any item can be used.</p>
*
* @param newType <p>The new item type to use for book pricing</p>
*/
public static void setBookPriceType(Material newType) {
bookPriceType = newType;
}
/**
* Gets the item type used for book pricing
*
* <p>This item is the one a player has to pay for copying books. AIR is used to denote economy. null is used if
* payment is disabled. Otherwise, any item can be used.</p>
*
* @return <p>The item type used for book pricing</p>
*/
public static Material getBookPriceType() {
return bookPriceType;
}
/**
* Gets the welcome message to show to new players
*
* @return <p>The welcome message to show new players</p>
*/
public static String getWelcomeMessage() {
return welcomeMessage;
}
/**
* Gets the limit of duplicates for each book
*
* @return <p>The book duplicate limit</p>
*/
public static int getBookDuplicateLimit() {
return bookDuplicateLimit;
}
/**
* Gets the separator used to split book title from book author
*
* @return <p>The separator between title and author</p>
*/
public static String getTitleAuthorSeparator() {
return titleAuthorSeparator;
}
/**
* Gets the separator used to denote a newline in a lore string
*
* @return <p>The separator used to denote lore newline</p>
*/
public static String getLoreSeparator() {
return loreSeparator;
}
/**
* Gets a copy of the list of books to give new players
*
* @return <p>The books to give new players</p>
*/
public static List<String> getFirstBooks() {
return new ArrayList<>(firstBooks);
UpdateChecker.checkForUpdate(this, "https://api.spigotmc.org/legacy/update.php?resource=96069",
() -> pluginVersion, null);
}
/**
@ -280,6 +171,8 @@ public class BooksWithoutBorders extends JavaPlugin {
registerCommand("loadPublicBook", new CommandLoadPublic());
registerCommand("booksWithoutBorders", new CommandBooksWithoutBorders());
registerCommand("reload", new CommandReload());
registerCommand("formatBook", new CommandFormat());
registerCommand("setBookGeneration", new CommandSetGeneration());
}
/**
@ -299,6 +192,7 @@ public class BooksWithoutBorders extends JavaPlugin {
/**
* Initializes the plugin, loading and fixing the config file
*
* @return <p>True if successful</p>
*/
private boolean initialize() {
@ -313,16 +207,25 @@ public class BooksWithoutBorders extends JavaPlugin {
}
//Load config
if (!loadConfig()) {
if (!BooksWithoutBordersConfig.loadConfig()) {
return false;
}
//Save config with loaded values to fix invalid config values
saveConfigValues();
BooksWithoutBordersConfig.saveConfigValues();
return testFileSaving();
}
/**
* Gets the server's item factory
*
* @return <p>The server's item factory</p>
*/
public static ItemFactory getItemFactory() {
return itemFactory;
}
/**
* Makes sure necessary folders exist
*
@ -356,215 +259,6 @@ public class BooksWithoutBorders extends JavaPlugin {
return true;
}
/**
* Saves the config
*/
public void saveConfigValues() {
Configuration config = this.getConfig();
config.set("Options.Save_Books_in_Yaml_Format", useYml);
config.set("Options.Max_Number_of_Duplicates", bookDuplicateLimit);
config.set("Options.Title-Author_Separator", titleAuthorSeparator);
config.set("Options.Lore_line_separator", loreSeparator);
config.set("Options.Books_for_new_players", firstBooks);
config.set("Options.Message_for_new_players", welcomeMessage);
if (bookPriceType != null) {
if (bookPriceType != Material.AIR) {
config.set("Options.Price_to_create_book.Item_type", bookPriceType.toString());
} else {
config.set("Options.Price_to_create_book.Item_type", "Economy");
}
} else {
config.set("Options.Price_to_create_book.Item_type", "Item type name");
}
config.set("Options.Price_to_create_book.Required_quantity", bookPriceQuantity);
config.set("Options.Admin_Auto_Decrypt", adminDecrypt);
config.set("Options.Author_Only_Copy", authorOnlyCopy);
//Handles old book and quill settings
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, "Updating to \"Price_to_create_book\" settings");
if (config.getBoolean("Options.Require_book_and_quill_to_create_book")) {
bookPriceType = Material.WRITABLE_BOOK;
bookPriceQuantity = 1;
config.set("Options.Price_to_create_book.Item_type", bookPriceType.toString());
config.set("Options.Price_to_create_book.Required_quantity", bookPriceQuantity);
}
config.set("Options.Require_book_and_quill_to_create_book", null);
}
this.saveConfig();
}
/**
* Loads the config
* @return <p>True if the config was loaded successfully</p>
*/
public boolean loadConfig() {
this.reloadConfig();
Configuration config = this.getConfig();
try {
useYml = config.getBoolean("Options.Save_Books_in_Yaml_Format", true);
bookDuplicateLimit = config.getInt("Options.Max_Number_of_Duplicates", 5);
titleAuthorSeparator = config.getString("Options.Title-Author_Separator", ",");
loreSeparator = config.getString("Options.Lore_line_separator", "~");
adminDecrypt = config.getBoolean("Options.Admin_Auto_Decrypt", false);
authorOnlyCopy = config.getBoolean("Options.Author_Only_Copy", false);
//Set books to give new players
firstBooks = config.getStringList("Options.Books_for_new_players");
if (config.contains("Options.Book_for_new_players")) {
firstBooks.add(config.getString("Options.Book_for_new_players"));
}
if (firstBooks.isEmpty()) {
firstBooks.add(" ");
}
welcomeMessage = config.getString("Options.Message_for_new_players", " ");
//Convert string into material
String paymentMaterial = config.getString("Options.Price_to_create_book.Item_type", " ");
if (paymentMaterial.equalsIgnoreCase("Economy")) {
if (EconomyHelper.setupEconomy()) {
bookPriceType = Material.AIR;
} else {
sendErrorMessage(consoleSender, "BooksWithoutBorders failed to hook into Vault! Book price not set!");
bookPriceType = null;
}
} else if (!paymentMaterial.equalsIgnoreCase(" ")) {
Material material = Material.matchMaterial(paymentMaterial);
if (material != null) {
bookPriceType = material;
}
}
bookPriceQuantity = config.getDouble("Options.Price_to_create_book.Required_quantity", 0);
//Make sure titleAuthorSeparator is a valid value
titleAuthorSeparator = cleanString(titleAuthorSeparator);
if (titleAuthorSeparator.length() != 1) {
sendErrorMessage(consoleSender, "Title-Author_Separator is set to an invalid value!");
sendErrorMessage(consoleSender, "Reverting to default value of \",\"");
titleAuthorSeparator = ",";
config.set("Options.Title-Author_Separator", titleAuthorSeparator);
}
} catch (Exception e) {
sendErrorMessage(consoleSender, "Warning! Config.yml failed to load!");
sendErrorMessage(consoleSender, "Try Looking for settings that are missing values!");
return false;
}
return true;
}
/**
* Loads the given book
*
* @param sender <p>The command sender trying to load the book</p>
* @param fileName <p>The index or file name of the book to load</p>
* @param isSigned <p>Whether to load the book as signed, and not unsigned</p>
* @param directory <p>The directory to save the book in</p>
* @return <p>The loaded book</p>
*/
public ItemStack loadBook(CommandSender sender, String fileName, String isSigned, String directory) {
return loadBook(sender, fileName, isSigned, directory, 1);
}
/**
* Loads the given book
*
* @param sender <p>The command sender trying to load the book</p>
* @param fileName <p>The index or file name of the book to load</p>
* @param isSigned <p>Whether to load the book as signed, and not unsigned</p>
* @param directory <p>The directory to save the book in</p>
* @param numCopies <p>The number of copies to load</p>
* @return <p>The loaded book</p>
*/
public ItemStack loadBook(CommandSender sender, String fileName, String isSigned, String directory, int numCopies) {
BookDirectory bookDirectory = BookDirectory.getFromString(directory);
//Find the filename if a book index is given
try {
int bookIndex = Integer.parseInt(fileName);
List<String> availableFiles = getAvailableBooks(sender, bookDirectory == BookDirectory.PUBLIC);
if (bookIndex <= availableFiles.size()) {
fileName = availableFiles.get(Integer.parseInt(fileName) - 1);
}
} catch (NumberFormatException ignored) {
}
//Get the full path of the book to load
File file = getFullPath(sender, fileName, bookDirectory, directory);
if (file == null) {
return null;
}
//Make sure the player can pay for the book
if (booksHavePrice() && !sender.hasPermission("bookswithoutborders.bypassBookPrice") &&
(bookDirectory == BookDirectory.PUBLIC || bookDirectory == BookDirectory.PLAYER) &&
EconomyHelper.cannotPayForBookPrinting((Player) sender, numCopies)) {
return null;
}
//Generate a new empty book
ItemStack book;
BookMeta bookMetadata = (BookMeta) itemFactory.getItemMeta(Material.WRITTEN_BOOK);
if (isSigned.equalsIgnoreCase("true")) {
book = new ItemStack(Material.WRITTEN_BOOK);
} else {
book = new ItemStack(Material.WRITABLE_BOOK);
}
//Load the book from the given file
BookToFromTextHelper.bookFromFile(file, bookMetadata);
if (bookMetadata == null) {
sendErrorMessage(sender, "File was blank!!");
return null;
}
//Remove "encrypted" from the book lore
if (bookDirectory == BookDirectory.ENCRYPTED && bookMetadata.hasLore()) {
List<String> oldLore = bookMetadata.getLore();
if (oldLore != null) {
List<String> newLore = new ArrayList<>(oldLore);
newLore.remove(0);
bookMetadata.setLore(newLore);
}
}
//Set the metadata and amount to the new book
book.setItemMeta(bookMetadata);
book.setAmount(numCopies);
return book;
}
/**
* Gets a File pointing to the wanted book
*
* @param sender <p>The sender to send errors to</p>
* @param fileName <p>The name of the book file</p>
* @param bookDirectory <p>The book directory the file resides in</p>
* @param directory <p>The relative directory given</p>
* @return <p>A file or null if it does not exist</p>
*/
private File getFullPath(CommandSender sender, String fileName, BookDirectory bookDirectory, String directory) {
File file = null;
if (bookDirectory == BookDirectory.PUBLIC) {
file = FileHelper.getBookFile(getBookFolder() + fileName);
} else if (bookDirectory == BookDirectory.PLAYER) {
file = FileHelper.getBookFile(getBookFolder() + cleanString(sender.getName()) + getSlash() + fileName);
} else if (bookDirectory == BookDirectory.ENCRYPTED) {
file = FileHelper.getBookFile(getBookFolder() + "Encrypted" + getSlash() + directory + getSlash() + fileName);
}
if (file == null || !file.isFile()) {
sendErrorMessage(sender, "Incorrect file name!");
return null;
} else {
return file;
}
}
/**
* Sends a success message to a command sender (player or a console)
*
@ -585,13 +279,4 @@ public class BooksWithoutBorders extends JavaPlugin {
sender.sendMessage(getErrorColor() + message);
}
/**
* Checks whether books have a price for printing them
*
* @return <p>True if players need to pay for printing books</p>
*/
public boolean booksHavePrice() {
return (bookPriceType != null && bookPriceQuantity > 0);
}
}

View File

@ -1,77 +0,0 @@
package net.knarcraft.bookswithoutborders;
import org.bukkit.ChatColor;
/**
* Class for getting various settings
*/
public class BooksWithoutBordersSettings {
//Static settings
private static final ChatColor errorColor = ChatColor.RED;
private static final ChatColor successColor = ChatColor.GREEN;
private static final ChatColor commandColor = ChatColor.YELLOW;
private static final String SLASH = System.getProperty("file.separator");
private static boolean isInitialized;
public static String bookFolder;
/**
* Initializes the books without borders settings class
*
* @param booksWithoutBorders <p>The books without borders object used for getting required data</p>
*/
public static void initialize(BooksWithoutBorders booksWithoutBorders) {
if (isInitialized) {
throw new IllegalArgumentException("Settings class initialized twice. This should not happen!");
}
isInitialized = true;
bookFolder = booksWithoutBorders.getDataFolder().getAbsolutePath() + getSlash() + "Books" + getSlash();
}
/**
* Gets the folder used for storing books
*
* @return <p>The folder used for storing books</p>
*/
public static String getBookFolder() {
return bookFolder;
}
/**
* Gets the color to use for error messages
*
* @return <p>The color to use for error messages</p>
*/
public static ChatColor getErrorColor() {
return errorColor;
}
/**
* Gets the color to use for success messages
*
* @return <p>The color to use for success messages</p>
*/
public static ChatColor getSuccessColor() {
return successColor;
}
/**
* Gets the color used to color commands
*
* @return <p>The color used to color commands</p>
*/
public static ChatColor getCommandColor() {
return commandColor;
}
/**
* Gets the correct slash to use for the used OS
*
* @return <p>The slash to use for file separators</p>
*/
public static String getSlash() {
return SLASH;
}
}

View File

@ -1,6 +1,7 @@
package net.knarcraft.bookswithoutborders.command;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
import net.knarcraft.bookswithoutborders.utility.EconomyHelper;
import org.bukkit.Material;
import org.bukkit.command.Command;
@ -8,25 +9,25 @@ import org.bukkit.command.CommandSender;
import org.bukkit.command.PluginCommand;
import org.bukkit.command.TabExecutor;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import static net.knarcraft.bookswithoutborders.BooksWithoutBorders.sendErrorMessage;
import static net.knarcraft.bookswithoutborders.BooksWithoutBordersSettings.getCommandColor;
import static net.knarcraft.bookswithoutborders.BooksWithoutBordersSettings.getSuccessColor;
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
*/
public class CommandBooksWithoutBorders implements TabExecutor {
private final BooksWithoutBorders booksWithoutBorders = BooksWithoutBorders.getInstance();
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
public boolean onCommand(CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
sender.sendMessage(getCommandColor() + "[] denote optional parameters");
sender.sendMessage(getCommandColor() + "<> denote required parameters");
sender.sendMessage(getCommandColor() + "{} denote required permission");
sender.sendMessage(getCommandColor() + "In some cases, commands with required parameters can be called with no parameters");
if (sender instanceof Player) {
showPlayerCommands(sender);
@ -43,9 +44,9 @@ public class CommandBooksWithoutBorders implements TabExecutor {
*/
private void showConsoleCommands(CommandSender sender) {
sender.sendMessage(getCommandColor() + "Commands:");
showCommandInfo("reload", sender);
showCommandInfo("givePublicBook", sender);
showCommandInfo("deletePublicBook", sender);
showCommandInfo("givePublicBook", sender);
showCommandInfo("reload", sender);
}
/**
@ -55,9 +56,9 @@ public class CommandBooksWithoutBorders implements TabExecutor {
*/
private void showPlayerCommands(CommandSender sender) {
//Lists all commands
Material bookPriceType = BooksWithoutBorders.getBookPriceType();
double bookPriceQuantity = BooksWithoutBorders.getBookPriceQuantity();
if (booksWithoutBorders.booksHavePrice()) {
Material bookPriceType = BooksWithoutBordersConfig.getBookPriceType();
double bookPriceQuantity = BooksWithoutBordersConfig.getBookPriceQuantity();
if (BooksWithoutBordersConfig.booksHavePrice()) {
if (bookPriceType != Material.AIR) {
sendErrorMessage(sender, "[" + (int) bookPriceQuantity + " " + bookPriceType.toString() +
"(s) are required to create a book]");
@ -68,24 +69,26 @@ public class CommandBooksWithoutBorders implements TabExecutor {
}
sender.sendMessage(getCommandColor() + "Commands:");
showCommandInfo("loadBook", sender);
showCommandInfo("loadPublicBook", sender);
showCommandInfo("saveBook", sender);
showCommandInfo("savePublicBook", sender);
showCommandInfo("giveBook", sender);
showCommandInfo("givePublicBook", sender);
showCommandInfo("copyBook", sender);
showCommandInfo("decryptBook", sender);
showCommandInfo("deleteBook", sender);
showCommandInfo("deletePublicBook", sender);
showCommandInfo("unsignBook", sender);
showCommandInfo("copyBook", sender);
showCommandInfo("encryptBook", sender);
showCommandInfo("formatBook", sender);
showCommandInfo("giveBook", sender);
showCommandInfo("givePublicBook", sender);
showCommandInfo("groupEncryptBook", sender);
showCommandInfo("decryptBook", sender);
showCommandInfo("setTitle", sender);
showCommandInfo("setAuthor", sender);
showCommandInfo("setLore", sender);
showCommandInfo("setBookPrice", 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);
}
/**
@ -99,15 +102,22 @@ public class CommandBooksWithoutBorders implements TabExecutor {
if (pluginCommand != null) {
String permission = pluginCommand.getPermission();
if (permission == null || sender.hasPermission(permission)) {
sender.sendMessage("\n" + getCommandColor() +
pluginCommand.getUsage().replace("<command>", pluginCommand.getName()) + ": " +
getSuccessColor() + pluginCommand.getDescription());
String commandInfo = "\n" + getCommandColor() + pluginCommand.getUsage().replace("<command>",
pluginCommand.getName()) + ": " + getSuccessColor() + pluginCommand.getDescription();
if (sender.hasPermission("bookswithoutborders.admin")) {
if (permission == null) {
permission = "None";
}
commandInfo += getCommandColor() + " {" + permission + "}";
}
sender.sendMessage(commandInfo);
}
}
}
@Override
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) {
return new ArrayList<>();
}
}

View File

@ -1,16 +1,19 @@
package net.knarcraft.bookswithoutborders.command;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
import net.knarcraft.bookswithoutborders.utility.BookHelper;
import net.knarcraft.bookswithoutborders.utility.EconomyHelper;
import net.knarcraft.bookswithoutborders.utility.InputCleaningHelper;
import net.knarcraft.bookswithoutborders.utility.InventoryHelper;
import net.knarcraft.bookswithoutborders.utility.TabCompletionHelper;
import org.bukkit.Material;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.BookMeta;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
@ -21,10 +24,8 @@ import java.util.Objects;
*/
public class CommandCopy implements TabExecutor {
private final BooksWithoutBorders booksWithoutBorders = BooksWithoutBorders.getInstance();
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
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;
@ -40,57 +41,110 @@ public class CommandCopy implements TabExecutor {
return false;
}
ItemStack heldBook = InventoryHelper.getHeldBook(player, true);
try {
ItemStack heldBook = InventoryHelper.getHeldBook(player, true);
int copies = Integer.parseInt(args[0]);
if (copies > 0) {
if (BooksWithoutBorders.getAuthorOnlyCopy() && !player.hasPermission("bookswithoutborders.bypassAuthorOnlyCopy")) {
if (!isAuthor(player, (BookMeta) Objects.requireNonNull(heldBook.getItemMeta())))
return false;
}
if (booksWithoutBorders.booksHavePrice() &&
!player.hasPermission("bookswithoutborders.bypassBookPrice") &&
EconomyHelper.cannotPayForBookPrinting(player, copies)) {
return false;
}
heldBook.setAmount(heldBook.getAmount() + copies);
BooksWithoutBorders.sendSuccessMessage(player, "Book copied!");
return true;
if (copies <= 0) {
throw new NumberFormatException("Number of copies must be larger than 0");
}
return performCopy(copies, player, heldBook);
} catch (NumberFormatException ignored) {
BooksWithoutBorders.sendErrorMessage(player, "Book not copied!");
BooksWithoutBorders.sendErrorMessage(player, "Number specified was invalid!");
return false;
}
BooksWithoutBorders.sendErrorMessage(player, "Book not copied!");
BooksWithoutBorders.sendErrorMessage(player, "Number specified was invalid!");
return false;
}
/**
* 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 book <p>The book to check</p>
* @return <p>True if the player is the book's author</p>
* @param copies <p>The number of copies to be made</p>
* @param player <p>The player requesting the copies</p>
* @param heldBook <p>The book to be copied</p>
* @return <p>True if the copying was successful</p>
*/
private boolean isAuthor(Player player, BookMeta book) {
String author = book.getAuthor();
String playerName = InputCleaningHelper.cleanString(player.getName());
if (author != null && playerName.equalsIgnoreCase(InputCleaningHelper.cleanString(author))) {
return true;
private boolean performCopy(int copies, Player player, ItemStack heldBook) {
//Make sure the player owns the book if authorOnlyCopy is enabled
if (BooksWithoutBordersConfig.getAuthorOnlyCopy() &&
!player.hasPermission("bookswithoutborders.bypassAuthorOnlyCopy")) {
if (BookHelper.isNotAuthor(player, (BookMeta) Objects.requireNonNull(heldBook.getItemMeta()))) {
return false;
}
}
BooksWithoutBorders.sendErrorMessage(player, "You must be the author of this book to use this command!");
return false;
BookMeta bookMeta = (BookMeta) heldBook.getItemMeta();
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
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias,
@NotNull String[] args) {
int argumentCount = args.length;
if (argumentCount == 1) {
return TabCompletionHelper.getNumbers(1, 20);
return TabCompletionHelper.filterMatchingStartsWith(TabCompletionHelper.getNumbers(1, 20), args[0]);
}
return new ArrayList<>();
}
}

View File

@ -1,6 +1,8 @@
package net.knarcraft.bookswithoutborders.command;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
import net.knarcraft.bookswithoutborders.utility.BookHelper;
import net.knarcraft.bookswithoutborders.utility.EncryptionHelper;
import net.knarcraft.bookswithoutborders.utility.InventoryHelper;
import org.bukkit.command.Command;
@ -9,13 +11,14 @@ 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 java.io.File;
import java.util.ArrayList;
import java.util.List;
import static net.knarcraft.bookswithoutborders.BooksWithoutBordersSettings.getBookFolder;
import static net.knarcraft.bookswithoutborders.BooksWithoutBordersSettings.getSlash;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getBookFolder;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getSlash;
/**
* Command executor for the decrypt command
@ -23,7 +26,7 @@ import static net.knarcraft.bookswithoutborders.BooksWithoutBordersSettings.getS
public class CommandDecrypt implements TabExecutor {
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
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;
@ -43,14 +46,8 @@ public class CommandDecrypt implements TabExecutor {
}
//Warning: admin decrypt only allows decrypting files created by the same player. Not sure if intended
if (args.length == 0 && BooksWithoutBorders.getAdminDecrypt() && player.hasPermission("bookswithoutborders.admin")) {
if (args.length == 0 && BooksWithoutBordersConfig.getAdminDecrypt() && player.hasPermission("bookswithoutborders.admin")) {
String path = getBookFolder() + "Encrypted" + getSlash();
String fileName;
if (bookMetadata.hasTitle()) {
fileName = bookMetadata.getTitle() + BooksWithoutBorders.getTitleAuthorSeparator() + bookMetadata.getAuthor();
} else {
fileName = "Untitled," + player.getName();
}
File encryptedDirectory = new File(path);
String[] encryptedFiles = encryptedDirectory.list();
@ -62,7 +59,7 @@ public class CommandDecrypt implements TabExecutor {
//Get the "encryption key" from the filename
String key = "";
for (String encryptedFile : encryptedFiles) {
if (encryptedFile.contains(fileName)) {
if (encryptedFile.contains(BookHelper.getBookFile(bookMetadata, player, true))) {
key = encryptedFile.substring(encryptedFile.indexOf("[") + 1, encryptedFile.indexOf("]"));
break;
}
@ -102,7 +99,7 @@ public class CommandDecrypt implements TabExecutor {
}
@Override
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) {
int argumentCount = args.length;
if (argumentCount == 1) {
List<String> info = new ArrayList<>();
@ -111,4 +108,5 @@ public class CommandDecrypt implements TabExecutor {
}
return new ArrayList<>();
}
}

View File

@ -1,27 +1,27 @@
package net.knarcraft.bookswithoutborders.command;
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.InputCleaningHelper;
import net.knarcraft.bookswithoutborders.utility.TabCompletionHelper;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import static net.knarcraft.bookswithoutborders.BooksWithoutBordersSettings.getBookFolder;
import static net.knarcraft.bookswithoutborders.BooksWithoutBordersSettings.getSlash;
/**
* Command executor for the delete command
*/
public class CommandDelete implements TabExecutor {
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (!(sender instanceof Player)) {
BooksWithoutBorders.sendErrorMessage(sender, "This command can only be used by a player!");
return false;
@ -80,13 +80,9 @@ public class CommandDelete implements TabExecutor {
}
//Get the file to be deleted
File file;
if (isPublic) {
file = FileHelper.getBookFile(getBookFolder() + fileName);
} else {
file = FileHelper.getBookFile(getBookFolder() +
InputCleaningHelper.cleanString(sender.getName()) + getSlash() + fileName);
}
String bookDirectory = BookHelper.getBookDirectoryPathString(
isPublic ? BookDirectory.PUBLIC : BookDirectory.PLAYER, sender);
File file = FileHelper.getBookFile(bookDirectory + fileName);
//Send message if no such file could be found
if (file == null) {
@ -107,21 +103,23 @@ public class CommandDelete implements TabExecutor {
}
@Override
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) {
return doTabCompletion(sender, args, false);
}
/**
* Performs tab completion
* @param sender <p>The sender of the command</p>
* @param args <p>The arguments given</p>
*
* @param sender <p>The sender of the command</p>
* @param args <p>The arguments given</p>
* @param deletePublic <p>Whether to delete a public book</p>
* @return <p>A list of available arguments</p>
*/
protected List<String> doTabCompletion(CommandSender sender, String[] args, boolean deletePublic) {
int argumentCount = args.length;
if (argumentCount == 1) {
return BooksWithoutBorders.getAvailableBooks(sender, deletePublic);
return TabCompletionHelper.filterMatchingContains(BooksWithoutBorders.getAvailableBooks(sender, deletePublic),
args[0]);
}
return new ArrayList<>();
}

View File

@ -3,6 +3,7 @@ package net.knarcraft.bookswithoutborders.command;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor;
import org.jetbrains.annotations.NotNull;
import java.util.List;
@ -12,12 +13,12 @@ import java.util.List;
public class CommandDeletePublic extends CommandDelete implements TabExecutor {
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
return deleteBook(sender, args, true);
}
@Override
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, String[] args) {
return doTabCompletion(sender, args, true);
}

View File

@ -5,12 +5,14 @@ import net.knarcraft.bookswithoutborders.state.EncryptionStyle;
import net.knarcraft.bookswithoutborders.state.ItemSlot;
import net.knarcraft.bookswithoutborders.utility.EncryptionHelper;
import net.knarcraft.bookswithoutborders.utility.InventoryHelper;
import net.knarcraft.bookswithoutborders.utility.TabCompletionHelper;
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 java.util.ArrayList;
import java.util.List;
@ -21,7 +23,7 @@ import java.util.List;
public class CommandEncrypt implements TabExecutor {
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (performPreChecks(sender, args, 1, "You must specify a key to encrypt a book!") == null) {
return false;
}
@ -96,13 +98,14 @@ public class CommandEncrypt implements TabExecutor {
}
@Override
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, String[] args) {
return doTabCompletion(args, false);
}
/**
* Gets a list of string for tab completions
* @param args <p>The arguments given</p>
*
* @param args <p>The arguments given</p>
* @param groupEncrypt <p>Whether to auto-complete for group encryption</p>
* @return <p>The strings to auto-complete</p>
*/
@ -123,12 +126,13 @@ public class CommandEncrypt implements TabExecutor {
info.add("<group>");
return info;
} else {
return encryptionStyles;
return TabCompletionHelper.filterMatchingStartsWith(encryptionStyles, args[1]);
}
} else if (argumentsCount == 3 && groupEncrypt) {
return encryptionStyles;
return TabCompletionHelper.filterMatchingStartsWith(encryptionStyles, args[2]);
}
return new ArrayList<>();
}
}

View File

@ -0,0 +1,47 @@
package net.knarcraft.bookswithoutborders.command;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.utility.BookFormatter;
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 java.util.ArrayList;
import java.util.List;
/**
* A command for converting color codes to color formatting
*/
public class CommandFormat 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 format it!",
"You cannot format two books at once!")) {
return false;
}
ItemStack heldBook = InventoryHelper.getHeldBook(player, true);
heldBook.setItemMeta(BookFormatter.formatPages((BookMeta) heldBook.getItemMeta()));
BooksWithoutBorders.sendSuccessMessage(sender, "Book formatted!");
return true;
}
@Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) {
return new ArrayList<>();
}
}

View File

@ -1,6 +1,7 @@
package net.knarcraft.bookswithoutborders.command;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.utility.BookLoader;
import net.knarcraft.bookswithoutborders.utility.FileHelper;
import net.knarcraft.bookswithoutborders.utility.InputCleaningHelper;
import net.knarcraft.bookswithoutborders.utility.TabCompletionHelper;
@ -10,6 +11,7 @@ import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
@ -22,7 +24,7 @@ public class CommandGive implements TabExecutor {
private final BooksWithoutBorders booksWithoutBorders = BooksWithoutBorders.getInstance();
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (!(sender instanceof Player)) {
BooksWithoutBorders.sendErrorMessage(sender, "This command can only be used by a player!");
return false;
@ -67,6 +69,20 @@ public class CommandGive implements TabExecutor {
}
}
//Try and find the target player
Player receivingPlayer = booksWithoutBorders.getServer().getPlayer(receivingPlayerName);
if (receivingPlayer == null) {
BooksWithoutBorders.sendErrorMessage(sender, "Player not found!");
return false;
}
//Make sure the receiver is able to fit the book
if (receivingPlayer.getInventory().firstEmpty() == -1) {
BooksWithoutBorders.sendErrorMessage(sender, "Receiving player must have space in their inventory" +
" to receive books!");
return false;
}
//Load books available to the player
try {
Integer.parseInt(bookIdentifier);
@ -74,29 +90,8 @@ public class CommandGive implements TabExecutor {
} catch (NumberFormatException ignored) {
}
Player receivingPlayer = booksWithoutBorders.getServer().getPlayer(receivingPlayerName);
if (receivingPlayer == null) {
BooksWithoutBorders.sendErrorMessage(sender, "Player not found!");
return false;
}
if (receivingPlayer.getInventory().firstEmpty() == -1) {
BooksWithoutBorders.sendErrorMessage(sender, "Receiving player must have space in their inventory to receive books!");
return false;
}
String bookToLoad = InputCleaningHelper.cleanString(bookIdentifier);
try {
ItemStack newBook = booksWithoutBorders.loadBook(sender, bookToLoad, isSigned, folder, Integer.parseInt(copies));
if (newBook != null) {
receivingPlayer.getInventory().addItem(newBook);
BooksWithoutBorders.sendSuccessMessage(sender, "Book sent!");
BooksWithoutBorders.sendSuccessMessage(receivingPlayer, "Book received!");
return true;
} else {
BooksWithoutBorders.sendErrorMessage(sender, "Book failed to load!");
return false;
}
return loadAndGiveBook(bookIdentifier, sender, receivingPlayer, isSigned, folder, copies);
} catch (NumberFormatException e) {
BooksWithoutBorders.sendErrorMessage(sender, "Invalid number of book copies specified!");
return false;
@ -104,14 +99,15 @@ public class CommandGive implements TabExecutor {
}
@Override
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) {
return doTabCompletion(sender, args, false);
}
/**
* Performs the actual tab completion
* @param sender <p>The sender of the command</p>
* @param args <p>The arguments given</p>
*
* @param sender <p>The sender of the command</p>
* @param args <p>The arguments given</p>
* @param listPublic <p>Whether to list public files or player files</p>
* @return <p>A list of available choices</p>
*/
@ -129,22 +125,51 @@ public class CommandGive implements TabExecutor {
if (argumentCount == 1) {
//Return list of books
return BooksWithoutBorders.getAvailableBooks(sender, listPublic);
return TabCompletionHelper.filterMatchingContains(BooksWithoutBorders.getAvailableBooks(sender, listPublic),
args[0]);
} else if (argumentCount == 2) {
//Return online players
return null;
} else if (argumentCount == 3) {
//Number of copies
return TabCompletionHelper.getBooleansAndNumbers(1, 3);
return TabCompletionHelper.filterMatchingStartsWith(TabCompletionHelper.getBooleansAndNumbers(1, 3), args[2]);
} else if (argumentCount == 4) {
//Signed
try {
Integer.parseInt(args[2]);
return TabCompletionHelper.getBooleans();
return TabCompletionHelper.filterMatchingStartsWith(TabCompletionHelper.getBooleans(), args[3]);
} catch (NumberFormatException e) {
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

@ -3,6 +3,7 @@ package net.knarcraft.bookswithoutborders.command;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor;
import org.jetbrains.annotations.NotNull;
import java.util.List;
@ -12,12 +13,12 @@ import java.util.List;
public class CommandGivePublic extends CommandGive implements TabExecutor {
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
return giveBook(sender, args, true, "public");
}
@Override
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, String[] args) {
return doTabCompletion(sender, args, true);
}

View File

@ -7,6 +7,7 @@ import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor;
import org.bukkit.entity.Player;
import org.bukkit.inventory.meta.BookMeta;
import org.jetbrains.annotations.NotNull;
import java.util.List;
@ -16,7 +17,7 @@ import java.util.List;
public class CommandGroupEncrypt extends CommandEncrypt implements TabExecutor {
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
BookMeta bookMetadata = performPreChecks(sender, args, 2,
"You must specify a group name and key to encrypt a book!");
@ -36,7 +37,7 @@ public class CommandGroupEncrypt extends CommandEncrypt implements TabExecutor {
}
@Override
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, String[] args) {
return doTabCompletion(args, true);
}

View File

@ -1,6 +1,7 @@
package net.knarcraft.bookswithoutborders.command;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.utility.BookLoader;
import net.knarcraft.bookswithoutborders.utility.FileHelper;
import net.knarcraft.bookswithoutborders.utility.InputCleaningHelper;
import net.knarcraft.bookswithoutborders.utility.TabCompletionHelper;
@ -9,6 +10,7 @@ import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
@ -18,10 +20,8 @@ import java.util.List;
*/
public class CommandLoad implements TabExecutor {
private final BooksWithoutBorders booksWithoutBorders = BooksWithoutBorders.getInstance();
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
return loadBook(sender, args, "player", false);
}
@ -78,7 +78,7 @@ public class CommandLoad implements TabExecutor {
String bookToLoad = InputCleaningHelper.cleanString(bookIdentifier);
try {
//Give the new book if it can be loaded
ItemStack newBook = booksWithoutBorders.loadBook(player, bookToLoad, isSigned, directory, Integer.parseInt(copies));
ItemStack newBook = BookLoader.loadBook(player, bookToLoad, isSigned, directory, Integer.parseInt(copies));
if (newBook != null) {
player.getInventory().addItem(newBook);
BooksWithoutBorders.sendSuccessMessage(player, "Book created!");
@ -94,14 +94,15 @@ public class CommandLoad implements TabExecutor {
}
@Override
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) {
return doTabCompletion(sender, args, false);
}
/**
* Performs the actual tab completion
* @param sender <p>The sender of the command</p>
* @param args <p>The arguments given</p>
*
* @param sender <p>The sender of the command</p>
* @param args <p>The arguments given</p>
* @param loadPublic <p>Whether to list public files or player files</p>
* @return <p>A list of available choices</p>
*/
@ -109,19 +110,21 @@ public class CommandLoad implements TabExecutor {
int argumentCount = args.length;
if (argumentCount == 1) {
//Return list of books
return BooksWithoutBorders.getAvailableBooks(sender, loadPublic);
return TabCompletionHelper.filterMatchingContains(BooksWithoutBorders.getAvailableBooks(sender, loadPublic),
args[0]);
} else if (argumentCount == 2) {
//Number of copies
return TabCompletionHelper.getBooleansAndNumbers(1, 3);
return TabCompletionHelper.filterMatchingStartsWith(TabCompletionHelper.getBooleansAndNumbers(1, 3), args[1]);
} else if (argumentCount == 3) {
//Signed
try {
Integer.parseInt(args[1]);
return TabCompletionHelper.getBooleans();
return TabCompletionHelper.filterMatchingStartsWith(TabCompletionHelper.getBooleans(), args[2]);
} catch (NumberFormatException e) {
return new ArrayList<>();
}
}
return new ArrayList<>();
}
}

View File

@ -3,6 +3,7 @@ package net.knarcraft.bookswithoutborders.command;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import java.util.List;
@ -12,12 +13,12 @@ import java.util.List;
public class CommandLoadPublic extends CommandLoad implements CommandExecutor {
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
return loadBook(sender, args, "public", true);
}
@Override
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, String[] args) {
return doTabCompletion(sender, args, true);
}

View File

@ -1,9 +1,11 @@
package net.knarcraft.bookswithoutborders.command;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
@ -13,11 +15,9 @@ import java.util.List;
*/
public class CommandReload implements TabExecutor {
private final BooksWithoutBorders booksWithoutBorders = BooksWithoutBorders.getInstance();
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (booksWithoutBorders.loadConfig()) {
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (BooksWithoutBordersConfig.loadConfig()) {
BooksWithoutBorders.sendSuccessMessage(sender, "BooksWithoutBorders configuration reloaded!");
} else {
BooksWithoutBorders.sendErrorMessage(sender, "Reload Failed!");
@ -27,7 +27,8 @@ public class CommandReload implements TabExecutor {
}
@Override
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) {
return new ArrayList<>();
}
}

View File

@ -1,7 +1,10 @@
package net.knarcraft.bookswithoutborders.command;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
import net.knarcraft.bookswithoutborders.state.BookDirectory;
import net.knarcraft.bookswithoutborders.state.ItemSlot;
import net.knarcraft.bookswithoutborders.utility.BookHelper;
import net.knarcraft.bookswithoutborders.utility.BookToFromTextHelper;
import net.knarcraft.bookswithoutborders.utility.FileHelper;
import net.knarcraft.bookswithoutborders.utility.InventoryHelper;
@ -11,19 +14,16 @@ 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 java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import static net.knarcraft.bookswithoutborders.BooksWithoutBorders.getTitleAuthorSeparator;
import static net.knarcraft.bookswithoutborders.BooksWithoutBordersSettings.getBookFolder;
import static net.knarcraft.bookswithoutborders.BooksWithoutBordersSettings.getCommandColor;
import static net.knarcraft.bookswithoutborders.BooksWithoutBordersSettings.getErrorColor;
import static net.knarcraft.bookswithoutborders.BooksWithoutBordersSettings.getSlash;
import static net.knarcraft.bookswithoutborders.utility.InputCleaningHelper.cleanString;
import static net.knarcraft.bookswithoutborders.utility.InputCleaningHelper.fixName;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getCommandColor;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getErrorColor;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getTitleAuthorSeparator;
/**
* Command executor for the save command
@ -31,7 +31,7 @@ import static net.knarcraft.bookswithoutborders.utility.InputCleaningHelper.fixN
public class CommandSave implements TabExecutor {
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
return saveHeldBook(sender, args, false);
}
@ -50,7 +50,7 @@ public class CommandSave implements TabExecutor {
}
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);
boolean duplicate = args.length == 1 && Boolean.parseBoolean(args[0]);
saveBook(player, holdingItem, duplicate, savePublic);
@ -76,32 +76,30 @@ public class CommandSave implements TabExecutor {
return;
}
String savePath;
if (saveToPublicFolder) {
savePath = getBookFolder();
} else {
savePath = getBookFolder() + cleanString(player.getName()) + getSlash();
//Only allow saving of own books if enabled
if (BooksWithoutBordersConfig.getAuthorOnlySave() && !saveToPublicFolder &&
(!player.hasPermission("bookswithoutborders.bypassAuthorOnlySave") &&
BookHelper.isNotAuthor(player, book))) {
return;
}
String savePath = BookHelper.getBookDirectoryPathString(
saveToPublicFolder ? BookDirectory.PUBLIC : BookDirectory.PLAYER, player);
//Generate book filename
String fileName;
if (!book.hasTitle()) {
fileName = "Untitled," + player.getName();
} else {
fileName = book.getTitle() + getTitleAuthorSeparator() + book.getAuthor();
}
fileName = cleanString(fileName);
fileName = fixName(fileName, false);
String fileName = BookHelper.getBookFile(book, player, saveToPublicFolder);
//Make sure the used folders exist
File file = new File(savePath);
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;
}
File[] foundFiles = file.listFiles();
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;
}
@ -112,27 +110,30 @@ public class CommandSave implements TabExecutor {
if (foundDuplicates > 0) {
//TODO: Decide if this makes sense or needs to be changed
//Skip duplicate book
if (!fileName.contains("Untitled") && !overwrite) {
if (!fileName.contains("Untitled" + getTitleAuthorSeparator()) && !overwrite) {
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;
}
//Skip if duplicate limit is reached
if (foundDuplicates > BooksWithoutBorders.getBookDuplicateLimit()) {
BooksWithoutBorders.sendErrorMessage(player, "Maximum amount of " + fileName + " duplicates reached!");
BooksWithoutBorders.sendErrorMessage(player, "Use " + getCommandColor() + "/savebook true " + getErrorColor() + "to overwrite!");
if (foundDuplicates > BooksWithoutBordersConfig.getBookDuplicateLimit()) {
BooksWithoutBorders.sendErrorMessage(player, "Maximum amount of " + fileName +
" duplicates reached!");
BooksWithoutBorders.sendErrorMessage(player, "Use " + getCommandColor() + "/savebook true " +
getErrorColor() + "to overwrite!");
return;
}
//Alter duplicated filename
if (fileName.contains("Untitled") && !overwrite) {
if (fileName.contains("Untitled" + getTitleAuthorSeparator()) && !overwrite) {
fileName = "(" + foundDuplicates + ")" + fileName;
}
}
try {
if (BooksWithoutBorders.getUseYml()) {
if (BooksWithoutBordersConfig.getUseYml()) {
BookToFromTextHelper.bookToYml(savePath, fileName, book);
} else {
BookToFromTextHelper.bookToTXT(savePath, fileName, book);
@ -148,7 +149,9 @@ public class CommandSave implements TabExecutor {
}
@Override
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias,
@NotNull String[] args) {
return new ArrayList<>();
}
}

View File

@ -3,6 +3,7 @@ package net.knarcraft.bookswithoutborders.command;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor;
import org.jetbrains.annotations.NotNull;
/**
* Command executor for the save public command
@ -10,7 +11,7 @@ import org.bukkit.command.TabExecutor;
public class CommandSavePublic extends CommandSave implements TabExecutor {
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
return saveHeldBook(sender, args, true);
}

View File

@ -9,6 +9,7 @@ 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 java.util.ArrayList;
import java.util.List;
@ -19,7 +20,7 @@ import java.util.List;
public class CommandSetAuthor implements TabExecutor {
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
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;
@ -49,11 +50,12 @@ public class CommandSetAuthor implements TabExecutor {
}
@Override
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, String[] args) {
if (args.length == 1) {
return null;
} else {
return new ArrayList<>();
}
}
}

View File

@ -1,6 +1,7 @@
package net.knarcraft.bookswithoutborders.command;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
import net.knarcraft.bookswithoutborders.utility.EconomyHelper;
import net.knarcraft.bookswithoutborders.utility.InventoryHelper;
import net.knarcraft.bookswithoutborders.utility.TabCompletionHelper;
@ -10,6 +11,7 @@ import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
@ -20,9 +22,10 @@ import java.util.List;
public class CommandSetBookPrice implements TabExecutor {
private final BooksWithoutBorders booksWithoutBorders = BooksWithoutBorders.getInstance();
private List<String> paymentTypes;
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
//Clear the current price
if (args.length == 0) {
clearItemPrice(sender);
@ -65,10 +68,10 @@ public class CommandSetBookPrice implements TabExecutor {
* @param sender <p>The sender of the command</p>
*/
private void clearItemPrice(CommandSender sender) {
BooksWithoutBorders.setBookPriceType(null);
BooksWithoutBorders.setBookPriceQuantity(0);
BooksWithoutBordersConfig.setBookPriceType(null);
BooksWithoutBordersConfig.setBookPriceQuantity(0);
booksWithoutBorders.getConfig().set("Options.Price_to_create_book.Item_type", "Item type name");
booksWithoutBorders.getConfig().set("Options.Price_to_create_book.Required_quantity", BooksWithoutBorders.getBookPriceQuantity());
booksWithoutBorders.getConfig().set("Options.Price_to_create_book.Required_quantity", BooksWithoutBordersConfig.getBookPriceQuantity());
booksWithoutBorders.saveConfig();
BooksWithoutBorders.sendSuccessMessage(sender, "Price to create books removed!");
@ -93,10 +96,10 @@ public class CommandSetBookPrice implements TabExecutor {
return false;
}
BooksWithoutBorders.setBookPriceType(heldItem.getType());
BooksWithoutBorders.setBookPriceQuantity(price);
String newPriceType = BooksWithoutBorders.getBookPriceType().toString();
double newPriceQuantity = BooksWithoutBorders.getBookPriceQuantity();
BooksWithoutBordersConfig.setBookPriceType(heldItem.getType());
BooksWithoutBordersConfig.setBookPriceQuantity(price);
String newPriceType = BooksWithoutBordersConfig.getBookPriceType().toString();
double newPriceQuantity = BooksWithoutBordersConfig.getBookPriceQuantity();
booksWithoutBorders.getConfig().set("Options.Price_to_create_book.Item_type", newPriceType);
booksWithoutBorders.getConfig().set("Options.Price_to_create_book.Required_quantity", newPriceQuantity);
booksWithoutBorders.saveConfig();
@ -115,9 +118,9 @@ public class CommandSetBookPrice implements TabExecutor {
*/
private boolean setEconomyPrice(CommandSender sender, double price) {
if (EconomyHelper.setupEconomy()) {
BooksWithoutBorders.setBookPriceQuantity(price);
BooksWithoutBorders.setBookPriceType(Material.AIR);
double newPriceQuantity = BooksWithoutBorders.getBookPriceQuantity();
BooksWithoutBordersConfig.setBookPriceQuantity(price);
BooksWithoutBordersConfig.setBookPriceType(Material.AIR);
double newPriceQuantity = BooksWithoutBordersConfig.getBookPriceQuantity();
booksWithoutBorders.getConfig().set("Options.Price_to_create_book.Item_type", "Economy");
booksWithoutBorders.getConfig().set("Options.Price_to_create_book.Required_quantity", newPriceQuantity);
booksWithoutBorders.saveConfig();
@ -132,16 +135,27 @@ public class CommandSetBookPrice implements TabExecutor {
}
@Override
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, String[] args) {
if (paymentTypes == null) {
initializeTabCompleteLists();
}
int argumentCount = args.length;
if (argumentCount == 1) {
List<String> paymentTypes = new ArrayList<>();
paymentTypes.add("item");
paymentTypes.add("eco");
return paymentTypes;
return TabCompletionHelper.filterMatchingStartsWith(paymentTypes, args[0]);
} else if (argumentCount == 2) {
return TabCompletionHelper.getNumbers(1, 3);
return TabCompletionHelper.filterMatchingStartsWith(TabCompletionHelper.getNumbers(1, 3), args[1]);
}
return new ArrayList<>();
}
/**
* Initializes the lists of tab complete values
*/
private void initializeTabCompleteLists() {
paymentTypes = new ArrayList<>();
paymentTypes.add("item");
paymentTypes.add("eco");
}
}

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,6 +1,7 @@
package net.knarcraft.bookswithoutborders.command;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
import net.knarcraft.bookswithoutborders.utility.InventoryHelper;
import org.bukkit.ChatColor;
import org.bukkit.Material;
@ -10,6 +11,7 @@ import org.bukkit.command.TabExecutor;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Arrays;
@ -21,7 +23,7 @@ import java.util.List;
public class CommandSetLore implements TabExecutor {
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
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;
@ -43,7 +45,7 @@ public class CommandSetLore implements TabExecutor {
//Format lore
rawLore = ChatColor.translateAlternateColorCodes('&', rawLore);
String[] loreParts = rawLore.split(BooksWithoutBorders.getLoreSeparator());
String[] loreParts = rawLore.split(BooksWithoutBordersConfig.getLoreSeparator());
List<String> newLore = new ArrayList<>(Arrays.asList(loreParts));
//Update lore
@ -59,10 +61,11 @@ public class CommandSetLore implements TabExecutor {
}
@Override
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, String[] args) {
//TODO: Figure out if there is a better way to display that an argument is required
List<String> options = new ArrayList<>();
options.add("<new lore>");
return options;
}
}

View File

@ -11,6 +11,7 @@ import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.BookMeta;
import org.bukkit.inventory.meta.ItemMeta;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
@ -21,7 +22,7 @@ import java.util.List;
public class CommandSetTitle implements TabExecutor {
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
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;
@ -65,9 +66,10 @@ public class CommandSetTitle implements TabExecutor {
}
@Override
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) {
List<String> options = new ArrayList<>();
options.add("<new title>");
return options;
}
}

View File

@ -1,7 +1,9 @@
package net.knarcraft.bookswithoutborders.command;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
import net.knarcraft.bookswithoutborders.state.ItemSlot;
import net.knarcraft.bookswithoutborders.utility.BookHelper;
import net.knarcraft.bookswithoutborders.utility.InventoryHelper;
import org.bukkit.Material;
import org.bukkit.command.Command;
@ -10,9 +12,11 @@ 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 java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* Command executor for the unsign command
@ -20,7 +24,7 @@ import java.util.List;
public class CommandUnSign implements TabExecutor {
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
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;
@ -47,6 +51,13 @@ public class CommandUnSign implements TabExecutor {
//Get the old book
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
ItemStack newBook = new ItemStack(Material.WRITABLE_BOOK);
newBook.setItemMeta(oldBook);
@ -55,7 +66,8 @@ public class CommandUnSign implements TabExecutor {
}
@Override
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) {
return new ArrayList<>();
}
}

View File

@ -0,0 +1,413 @@
package net.knarcraft.bookswithoutborders.config;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.utility.EconomyHelper;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.configuration.Configuration;
import java.util.ArrayList;
import java.util.List;
import static net.knarcraft.bookswithoutborders.BooksWithoutBorders.sendErrorMessage;
import static net.knarcraft.bookswithoutborders.BooksWithoutBorders.sendSuccessMessage;
import static net.knarcraft.bookswithoutborders.utility.InputCleaningHelper.cleanString;
/**
* A config class that keeps track of all config values
*/
public class BooksWithoutBordersConfig {
private static final ChatColor errorColor = ChatColor.RED;
private static final ChatColor successColor = ChatColor.GREEN;
private static final ChatColor commandColor = ChatColor.YELLOW;
private static final String SLASH = System.getProperty("file.separator");
private static boolean isInitialized;
public static String bookFolder;
private static int bookDuplicateLimit;
private static String titleAuthorSeparator;
private static String loreSeparator;
private static List<String> firstBooks = new ArrayList<>();
private static String welcomeMessage;
private static Material bookPriceType = null;
private static double bookPriceQuantity;
private static boolean authorOnlyCopy;
private static boolean authorOnlyUnsign;
private static boolean authorOnlySave;
private static boolean useYml;
private static boolean adminDecrypt;
private static boolean formatBooks;
private static boolean changeGenerationOnCopy;
/**
* Initializes the books without borders settings class
*
* @param booksWithoutBorders <p>The books without borders object used for getting required data</p>
*/
public static void initialize(BooksWithoutBorders booksWithoutBorders) {
if (isInitialized) {
throw new IllegalArgumentException("Settings class initialized twice. This should not happen!");
}
isInitialized = true;
bookFolder = booksWithoutBorders.getDataFolder().getAbsolutePath() + getSlash() + "Books" + getSlash();
loadConfig();
}
/**
* Gets the folder used for storing books
*
* @return <p>The folder used for storing books</p>
*/
public static String getBookFolder() {
return bookFolder;
}
/**
* Gets the color to use for error messages
*
* @return <p>The color to use for error messages</p>
*/
public static ChatColor getErrorColor() {
return errorColor;
}
/**
* Gets the color to use for success messages
*
* @return <p>The color to use for success messages</p>
*/
public static ChatColor getSuccessColor() {
return successColor;
}
/**
* Gets the color used to color commands
*
* @return <p>The color used to color commands</p>
*/
public static ChatColor getCommandColor() {
return commandColor;
}
/**
* Gets the correct slash to use for the used OS
*
* @return <p>The slash to use for file separators</p>
*/
public static String getSlash() {
return SLASH;
}
/**
* Gets whether only the author of a book should be able to copy it
*
* @return <p>Whether only the book author can copy it</p>
*/
public static boolean getAuthorOnlyCopy() {
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
*
* @return <p>Whether to use YML for saving books</p>
*/
public static boolean getUseYml() {
return useYml;
}
/**
* Gets whether admins should be able to decrypt books without a password, and decrypt all group encrypted books
*
* @return <p>Whether admins can bypass the encryption password</p>
*/
public static boolean getAdminDecrypt() {
return adminDecrypt;
}
/**
* Sets the quantity of items/currency necessary for copying books
*
* @param newQuantity <p>The new quantity necessary for payment</p>
*/
public static void setBookPriceQuantity(double newQuantity) {
bookPriceQuantity = newQuantity;
}
/**
* Gets the quantity of items/currency necessary for copying books
*
* @return <p>The quantity necessary for payment</p>
*/
public static double getBookPriceQuantity() {
return bookPriceQuantity;
}
/**
* Sets the item type used for book pricing
*
* <p>This item is the one a player has to pay for copying books. AIR is used to denote economy. null is used if
* payment is disabled. Otherwise, any item can be used.</p>
*
* @param newType <p>The new item type to use for book pricing</p>
*/
public static void setBookPriceType(Material newType) {
bookPriceType = newType;
}
/**
* Gets the item type used for book pricing
*
* <p>This item is the one a player has to pay for copying books. AIR is used to denote economy. null is used if
* payment is disabled. Otherwise, any item can be used.</p>
*
* @return <p>The item type used for book pricing</p>
*/
public static Material getBookPriceType() {
return bookPriceType;
}
/**
* Gets the welcome message to show to new players
*
* @return <p>The welcome message to show new players</p>
*/
public static String getWelcomeMessage() {
return welcomeMessage;
}
/**
* Gets the limit of duplicates for each book
*
* @return <p>The book duplicate limit</p>
*/
public static int getBookDuplicateLimit() {
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
*
* @return <p>The separator between title and author</p>
*/
public static String getTitleAuthorSeparator() {
return titleAuthorSeparator;
}
/**
* Gets the separator used to denote a newline in a lore string
*
* @return <p>The separator used to denote lore newline</p>
*/
public static String getLoreSeparator() {
return loreSeparator;
}
/**
* Gets whether all books should be formatted when they are signed
*
* @return <p>Whether all books should be formatted</p>
*/
public static boolean formatBooks() {
return formatBooks;
}
/**
* Gets a copy of the list of books to give new players
*
* @return <p>The books to give new players</p>
*/
public static List<String> getFirstBooks() {
return new ArrayList<>(firstBooks);
}
/**
* Checks whether books have a price for printing them
*
* @return <p>True if players need to pay for printing books</p>
*/
public static boolean booksHavePrice() {
return (bookPriceType != null && bookPriceQuantity > 0);
}
/**
* Saves the config
*/
public static void saveConfigValues() {
ConsoleCommandSender consoleSender = BooksWithoutBorders.getInstance().getServer().getConsoleSender();
Configuration config = BooksWithoutBorders.getInstance().getConfig();
config.set(ConfigOption.USE_YAML.getConfigNode(), useYml);
config.set(ConfigOption.MAX_DUPLICATES.getConfigNode(), bookDuplicateLimit);
config.set(ConfigOption.TITLE_AUTHOR_SEPARATOR.getConfigNode(), titleAuthorSeparator);
config.set(ConfigOption.LORE_LINE_SEPARATOR.getConfigNode(), loreSeparator);
config.set(ConfigOption.BOOKS_FOR_NEW_PLAYERS.getConfigNode(), firstBooks);
config.set(ConfigOption.MESSAGE_FOR_NEW_PLAYERS.getConfigNode(), welcomeMessage);
config.set(ConfigOption.FORMAT_AFTER_SIGNING.getConfigNode(), formatBooks);
String itemTypeNode = ConfigOption.PRICE_ITEM_TYPE.getConfigNode();
if (bookPriceType != null) {
if (bookPriceType != Material.AIR) {
config.set(itemTypeNode, bookPriceType.toString());
} else {
config.set(itemTypeNode, "Economy");
}
} else {
config.set(itemTypeNode, "Item type name");
}
config.set(ConfigOption.PRICE_QUANTITY.getConfigNode(), bookPriceQuantity);
config.set(ConfigOption.ADMIN_AUTO_DECRYPT.getConfigNode(), adminDecrypt);
config.set(ConfigOption.AUTHOR_ONLY_COPY.getConfigNode(), authorOnlyCopy);
config.set(ConfigOption.AUTHOR_ONLY_UNSIGN.getConfigNode(), authorOnlyUnsign);
config.set(ConfigOption.AUTHOR_ONLY_SAVE.getConfigNode(), authorOnlySave);
config.set(ConfigOption.CHANGE_GENERATION_ON_COPY.getConfigNode(), changeGenerationOnCopy);
//Handles old book and quill settings
if (config.contains("Options.Require_book_and_quill_to_create_book")) {
sendSuccessMessage(consoleSender, "[BooksWithoutBorders] Found old config setting " +
"\"Require_book_and_quill_to_create_book\"");
sendSuccessMessage(consoleSender, "Updating to \"Price_to_create_book\" settings");
if (config.getBoolean("Options.Require_book_and_quill_to_create_book")) {
bookPriceType = Material.WRITABLE_BOOK;
bookPriceQuantity = 1;
config.set("Options.Price_to_create_book.Item_type", bookPriceType.toString());
config.set("Options.Price_to_create_book.Required_quantity", bookPriceQuantity);
}
config.set("Options.Require_book_and_quill_to_create_book", null);
}
BooksWithoutBorders.getInstance().saveConfig();
}
/**
* Loads the config
*
* @return <p>True if the config was loaded successfully</p>
*/
public static boolean loadConfig() {
ConsoleCommandSender consoleSender = BooksWithoutBorders.getInstance().getServer().getConsoleSender();
BooksWithoutBorders.getInstance().reloadConfig();
Configuration config = BooksWithoutBorders.getInstance().getConfig();
try {
useYml = getBoolean(config, ConfigOption.USE_YAML);
bookDuplicateLimit = getInt(config, ConfigOption.MAX_DUPLICATES);
titleAuthorSeparator = getString(config, ConfigOption.TITLE_AUTHOR_SEPARATOR);
loreSeparator = getString(config, ConfigOption.LORE_LINE_SEPARATOR);
adminDecrypt = getBoolean(config, ConfigOption.ADMIN_AUTO_DECRYPT);
authorOnlyCopy = getBoolean(config, ConfigOption.AUTHOR_ONLY_COPY);
authorOnlyUnsign = getBoolean(config, ConfigOption.AUTHOR_ONLY_UNSIGN);
authorOnlySave = getBoolean(config, ConfigOption.AUTHOR_ONLY_SAVE);
firstBooks = config.getStringList(ConfigOption.BOOKS_FOR_NEW_PLAYERS.getConfigNode());
welcomeMessage = getString(config, ConfigOption.MESSAGE_FOR_NEW_PLAYERS);
formatBooks = getBoolean(config, ConfigOption.FORMAT_AFTER_SIGNING);
changeGenerationOnCopy = getBoolean(config, ConfigOption.CHANGE_GENERATION_ON_COPY);
//Convert string into material
String paymentMaterial = getString(config, ConfigOption.PRICE_ITEM_TYPE);
if (paymentMaterial.equalsIgnoreCase("Economy")) {
if (EconomyHelper.setupEconomy()) {
bookPriceType = Material.AIR;
} else {
sendErrorMessage(consoleSender,
"BooksWithoutBorders failed to hook into Vault! Book price not set!");
bookPriceType = null;
}
} else if (!paymentMaterial.trim().isEmpty()) {
Material material = Material.matchMaterial(paymentMaterial);
if (material != null) {
bookPriceType = material;
}
}
bookPriceQuantity = getDouble(config, ConfigOption.PRICE_QUANTITY);
//Make sure titleAuthorSeparator is a valid value
titleAuthorSeparator = cleanString(titleAuthorSeparator);
if (titleAuthorSeparator.length() != 1) {
sendErrorMessage(consoleSender, "Title-Author_Separator is set to an invalid value!");
sendErrorMessage(consoleSender, "Reverting to default value of \",\"");
titleAuthorSeparator = ",";
config.set("Options.Title-Author_Separator", titleAuthorSeparator);
}
} catch (Exception e) {
sendErrorMessage(consoleSender, "Warning! Config.yml failed to load!");
sendErrorMessage(consoleSender, "Try Looking for settings that are missing values!");
return false;
}
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

@ -0,0 +1,110 @@
package net.knarcraft.bookswithoutborders.config;
/**
* A representation of the different available config options
*/
public enum ConfigOption {
/**
* Whether YAML should be used to store books instead of simple text files
*/
USE_YAML("Options.Save_Books_in_Yaml_Format", true),
/**
* The max duplicates of a book that can be saved
*/
MAX_DUPLICATES("Options.Max_Number_of_Duplicates", 5),
/**
* The separator used to separate book title and book author
*/
TITLE_AUTHOR_SEPARATOR("Options.Title-Author_Separator", ","),
/**
* The separator used to specify a new line in an item's lore
*/
LORE_LINE_SEPARATOR("Options.Lore_line_separator", "~"),
/**
* The books given to new players when they first join
*/
BOOKS_FOR_NEW_PLAYERS("Options.Books_for_new_players", "[]"),
/**
* The message to display to new players when they first join
*/
MESSAGE_FOR_NEW_PLAYERS("Options.Message_for_new_players", ""),
/**
* The item type used to pay for book copying
*/
PRICE_ITEM_TYPE("Options.Price_to_create_book.Item_type", ""),
/**
* The amount of items used to pay for book copying
*/
PRICE_QUANTITY("Options.Price_to_create_book.Required_quantity", 0.0),
/**
* Whether admins should be able to decrypt books for all groups
*/
ADMIN_AUTO_DECRYPT("Options.Admin_Auto_Decrypt", false),
/**
* Whether only the book author should be able to copy a book
*/
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
*/
FORMAT_AFTER_SIGNING("Options.Format_Book_After_Signing", false);
private final String configNode;
private final Object defaultValue;
/**
* Instantiates a new config option
*
* @param configNode <p>The config node in the config file this option represents</p>
* @param defaultValue <p>The default value for this config option</p>
*/
ConfigOption(String configNode, Object defaultValue) {
this.configNode = configNode;
this.defaultValue = defaultValue;
}
/**
* Gets the config node used for loading/saving this config value
*
* @return <p>The config node</p>
*/
public String getConfigNode() {
return this.configNode;
}
/**
* Gets the default value of this config option
*
* @return <p>The default value of this config option</p>
*/
public Object getDefaultValue() {
return this.defaultValue;
}
}

View File

@ -14,8 +14,6 @@ public class GenenCrypt {
private final Random ranGen;
private final String[] bases;
private final ArrayList<String> originalCodonList;
private final ArrayList<String> shuffledCodonList;
private final String[] charList;
private final HashMap<String, String[]> codonTable;
private final HashMap<String, String> decryptTable;
@ -29,7 +27,7 @@ public class GenenCrypt {
public GenenCrypt(String key) {
// define the initial, unshuffled codon list of 4 base codons
originalCodonList = new ArrayList<>();
ArrayList<String> originalCodonList = new ArrayList<>();
bases = new String[]{"A", "T", "G", "C"};
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
@ -50,7 +48,7 @@ public class GenenCrypt {
ranGen = new java.util.Random(longKey);
// use the random number generator and the originalCodonList to make a shuffled list
shuffledCodonList = new ArrayList<>();
ArrayList<String> shuffledCodonList = new ArrayList<>();
while (originalCodonList.size() > 0) {
int index = ranGen.nextInt(originalCodonList.size());
shuffledCodonList.add(originalCodonList.get(index));
@ -90,24 +88,6 @@ public class GenenCrypt {
}
/**
* Prints the shuffled codon list used for generating the codon table
*/
public void printShuffledList() {
for (String s : shuffledCodonList) {
System.out.println(s);
}
}
/**
* Prints the original codon list before it was shuffled
*/
public void printOriginalList() {
for (String s : originalCodonList) {
System.out.println(s);
}
}
/**
* Prints the codon table used for encryption and decryption
*/
@ -181,4 +161,5 @@ public class GenenCrypt {
}
return output.toString();
}
}

View File

@ -3,6 +3,9 @@ package net.knarcraft.bookswithoutborders.encryption;
import java.math.BigInteger;
import java.util.StringTokenizer;
/**
* A simple substitution cipher
*/
public class SubstitutionCipher {
public SubstitutionCipher() {
@ -83,5 +86,6 @@ public class SubstitutionCipher {
}
return output.toString();
}
}

View File

@ -0,0 +1,24 @@
package net.knarcraft.bookswithoutborders.listener;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
import net.knarcraft.bookswithoutborders.utility.BookFormatter;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerEditBookEvent;
/**
* A listener for listening to book events
*
* <p>Mainly used for auto-formatting signed books if enabled</p>
*/
public class BookEventListener implements Listener {
@EventHandler
public void onBookSign(PlayerEditBookEvent event) {
if (event.isCancelled() || !event.isSigning() || !BooksWithoutBordersConfig.formatBooks()) {
return;
}
event.setNewBookMeta(BookFormatter.formatPages(event.getNewBookMeta()));
}
}

View File

@ -1,6 +1,10 @@
package net.knarcraft.bookswithoutborders.listener;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
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.InputCleaningHelper;
import net.knarcraft.bookswithoutborders.utility.InventoryHelper;
import org.bukkit.Material;
@ -15,13 +19,13 @@ import org.bukkit.inventory.meta.BookMeta;
import org.bukkit.inventory.meta.ItemMeta;
import java.io.File;
import java.util.logging.Level;
import static net.knarcraft.bookswithoutborders.BooksWithoutBordersSettings.getBookFolder;
import static net.knarcraft.bookswithoutborders.BooksWithoutBordersSettings.getSlash;
/**
* A listener for listening to player-related events such as joining or holding a book
*/
public class PlayerEventListener implements Listener {
private final String slash = getSlash();
private final BooksWithoutBorders booksWithoutBorders = BooksWithoutBorders.getInstance();
@EventHandler
@ -53,12 +57,22 @@ public class PlayerEventListener implements Listener {
public void onPlayerJoin(PlayerJoinEvent event) {
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
if (!player.hasPlayedBefore()) {
boolean sendMessage = true;
//Gives new players necessary books
for (String bookName : BooksWithoutBorders.getFirstBooks()) {
for (String bookName : BooksWithoutBordersConfig.getFirstBooks()) {
sendMessage = giveBookToNewPlayer(bookName, player, sendMessage);
}
}
@ -88,13 +102,13 @@ public class PlayerEventListener implements Listener {
if (!bookName.trim().isEmpty()) {
//Give the book to the player if it exists
ItemStack newBook = booksWithoutBorders.loadBook(player, bookName, "true", "public");
ItemStack newBook = BookLoader.loadBook(player, bookName, "true", "public");
if (newBook != null) {
player.getInventory().addItem(newBook);
}
//Send the player a welcome message if it exists
String welcomeMessage = BooksWithoutBorders.getWelcomeMessage();
String welcomeMessage = BooksWithoutBordersConfig.getWelcomeMessage();
if (!welcomeMessage.trim().isEmpty() && newBook != null && sendMessage) {
sendMessage = false;
booksWithoutBorders.getServer().getScheduler().scheduleSyncDelayedTask(booksWithoutBorders,
@ -143,22 +157,23 @@ public class PlayerEventListener implements Listener {
//Unknown author is ignored
fileName = oldBook.getTitle();
} else {
fileName = oldBook.getTitle() + BooksWithoutBorders.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[]{
getBookFolder() + fileName + ".yml",
getBookFolder() + fileName + ".txt",
getBookFolder() + cleanPlayerName + slash + fileName + ".yml",
getBookFolder() + cleanPlayerName + slash + fileName + ".txt"
publicFolderPath + fileName + ".yml",
publicFolderPath + fileName + ".txt",
playerFolderPath + fileName + ".yml",
playerFolderPath + fileName + ".txt"
};
for (String path : possiblePaths) {
File file = new File(path);
if (file.isFile()) {
return booksWithoutBorders.loadBook(player, fileName, "true", "player");
return BookLoader.loadBook(player, fileName, "true", "player");
}
}
return null;

View File

@ -1,7 +1,9 @@
package net.knarcraft.bookswithoutborders.listener;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
import net.knarcraft.bookswithoutborders.state.EncryptionStyle;
import net.knarcraft.bookswithoutborders.utility.BookLoader;
import net.knarcraft.bookswithoutborders.utility.EncryptionHelper;
import net.knarcraft.bookswithoutborders.utility.FileHelper;
import net.knarcraft.bookswithoutborders.utility.InputCleaningHelper;
@ -22,10 +24,13 @@ import org.bukkit.inventory.meta.BookMeta;
import java.io.File;
import static net.knarcraft.bookswithoutborders.BooksWithoutBordersSettings.getBookFolder;
import static net.knarcraft.bookswithoutborders.BooksWithoutBordersSettings.getSlash;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getBookFolder;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getSlash;
import static net.knarcraft.bookswithoutborders.utility.FileHelper.isBookListIndex;
/**
* A listener for relevant sign events such as clicking a decryption sign
*/
public class SignEventListener implements Listener {
private final String slash = getSlash();
@ -84,6 +89,9 @@ public class SignEventListener implements Listener {
return;
}
ItemStack heldItem = playerInventory.getItem(hand);
if (heldItem == null) {
return;
}
Material heldItemType = heldItem.getType();
if (event.getAction() == Action.RIGHT_CLICK_BLOCK && (Tag.SIGNS.isTagged(clickedBlockType) ||
@ -177,7 +185,8 @@ public class SignEventListener implements Listener {
*/
private void generateGiveSign(SignChangeEvent event, String[] lines, Player player) {
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);
return;
}
@ -234,11 +243,11 @@ public class SignEventListener implements Listener {
//Permission check
if (!player.hasPermission("bookswithoutborders.decrypt." + groupName) &&
!(BooksWithoutBorders.getAdminDecrypt() && player.hasPermission("bookswithoutborders.admin"))) {
!(BooksWithoutBordersConfig.getAdminDecrypt() && player.hasPermission("bookswithoutborders.admin"))) {
return;
}
String fileName = oldBook.getTitle() + BooksWithoutBorders.getTitleAuthorSeparator() + oldBook.getAuthor();
String fileName = oldBook.getTitle() + BooksWithoutBordersConfig.getTitleAuthorSeparator() + oldBook.getAuthor();
String encryptionFile = InputCleaningHelper.cleanString(groupName) + slash + fileName + ".yml";
@ -249,7 +258,7 @@ public class SignEventListener implements Listener {
return;
}
}
newBook = BooksWithoutBorders.getInstance().loadBook(player, fileName, "true", groupName, heldItem.getAmount());
newBook = BookLoader.loadBook(player, fileName, "true", groupName, heldItem.getAmount());
if (newBook == null) {
return;
@ -257,7 +266,7 @@ public class SignEventListener implements Listener {
player.getInventory().setItem(hand, newBook);
player.closeInventory();
player.sendMessage(ChatColor.GREEN + "Book auto-decrypted!");
BooksWithoutBorders.sendSuccessMessage(player, "Book auto-decrypted!");
}
/**
@ -304,13 +313,13 @@ public class SignEventListener implements Listener {
fileName += ChatColor.stripColor(thirdLine);
}
ItemStack newBook = BooksWithoutBorders.getInstance().loadBook(player, fileName, "true", "public");
ItemStack newBook = BookLoader.loadBook(player, fileName, "true", "public");
if (newBook != null) {
player.getInventory().addItem(newBook);
player.sendMessage(ChatColor.GREEN + "Received book!");
BooksWithoutBorders.sendSuccessMessage(player, "Received book!");
} else {
player.sendMessage(ChatColor.RED + "Book failed to load!");
BooksWithoutBorders.sendErrorMessage(player, "Book failed to load!");
}
}

View File

@ -4,8 +4,20 @@ package net.knarcraft.bookswithoutborders.state;
* This enum represents the different directories books can be saved in
*/
public enum BookDirectory {
/**
* The public directory
*/
PUBLIC,
/**
* A player directory
*/
PLAYER,
/**
* The encrypted directory
*/
ENCRYPTED;
/**
@ -25,4 +37,5 @@ public enum BookDirectory {
return null;
}
}
}

View File

@ -4,6 +4,7 @@ package net.knarcraft.bookswithoutborders.state;
* This enum represents the different available encryption styles
*/
public enum EncryptionStyle {
DNA("dna"),
SUBSTITUTION("substitution");
@ -27,4 +28,5 @@ public enum EncryptionStyle {
}
return SUBSTITUTION;
}
}

View File

@ -1,6 +1,13 @@
package net.knarcraft.bookswithoutborders.utility;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.inventory.meta.BookMeta;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* A class for formatting text to fit books
@ -8,7 +15,6 @@ import java.util.List;
public final class BookFormatter {
private BookFormatter() {
}
/**
@ -94,4 +100,35 @@ public final class BookFormatter {
}
}
/**
* Formats every page in the given book meta by converting color and formatting codes
*
* @param bookMeta <p>The book meta to change</p>
* @return <p>The changed book meta</p>
*/
public static BookMeta formatPages(BookMeta bookMeta) {
List<String> formattedPages = new ArrayList<>(Objects.requireNonNull(bookMeta).getPageCount());
for (String page : bookMeta.getPages()) {
formattedPages.add(BookFormatter.translateAllColorCodes(page));
}
bookMeta.setPages(formattedPages);
return bookMeta;
}
/**
* Translates all found color codes to formatting in a string
*
* @param message <p>The string to search for color codes</p>
* @return <p>The message with color codes translated</p>
*/
private static String translateAllColorCodes(String message) {
message = ChatColor.translateAlternateColorCodes('&', message);
Pattern pattern = Pattern.compile("(#[a-fA-F0-9]{6})");
Matcher matcher = pattern.matcher(message);
while (matcher.find()) {
message = message.replace(matcher.group(), "" + ChatColor.of(matcher.group()));
}
return message;
}
}

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

@ -0,0 +1,136 @@
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.Material;
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.ArrayList;
import java.util.List;
/**
* A helper class for loading books from files
*/
public final class BookLoader {
private BookLoader() {
}
/**
* Loads the given book
*
* @param sender <p>The command sender trying to load the book</p>
* @param fileName <p>The index or file name of the book to load</p>
* @param isSigned <p>Whether to load the book as signed, and not unsigned</p>
* @param directory <p>The directory to save the book in</p>
* @return <p>The loaded book</p>
*/
public static ItemStack loadBook(CommandSender sender, String fileName, String isSigned, String directory) {
return loadBook(sender, fileName, isSigned, directory, 1);
}
/**
* Loads the given book
*
* @param sender <p>The command sender trying to load the book</p>
* @param fileName <p>The index or file name of the book to load</p>
* @param isSigned <p>Whether to load the book as signed, and not unsigned</p>
* @param directory <p>The directory to save the book in</p>
* @param numCopies <p>The number of copies to load</p>
* @return <p>The loaded book</p>
*/
public static ItemStack loadBook(CommandSender sender, String fileName, String isSigned, String directory, int numCopies) {
BookDirectory bookDirectory = BookDirectory.getFromString(directory);
//Find the filename if a book index is given
try {
int bookIndex = Integer.parseInt(fileName);
List<String> availableFiles = BooksWithoutBorders.getAvailableBooks(sender, bookDirectory == BookDirectory.PUBLIC);
if (bookIndex <= availableFiles.size()) {
fileName = availableFiles.get(Integer.parseInt(fileName) - 1);
}
} catch (NumberFormatException ignored) {
}
//Get the full path of the book to load
File file = getFullPath(sender, fileName, bookDirectory, directory);
if (file == null) {
return null;
}
//Make sure the player can pay for the book
if (BooksWithoutBordersConfig.booksHavePrice() &&
!sender.hasPermission("bookswithoutborders.bypassBookPrice") &&
(bookDirectory == BookDirectory.PUBLIC || bookDirectory == BookDirectory.PLAYER) &&
EconomyHelper.cannotPayForBookPrinting((Player) sender, numCopies)) {
return null;
}
//Generate a new empty book
ItemStack book;
BookMeta bookMetadata = (BookMeta) BooksWithoutBorders.getItemFactory().getItemMeta(Material.WRITTEN_BOOK);
if (isSigned.equalsIgnoreCase("true")) {
book = new ItemStack(Material.WRITTEN_BOOK);
} else {
book = new ItemStack(Material.WRITABLE_BOOK);
}
//Load the book from the given file
BookToFromTextHelper.bookFromFile(file, bookMetadata);
if (bookMetadata == null) {
BooksWithoutBorders.sendErrorMessage(sender, "File was blank!!");
return null;
}
//Remove "encrypted" from the book lore
if (bookDirectory == BookDirectory.ENCRYPTED && bookMetadata.hasLore()) {
List<String> oldLore = bookMetadata.getLore();
if (oldLore != null) {
List<String> newLore = new ArrayList<>(oldLore);
newLore.remove(0);
bookMetadata.setLore(newLore);
}
}
//Set the metadata and amount to the new book
book.setItemMeta(bookMetadata);
//Increase book generation if enabled
BookHelper.increaseGeneration(book);
book.setAmount(numCopies);
return book;
}
/**
* Gets a File pointing to the wanted book
*
* @param sender <p>The sender to send errors to</p>
* @param fileName <p>The name of the book file</p>
* @param bookDirectory <p>The book directory the file resides in</p>
* @param directory <p>The relative directory given</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) {
File file;
String slash = BooksWithoutBordersConfig.getSlash();
String bookFolder = BooksWithoutBordersConfig.getBookFolder();
if (bookDirectory == BookDirectory.ENCRYPTED) {
file = FileHelper.getBookFile(bookFolder + "Encrypted" + slash + directory + slash + fileName);
} else {
file = FileHelper.getBookFile(BookHelper.getBookDirectoryPathString(bookDirectory, sender) + fileName);
}
if (file == null || !file.isFile()) {
BooksWithoutBorders.sendErrorMessage(sender, "Incorrect file name!");
return null;
} else {
return file;
}
}
}

View File

@ -1,6 +1,6 @@
package net.knarcraft.bookswithoutborders.utility;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.inventory.meta.BookMeta;
@ -14,6 +14,7 @@ import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import static net.knarcraft.bookswithoutborders.utility.BookHelper.authorFromUUID;
import static net.knarcraft.bookswithoutborders.utility.InputCleaningHelper.fixName;
/**
@ -41,6 +42,11 @@ public final class BookToFromTextHelper {
if (bookMetadata.hasAuthor()) {
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()) {
bookYml.set("Pages", bookMetadata.getPages());
}
@ -81,8 +87,14 @@ public final class BookToFromTextHelper {
PrintWriter printWriter = new PrintWriter(fileWriter);
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
printWriter.println("[Book]");
printWriter.println("[Book]" + generationString);
for (String page : pages) {
printWriter.println(page);
}
@ -100,8 +112,9 @@ public final class BookToFromTextHelper {
try {
FileConfiguration bookYml = YamlConfiguration.loadConfiguration(file);
bookMetadata.setGeneration(BookMeta.Generation.valueOf(bookYml.getString("Generation", "ORIGINAL")));
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.setLore(bookYml.getStringList("Lore"));
} catch (IllegalArgumentException e) {
@ -121,15 +134,18 @@ public final class BookToFromTextHelper {
private static BookMeta bookFromTXT(String fileName, File file, BookMeta bookMetadata) {
String author;
String title;
String titleAuthorSeparator = BooksWithoutBorders.getTitleAuthorSeparator();
String titleAuthorSeparator = BooksWithoutBordersConfig.getTitleAuthorSeparator();
//Remove .txt extension
fileName = fileName.substring(0, fileName.length() - 4);
//Get title and author from the file name
if (fileName.contains(titleAuthorSeparator)) {
author = fileName.substring(fileName.indexOf(titleAuthorSeparator) + 1, fileName.length() - 4);
title = fileName.substring(0, fileName.indexOf(titleAuthorSeparator));
String[] titleAuthor = fileName.split(titleAuthorSeparator);
title = titleAuthor[0];
author = titleAuthor[1];
} else {
author = "Unknown";
title = fileName.substring(0, fileName.length() - 4);
title = fileName;
}
//Replace underscores with spaces
@ -144,11 +160,17 @@ public final class BookToFromTextHelper {
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
List<String> pages = new ArrayList<>(InputCleaningHelper.cleanList(rawPages));
//Update the metadata of the book with its new values
bookMetadata.setAuthor(author);
bookMetadata.setAuthor(authorFromUUID(author));
bookMetadata.setTitle(title);
bookMetadata.setPages(pages);
@ -172,8 +194,11 @@ public final class BookToFromTextHelper {
bufferedReader.close();
return null;
}
if (firstLine.equalsIgnoreCase("[Book]")) {
if (firstLine.toLowerCase().startsWith("[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;
do {
readLine = bufferedReader.readLine();

View File

@ -1,6 +1,7 @@
package net.knarcraft.bookswithoutborders.utility;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
import net.milkbowl.vault.economy.Economy;
import org.bukkit.Material;
import org.bukkit.Server;
@ -21,7 +22,6 @@ public final class EconomyHelper {
private static Economy economy;
private EconomyHelper() {
}
/**
@ -67,8 +67,8 @@ public final class EconomyHelper {
public static boolean cannotPayForBookPrinting(Player player, int numCopies) {
//BookPriceQuantity: How many items are required to pay for each book
//BookPriceType: Which item is used to pay for the books. AIR = use economy
Material bookCurrency = BooksWithoutBorders.getBookPriceType();
double cost = BooksWithoutBorders.getBookPriceQuantity() * numCopies;
Material bookCurrency = BooksWithoutBordersConfig.getBookPriceType();
double cost = BooksWithoutBordersConfig.getBookPriceQuantity() * numCopies;
int itemCost = (int) cost;
if (bookCurrency == Material.AIR) {
@ -116,7 +116,7 @@ public final class EconomyHelper {
int clearedAmount = 0;
while (clearedAmount < itemCost) {
int firstItemIndex = playerInventory.first(BooksWithoutBorders.getBookPriceType());
int firstItemIndex = playerInventory.first(BooksWithoutBordersConfig.getBookPriceType());
ItemStack firstItem = playerInventory.getItem(firstItemIndex);
if (Objects.requireNonNull(firstItem).getAmount() <= itemCost - clearedAmount) {

View File

@ -1,6 +1,7 @@
package net.knarcraft.bookswithoutborders.utility;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
import net.knarcraft.bookswithoutborders.encryption.GenenCrypt;
import net.knarcraft.bookswithoutborders.encryption.SubstitutionCipher;
import net.knarcraft.bookswithoutborders.state.EncryptionStyle;
@ -16,8 +17,8 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import static net.knarcraft.bookswithoutborders.BooksWithoutBordersSettings.getBookFolder;
import static net.knarcraft.bookswithoutborders.BooksWithoutBordersSettings.getSlash;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getBookFolder;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getSlash;
import static net.knarcraft.bookswithoutborders.utility.InputCleaningHelper.cleanString;
import static net.knarcraft.bookswithoutborders.utility.InputCleaningHelper.fixName;
@ -27,7 +28,6 @@ import static net.knarcraft.bookswithoutborders.utility.InputCleaningHelper.fixN
public final class EncryptionHelper {
private EncryptionHelper() {
}
/**
@ -198,11 +198,8 @@ public final class EncryptionHelper {
return null;
}
String fileName = (!bookMetadata.hasTitle()) ? "Untitled," + player.getName() : bookMetadata.getTitle() +
BooksWithoutBorders.getTitleAuthorSeparator() + bookMetadata.getAuthor();
fileName = "[" + key + "]" + fileName;
fileName = cleanString(fileName);
fileName = fixName(fileName, false);
String fileName = "[" + key + "]" + BookHelper.getBookFile(bookMetadata, player, true);
fileName = fixName(cleanString(fileName), false);
File file = new File(path + fileName + ".yml");
if (!file.isFile()) {
@ -263,12 +260,8 @@ public final class EncryptionHelper {
return null;
}
}
//Creates file
String fileName = (!bookMetadata.hasTitle()) ? "Untitled," + player.getName() :
bookMetadata.getTitle() + BooksWithoutBorders.getTitleAuthorSeparator() + bookMetadata.getAuthor();
fileName = cleanString(fileName);
fileName = fixName(fileName, false);
//Generate file name
String fileName = BookHelper.getBookFile(bookMetadata, player, true);
List<String> newLore = new ArrayList<>();
newLore.add(ChatColor.GRAY + "[" + groupName + " encrypted]");
@ -281,7 +274,8 @@ public final class EncryptionHelper {
bookMetadata.setLore(newLore);
//Save file
File file = (BooksWithoutBorders.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()) {
try {
BookToFromTextHelper.bookToYml(path, fileName, bookMetadata);
@ -305,15 +299,13 @@ public final class EncryptionHelper {
*/
private static Boolean saveEncryptedBook(Player player, BookMeta bookMetaData, String key) {
String path = getBookFolder() + "Encrypted" + getSlash();
String fileName = (!bookMetaData.hasTitle()) ? "Untitled," + player.getName() :
bookMetaData.getTitle() + BooksWithoutBorders.getTitleAuthorSeparator() + bookMetaData.getAuthor();
fileName = "[" + key + "]" + fileName;
fileName = cleanString(fileName);
fileName = fixName(fileName, false);
String fileName = "[" + key + "]" + BookHelper.getBookFile(bookMetaData, player, true);
fileName = fixName(cleanString(fileName), false);
//cancels saving if file is already encrypted
File file = (BooksWithoutBorders.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()) {
return true;
}

View File

@ -1,18 +1,22 @@
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.ChatColor;
import org.bukkit.command.CommandSender;
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.regex.Pattern;
import static net.knarcraft.bookswithoutborders.BooksWithoutBordersSettings.getBookFolder;
import static net.knarcraft.bookswithoutborders.BooksWithoutBordersSettings.getSlash;
import static net.knarcraft.bookswithoutborders.utility.InputCleaningHelper.cleanString;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getBookFolder;
/**
* Helper class for dealing with files
@ -20,7 +24,6 @@ import static net.knarcraft.bookswithoutborders.utility.InputCleaningHelper.clea
public final class FileHelper {
private FileHelper() {
}
/**
@ -95,11 +98,9 @@ public final class FileHelper {
* @return <p>A list of available files</p>
*/
public static List<String> listFiles(CommandSender sender, Boolean listPublic) {
File file;
if (listPublic) {
file = new File(getBookFolder());
} else {
file = new File(getBookFolder() + cleanString(sender.getName()) + getSlash());
File file = BookHelper.getBookDirectoryPath(listPublic ? BookDirectory.PUBLIC : BookDirectory.PLAYER, sender);
if (file == null) {
return new ArrayList<>();
}
return FileHelper.listFiles(sender, file);
}
@ -123,6 +124,9 @@ public final class FileHelper {
*/
public static void printFiles(CommandSender sender, List<String> fileList) {
BooksWithoutBorders.sendSuccessMessage(sender, "Available Books:");
if (fileList == null) {
return;
}
int listSize = fileList.size();
for (int fileIndex = 0; fileIndex < listSize; fileIndex++) {
sender.sendMessage(ChatColor.GRAY + "[" + (fileIndex + 1) + "] " + fileList.get(fileIndex));
@ -146,8 +150,20 @@ public final class FileHelper {
}
for (File foundFile : existingFiles) {
if (foundFile.isFile()) {
fileList.add(foundFile.getName());
if (!foundFile.isFile()) {
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);
}
}
@ -171,4 +187,15 @@ public final class FileHelper {
return foundDuplicates;
}
/**
* Gets a buffered reader given an input stream
*
* @param inputStream <p>The input stream to read</p>
* @return <p>A buffered reader reading the input stream</p>
*/
public static BufferedReader getBufferedReaderFromInputStream(InputStream inputStream) {
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
return new BufferedReader(inputStreamReader);
}
}

View File

@ -9,7 +9,6 @@ import java.util.List;
public final class InputCleaningHelper {
private InputCleaningHelper() {
}
/**

View File

@ -14,7 +14,6 @@ import org.bukkit.inventory.meta.BookMeta;
public final class InventoryHelper {
private InventoryHelper() {
}
/**
@ -97,7 +96,7 @@ public final class InventoryHelper {
if (state == BookHoldingState.SIGNED_BOTH_HANDS ||
state == BookHoldingState.UNSIGNED_BOTH_HANDS ||
state == BookHoldingState.NONE) {
return null;
return ItemSlot.NONE;
}
if (handMatters && typeMatters) {
if (mainHand && mainHandItem.getType() == requiredMaterial) {

View File

@ -9,7 +9,40 @@ import java.util.List;
public final class TabCompletionHelper {
private TabCompletionHelper() {
}
/**
* Finds tab complete values that contain the typed text
*
* @param values <p>The values to filter</p>
* @param typedText <p>The text the player has started typing</p>
* @return <p>The given string values that contain the player's typed text</p>
*/
public static List<String> filterMatchingContains(List<String> values, String typedText) {
List<String> configValues = new ArrayList<>();
for (String value : values) {
if (value.toLowerCase().contains(typedText.toLowerCase())) {
configValues.add(value);
}
}
return configValues;
}
/**
* Finds tab complete values that match the start of the typed text
*
* @param values <p>The values to filter</p>
* @param typedText <p>The text the player has started typing</p>
* @return <p>The given string values that start with the player's typed text</p>
*/
public static List<String> filterMatchingStartsWith(List<String> values, String typedText) {
List<String> configValues = new ArrayList<>();
for (String value : values) {
if (value.toLowerCase().startsWith(typedText.toLowerCase())) {
configValues.add(value);
}
}
return configValues;
}
/**

View File

@ -0,0 +1,92 @@
package net.knarcraft.bookswithoutborders.utility;
import org.bukkit.plugin.Plugin;
import org.bukkit.scheduler.BukkitScheduler;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.logging.Level;
/**
* The update checker is responsible for looking for new updates
*/
public final class UpdateChecker {
private final static String updateNotice = "A new update is available: %s (You are still on %s)";
private UpdateChecker() {
}
/**
* Checks if there's a new update available, and alerts the user if necessary
*/
public static void checkForUpdate(Plugin plugin, String apiResourceURL, Supplier<String> getVersionMethod,
Consumer<String> setVersionMethod) {
BukkitScheduler scheduler = plugin.getServer().getScheduler();
scheduler.runTaskAsynchronously(plugin, () -> UpdateChecker.queryAPI(plugin, apiResourceURL, getVersionMethod,
setVersionMethod));
}
/**
* Queries the spigot API to check for a newer version, and informs the user
*/
private static void queryAPI(Plugin plugin, String APIResourceURL, Supplier<String> getVersionMethod,
Consumer<String> setVersionMethod) {
try {
InputStream inputStream = new URL(APIResourceURL).openStream();
BufferedReader reader = FileHelper.getBufferedReaderFromInputStream(inputStream);
//There should only be one line of output
String newVersion = reader.readLine();
reader.close();
String oldVersion = getVersionMethod.get();
//If there is a newer version, notify the user
if (isVersionHigher(oldVersion, newVersion)) {
plugin.getLogger().log(Level.INFO, getUpdateAvailableString(newVersion, oldVersion));
if (setVersionMethod != null) {
setVersionMethod.accept(newVersion);
}
}
} catch (IOException e) {
plugin.getLogger().log(Level.WARNING, "Unable to get newest version.");
}
}
/**
* Gets the string to display to a user to alert about a new update
*
* @param newVersion <p>The new available plugin version</p>
* @param oldVersion <p>The old (current) plugin version</p>
* @return <p>The string to display</p>
*/
public static String getUpdateAvailableString(String newVersion, String oldVersion) {
return String.format(updateNotice, newVersion, oldVersion);
}
/**
* Decides whether one version number is higher than another
*
* @param oldVersion <p>The old version to check</p>
* @param newVersion <p>The new version to check</p>
* @return <p>True if the new version is higher than the old one</p>
*/
public static boolean isVersionHigher(String oldVersion, String newVersion) {
String[] oldVersionParts = oldVersion.split("\\.");
String[] newVersionParts = newVersion.split("\\.");
int versionLength = Math.max(oldVersionParts.length, newVersionParts.length);
for (int i = 0; i < versionLength; i++) {
int oldVersionNumber = oldVersionParts.length > i ? Integer.parseInt(oldVersionParts[i]) : 0;
int newVersionNumber = newVersionParts.length > i ? Integer.parseInt(newVersionParts[i]) : 0;
if (newVersionNumber != oldVersionNumber) {
return newVersionNumber > oldVersionNumber;
}
}
return false;
}
}

View File

@ -0,0 +1,33 @@
Options:
# Whether to use YAML for saved books instead of just storing them as text
Save_Books_in_Yaml_Format: true
# The maximum number of duplicates of a saved book allowed
Max_Number_of_Duplicates: 5
# The separator used to separate the book title and the book author
Title-Author_Separator: ","
# The separator used to denote a new line in the book/item lore
Lore_line_separator: "~"
# A list of books given to new players the first time they join the server
Books_for_new_players: [ ]
# An optional message displayed to new players the first time they join the server
Message_for_new_players: ""
# Price settings for book copying
Price_to_create_book:
# The item type used as currency for copying books. Use "Economy" to use money instead of items
Item_type: ""
# The quantity of currency required to pay for each book produced
Required_quantity: 0
# Whether any admin can decrypt any book regardless of the group it was encrypted for
Admin_Auto_Decrypt: false
# Whether to only allow the author of a book to create copies
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
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

@ -1,18 +1,26 @@
name: BooksWithoutBorders
version: '${project.version}'
main: net.knarcraft.bookswithoutborders.BooksWithoutBorders
api-version: 1.17
api-version: 1.18
prefix: Books Without Borders
authors: [ EpicKnarvik97, AkiraAkiba ]
description: A continuation of the original Books Without Borders
softdepend: [ Vault ]
website: ????
website: https://www.spigotmc.org/resources/books-without-borders-updated.96069/
dev-url: https://git.knarcraft.net/EpicKnarvik97/Books-Without-Borders
commands:
bookswithoutborders:
description: Lists Books Without Borders's commands and uses.
aliases: [ bwb ]
usage: /<command>
decryptbook:
description: Decrypts the book the player is holding. "key" is required and MUST be IDENTICAL to the key used to encrypt held book
usage: /<command> <key>
permission: bookswithoutborders.decrypt
formatbook:
description: Replaces color/formatting codes in a written book with formatted text
usage: /<command>
permission: bookswithoutborders.format
givebook:
description: Gives the selected player a book from your personal directory
usage: /<command> <file name or number> <playername> [# of copies (num)] [signed (true/false)]
@ -21,10 +29,6 @@ commands:
description: Same as givebook, but uses books from the public directory
usage: /<command> <file name or number> <playername> [# of copies (num)] [signed (true/false)]
permission: bookswithoutborders.givepublic
decryptbook:
description: Decrypts the book the player is holding. "key" is required and MUST be IDENTICAL to the key used to encrypt held book
usage: /<command> <key>
permission: bookswithoutborders.decrypt
groupencryptbook:
description: Encrypts book so that only players with the bookswithoutborders.decrypt.<group name> permission may decrypt the book by holding and left clicking the book
usage: /<command> <group name> <key> [encryption style]
@ -49,12 +53,16 @@ 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 ""
usage: /<command> <key> [encryption style]
permission: bookswithoutborders.encrypt
setbookgeneration:
description: Sets the generation of the held book
usage: /<command> <generation>
permission: bookswithoutborders.setgeneration
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>
permission: bookswithoutborders.setbookprice
setlore:
description: Sets the lore of the item the player is holding. Insert the lore_line_separator character to force a new line ("~" by default).
description: Sets the lore of the item the player is holding. Insert the lore_line_separator character to force a new line ("~" by default)
usage: /<command> <new lore>
permission: bookswithoutborders.setlore
savepublicbook:
@ -74,7 +82,7 @@ commands:
usage: /<command> <title>
permission: bookswithoutborders.settitle
loadbook:
description: 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 will be signed, if false it will be unsigned
description: 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 will be signed, if false it will be unsigned
usage: /<command> <file name or number> [# of copies] [signed (true/false)]
permission: bookswithoutborders.load
loadpublicbook:
@ -84,7 +92,7 @@ commands:
reload:
description: Reloads BwB's configuration file
usage: /<command>
permission: bookswithoutborders.admin
permission: bookswithoutborders.reload
permissions:
bookswithoutborders.*:
description: Grants all permissions
@ -96,28 +104,40 @@ permissions:
default: op
children:
bookswithoutborders.use: true
bookswithoutborders.unsign: true
bookswithoutborders.alterbooks: true
bookswithoutborders.copy: true
bookswithoutborders.loadpublic: true
bookswithoutborders.savepublic: true
bookswithoutborders.encrypt: true
bookswithoutborders.decrypt: true
bookswithoutborders.groupencrypt: true
bookswithoutborders.signs: true
bookswithoutborders.give: true
bookswithoutborders.givepublic: true
bookswithoutborders.settitle: true
bookswithoutborders.setauthor: true
bookswithoutborders.setlore: true
bookswithoutborders.bypassauthoronlycopy: true
bookswithoutborders.bypassauthoronlyunsign: true
bookswithoutborders.bypassauthoronlysave: true
bookswithoutborders.bypassbookprice: true
bookswithoutborders.groupencrypt: true
bookswithoutborders.setbookprice: true
bookswithoutborders.reload: true
bookswithoutborders.setgeneration: true
bookswithoutborders.use:
description: Allows player to use commands and to save/load/delete in their personal directory
description: Allows player to use commands to save/load/delete in their personal directory
children:
bookswithoutborders.save: true
bookswithoutborders.load: true
bookswithoutborders.delete: true
bookswithoutborders.alterbooks:
description: Allows player to change books' data such as lore/title/author/formatting and unsigning books
children:
bookswithoutborders.unsign: true
bookswithoutborders.settitle: true
bookswithoutborders.setauthor: true
bookswithoutborders.setlore: true
bookswithoutborders.format: true
bookswithoutborders.setgeneration: true
bookswithoutborders.format:
description: Allows a player to format a book
bookswithoutborders.save:
description: Allows player to save books to their personal directory
bookswithoutborders.load:
@ -129,13 +149,13 @@ permissions:
bookswithoutborders.copy:
description: Allows player to use copy command
bookswithoutborders.loadpublic:
description: Allows player to load in the public directory
description: Allows player to load from the public directory
bookswithoutborders.savepublic:
description: Allows player to save in the public directory
description: Allows player to save to the public directory
bookswithoutborders.encrypt:
description: Allows player to encrypt books
bookswithoutborders.groupencrypt:
description: Allows player to set group based encryption
description: Allows player to use group-based encryption
bookswithoutborders.decrypt:
description: Allows player to decrypt books
bookswithoutborders.signs:
@ -152,7 +172,15 @@ permissions:
description: Allows player to set the lore of the currently held item
bookswithoutborders.bypassauthoronlycopy:
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:
description: Allows player to ignore Price_to_create_book config setting
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)

View File

@ -1,9 +1,9 @@
package net.knarcraft.bookswithoutborders.encryption;
import junit.framework.Assert;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static junit.framework.Assert.assertEquals;
public class AESTest {
@ -15,7 +15,7 @@ public class AESTest {
AES aes = new AES(AES.generateIV(), AES.generateIV());
String encrypted = aes.encryptDecryptText(plainText, password, true);
assertFalse(encrypted.equals(plainText));
Assert.assertNotSame(encrypted, plainText);
String decrypted = aes.encryptDecryptText(encrypted, password, false);
assertEquals(plainText, decrypted);
}