Compare commits

..

74 Commits
1.0 ... master

Author SHA1 Message Date
0b701ddef1 Bumps version for development
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2024-09-07 01:44:40 +02:00
0fe9ccb590 Bumps version for release
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2024-09-07 01:34:42 +02:00
1a7236cebd Updates KnarLib and fixes pom 2024-09-07 01:33:38 +02:00
e482e494f8 Fixes some warnings 2024-09-07 01:27:00 +02:00
0f76c8f869 Changes unsign behavior to work on 1.21 2024-09-07 01:26:52 +02:00
6b4e87d33a Bumps Spigot version 2024-09-07 01:16:49 +02:00
40b0fa0baf Updates dependencies
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2024-05-03 15:53:55 +02:00
31bb26b755 Makes it clear that use includes peekbookshelf
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2023-06-24 12:40:55 +02:00
0e134fcdab Bumps version back to dev version
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2023-06-24 12:34:18 +02:00
f5ba4db5ac Bumps version for release
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2023-06-24 12:15:22 +02:00
29d5247c9b Bumps KnarLib version
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2023-06-24 12:09:31 +02:00
c389c6fbcb Minor changes
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
Changes bookshelf peeking to require sneaking
Makes sure plain books are displayed in the book list
2023-06-24 12:03:37 +02:00
1158820f97 Fixes some issues
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
Fixes the API version being read incorrectly by Paper
Fixes the check for missing title or author for a book in a bookshelf
2023-06-23 18:10:45 +02:00
f9908db88f 1.20 update
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
Adds an optional feature which displays the contents of a bookshelf when left-clicked. Sneaking must be used to destroy the bookshelf when enabled.
Updates depreciated sign code.
Bumps the API version to 1.20
Builds against 1.20
2023-06-23 17:57:54 +02:00
738dbe6e30 Updates version to 1.3.4-SNAPSHOT
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2022-11-26 17:20:41 +01:00
3838750f64 Adds Jenkinsfile
Some checks failed
EpicKnarvik97/Books-Without-Borders/pipeline/head There was a failure building this commit
2022-11-26 15:54:17 +01:00
16954f22e3 Merge branch 'master' into knarlib 2022-11-26 15:52:17 +01:00
6359af02e6 Fixes table formatting 2022-11-14 00:49:58 +00:00
6ae15acc72 Bumps version to 1.3.3 2022-11-08 20:37:06 +01:00
2c80e4117e Lists real file names, in addition to converted file names 2022-11-08 20:36:17 +01:00
6d539a14df Fixes the files provided in the tab-completion not translating to actual files 2022-11-08 20:34:53 +01:00
51371736e4 Removes KnarLib.setPlugin call 2022-11-07 22:24:33 +01:00
2383dfd2ef Renames FileHelper to BookFileHelper
Makes sure books are saved and read as UTF-8
2022-11-07 09:23:25 +01:00
04d4bc8303 Uses KnarLib where possible 2022-11-07 01:43:38 +01:00
184f78d935 Adds a missing / to the README 2022-11-05 12:35:27 +01:00
95b0b42fd1 Bumps version to 1.3.2 2022-11-04 18:47:31 +01:00
73eb903517 Implements RGB support for setLore and setName 2022-11-04 18:39:58 +01:00
b577b1ff75 Bumps version to 1.3.1 2022-11-04 16:19:27 +01:00
c27756f7dc Clears author, generation and title data just in case 2022-11-04 16:19:15 +01:00
90bab4a148 Fixes various bugs in the new features
Fixes the clearBook command not being aborted when no book is held.
Adds missing clearBook success message
Fixes a bug where too many empty books would be taken.
Adds some missing null checks when looking for empty books.
2022-11-04 16:06:27 +01:00
f005b8f8e5 Adds clearBook command #7 and improves README 2022-11-04 13:01:56 +01:00
0fdfd81579 Prevents WIP books from being lost
This change adds a workaround for writable books used for payment. Only books that don't contain any text can be used for payment, thus preventing any books with important text from being lost.
2022-11-04 03:37:38 +01:00
6820b71dde Updates spigot version to 1.19.2 2022-11-04 03:07:32 +01:00
28b7849ff5 Tries to fix #9
Sets the amount of books to the same amount as the old book
2022-11-04 03:05:30 +01:00
e893684dec Prevents consumption of dye when clicking plugin signs 2022-08-20 14:54:40 +02:00
f1a8db4f5e Merge branch 'master' of https://git.knarcraft.net/EpicKnarvik97/Books-Without-Borders
 Conflicts:
	README.md
2022-08-20 14:16:12 +02:00
47fabe5806 Merge pull request 'Excapes <' (#8) from epicknarvik97-patch-1 into master
Reviewed-on: #8
2022-08-20 14:13:31 +02:00
ec6def15f9 Excapes < 2022-08-20 14:12:20 +02:00
13cbe883ed Adds missing mention of generation for alterbooks permission node 2022-08-14 11:36:46 +02:00
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
56 changed files with 2457 additions and 906 deletions

33
Jenkinsfile vendored Normal file
View File

@ -0,0 +1,33 @@
pipeline {
agent any
tools {
jdk 'JDK17'
}
stages {
stage('Build') {
steps {
echo 'Building...'
sh 'mvn clean & mvn validate & mvn compile'
}
}
stage('Test') {
steps {
echo 'Testing...'
sh 'mvn test'
}
}
stage('Verify') {
steps {
echo 'Verifying...'
sh 'mvn verify -Dmaven.test.skip=true'
}
}
stage('Deploy') {
steps {
echo 'Deploying...'
sh 'mvn deploy -Dmaven.install.skip=true -Dmaven.test.skip=true'
archiveArtifacts artifacts: '**/target/*.jar', fingerprint: true
}
}
}
}

134
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,131 @@ 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
- Clear a book, and start anew with /clearbook
- 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
- Optionally, hit a bookshelf while sneaking to display the contained books
#### 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.
| Command | Arguments | Description |
|----------------------|----------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| /bookswithoutborders | None | 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 | None | 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 | None | 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 of the held book (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 | None | Un-signs the book the player is holding |
| /clearbook | None | Removes all text from the held un-signed book |
### Permissions:
| Node | Description |
|--------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------|
| bookswithoutborders.* | Grants all permissions |
| bookswithoutborders.admin | Grants all permissions |
| bookswithoutborders.use | bookswithoutborders.use - Allows player to use commands to save/load/delete in their personal directory, and peeking at bookshelves if enabled |
| bookswithoutborders.alterbooks | Allows player to change books' data such as lore/title/author/generation/formatting and un-signing 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) |
| bookswithoutborders.clear | Allows player to clear the contents of the held writable book |
| bookswithoutborders.peekbookshelf | Allows player to left-click a bookshelf to see the contents of the shelf |
### 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:
| Option | Description |
|----------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 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. |
| Enable_Book_Peeking | Whether to enable hitting a chiseled bookshelf while sneaking to see the shelf's contents. |

64
pom.xml
View File

@ -6,7 +6,7 @@
<groupId>net.knarcraft</groupId>
<artifactId>BooksWithoutBorders</artifactId>
<version>1.0</version>
<version>1.3.6-SNAPSHOT</version>
<packaging>jar</packaging>
<licenses>
@ -38,7 +38,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<version>3.3.0</version>
<executions>
<execution>
<phase>package</phase>
@ -47,6 +47,30 @@
</goals>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<relocations>
<relocation>
<pattern>net.knarcraft.knarlib</pattern>
<shadedPattern>net.knarcraft.bookswithoutborders.lib.knarlib</shadedPattern>
</relocation>
<relocation>
<pattern>org.jetbrains.annotations</pattern>
<shadedPattern>net.knarcraft.bookswithoutborders.lib.annotations</shadedPattern>
</relocation>
</relocations>
<filters>
<filter>
<artifact>net.knarcraft:knarlib</artifact>
<includes>
<include>net/knarcraft/knarlib/**</include>
</includes>
</filter>
<filter>
<artifact>org.jetbrains:annotations</artifact>
<includes>
<include>org/jetbrains/annotations/**</include>
</includes>
</filter>
</filters>
</configuration>
</execution>
</executions>
@ -61,6 +85,10 @@
</build>
<repositories>
<repository>
<id>knarcraft-repo</id>
<url>https://git.knarcraft.net/api/packages/EpicKnarvik97/maven</url>
</repository>
<repository>
<id>spigotmc-repo</id>
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
@ -70,19 +98,47 @@
<url>https://jitpack.io</url>
</repository>
</repositories>
<distributionManagement>
<repository>
<id>knarcraft-repo</id>
<url>https://git.knarcraft.net/api/packages/EpicKnarvik97/maven</url>
</repository>
<snapshotRepository>
<id>knarcraft-repo</id>
<url>https://git.knarcraft.net/api/packages/EpicKnarvik97/maven</url>
</snapshotRepository>
</distributionManagement>
<dependencies>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.17.1-R0.1-SNAPSHOT</version>
<version>1.21-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.github.MilkBowl</groupId>
<artifactId>VaultAPI</artifactId>
<version>1.7</version>
<version>1.7.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>net.knarcraft</groupId>
<artifactId>knarlib</artifactId>
<version>1.2.7</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
<version>24.0.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -1,11 +1,13 @@
package net.knarcraft.bookswithoutborders;
import net.knarcraft.bookswithoutborders.command.CommandBooksWithoutBorders;
import net.knarcraft.bookswithoutborders.command.CommandClear;
import net.knarcraft.bookswithoutborders.command.CommandCopy;
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 +18,25 @@ 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.BookshelfListener;
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.BookFileHelper;
import net.knarcraft.knarlib.util.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 +45,21 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.logging.Level;
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 +83,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)) {
List<String> newFiles = FileHelper.listFiles(sender, false);
playerBooksList.put(senderName, newFiles);
} else if (sender instanceof Player player) {
UUID playerUUID = player.getUniqueId();
if (!playerBooksList.containsKey(playerUUID)) {
List<String> newFiles = BookFileHelper.listFiles(sender, false);
playerBooksList.put(playerUUID, newFiles);
}
return playerBooksList.get(senderName);
return playerBooksList.get(playerUUID);
} else {
return new ArrayList<>();
}
}
@ -105,147 +102,45 @@ public class BooksWithoutBorders extends JavaPlugin {
* @param updatePublic <p>Whether to update public books</p>
*/
public static void updateBooks(CommandSender sender, boolean updatePublic) {
List<String> newFiles = FileHelper.listFiles(sender, updatePublic);
List<String> newFiles = BookFileHelper.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);
publicBooksList = FileHelper.listFiles(consoleSender, true);
BooksWithoutBordersConfig.initialize(this);
publicBooksList = BookFileHelper.listFiles(consoleSender, true);
PluginManager pluginManager = this.getServer().getPluginManager();
if (getSlash() != null && initialize()) {
pluginManager.registerEvents(new PlayerEventListener(), this);
pluginManager.registerEvents(new SignEventListener(), this);
pluginManager.registerEvents(new BookEventListener(), this);
pluginManager.registerEvents(new BookshelfListener(), 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 +175,9 @@ 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());
registerCommand("clearBook", new CommandClear());
}
/**
@ -299,6 +197,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 +212,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
*
@ -337,8 +245,8 @@ public class BooksWithoutBorders extends JavaPlugin {
sendErrorMessage(consoleSender, "Saving failed! Aborting...");
return false;
}
} catch (Exception e) {
e.printStackTrace();
} catch (Exception exception) {
BooksWithoutBorders.getInstance().getLogger().log(Level.SEVERE, "Unable to create necessary folders");
return false;
}
}
@ -348,223 +256,14 @@ public class BooksWithoutBorders extends JavaPlugin {
sendErrorMessage(consoleSender, "Saving failed! Aborting...");
return false;
}
} catch (Exception e) {
e.printStackTrace();
} catch (Exception exception) {
BooksWithoutBorders.getInstance().getLogger().log(Level.SEVERE, "Unable to create necessary folders");
return false;
}
}
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 +284,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

@ -0,0 +1,58 @@
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 clear command
*/
public class CommandClear 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.notHoldingOneWritableBookCheck(player, "You must be holding a writable book to " +
"clear it!", "You cannot clear two books at once!")) {
return false;
}
//Clear the player's held book
ItemStack heldBook = InventoryHelper.getHeldBook(player, false);
BookMeta bookMeta = (BookMeta) heldBook.getItemMeta();
if (bookMeta == null) {
BooksWithoutBorders.sendErrorMessage(sender, "Unable to get metadata for the held book!");
return false;
}
bookMeta.setPages("");
bookMeta.setAuthor(null);
bookMeta.setGeneration(null);
bookMeta.setTitle(null);
heldBook.setItemMeta(bookMeta);
BooksWithoutBorders.sendSuccessMessage(sender, "Book cleared!");
return true;
}
@Nullable
@Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
@NotNull String[] args) {
return new ArrayList<>();
}
}

View File

@ -1,16 +1,20 @@
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 net.knarcraft.bookswithoutborders.utility.TabCompletionTypeHelper;
import net.knarcraft.knarlib.util.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 +25,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 +42,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(TabCompletionTypeHelper.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.utility.FileHelper;
import net.knarcraft.bookswithoutborders.utility.InputCleaningHelper;
import net.knarcraft.bookswithoutborders.state.BookDirectory;
import net.knarcraft.bookswithoutborders.utility.BookFileHelper;
import net.knarcraft.bookswithoutborders.utility.BookHelper;
import net.knarcraft.knarlib.util.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;
@ -41,7 +41,7 @@ public class CommandDelete implements TabExecutor {
boolean deleteBook(CommandSender sender, String[] args, boolean deletePublic) {
//List deletable files
if (args.length == 0) {
FileHelper.printBooks(sender, deletePublic);
BookFileHelper.printBooks(sender, deletePublic);
return true;
}
//Delete the file
@ -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 = BookFileHelper.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.knarlib.util.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,15 +1,18 @@
package net.knarcraft.bookswithoutborders.command;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.utility.FileHelper;
import net.knarcraft.bookswithoutborders.utility.BookFileHelper;
import net.knarcraft.bookswithoutborders.utility.BookLoader;
import net.knarcraft.bookswithoutborders.utility.InputCleaningHelper;
import net.knarcraft.bookswithoutborders.utility.TabCompletionHelper;
import net.knarcraft.bookswithoutborders.utility.TabCompletionTypeHelper;
import net.knarcraft.knarlib.util.TabCompletionHelper;
import org.bukkit.Server;
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.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
@ -22,7 +25,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;
@ -47,7 +50,7 @@ public class CommandGive implements TabExecutor {
}
if (args.length == 0) {
FileHelper.printBooks(sender, givePublic);
BookFileHelper.printBooks(sender, givePublic);
return true;
}
@ -67,6 +70,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 +91,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 +100,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 +126,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(TabCompletionTypeHelper.getBooleansAndNumbers(1, 3), args[2]);
} else if (argumentCount == 4) {
//Signed
try {
Integer.parseInt(args[2]);
return TabCompletionHelper.getBooleans();
return TabCompletionHelper.filterMatchingStartsWith(TabCompletionTypeHelper.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,14 +1,17 @@
package net.knarcraft.bookswithoutborders.command;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.utility.FileHelper;
import net.knarcraft.bookswithoutborders.utility.BookFileHelper;
import net.knarcraft.bookswithoutborders.utility.BookLoader;
import net.knarcraft.bookswithoutborders.utility.InputCleaningHelper;
import net.knarcraft.bookswithoutborders.utility.TabCompletionHelper;
import net.knarcraft.bookswithoutborders.utility.TabCompletionTypeHelper;
import net.knarcraft.knarlib.util.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.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
@ -18,10 +21,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);
}
@ -49,7 +50,7 @@ public class CommandLoad implements TabExecutor {
//Show books available to the player
if (argumentCount == 0) {
FileHelper.printBooks(sender, loadPublic);
BookFileHelper.printBooks(sender, loadPublic);
return true;
}
@ -78,7 +79,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 +95,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 +111,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(TabCompletionTypeHelper.getBooleansAndNumbers(1, 3), args[1]);
} else if (argumentCount == 3) {
//Signed
try {
Integer.parseInt(args[1]);
return TabCompletionHelper.getBooleans();
return TabCompletionHelper.filterMatchingStartsWith(TabCompletionTypeHelper.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,9 +1,12 @@
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.BookFileHelper;
import net.knarcraft.bookswithoutborders.utility.BookHelper;
import net.knarcraft.bookswithoutborders.utility.BookToFromTextHelper;
import net.knarcraft.bookswithoutborders.utility.FileHelper;
import net.knarcraft.bookswithoutborders.utility.InventoryHelper;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
@ -11,19 +14,17 @@ 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 java.util.logging.Level;
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 +32,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 +51,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,63 +77,64 @@ 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;
}
//Find any duplicates of the book
int foundDuplicates = FileHelper.findDuplicates(foundFiles, fileName);
int foundDuplicates = BookFileHelper.findDuplicates(foundFiles, fileName);
//Deal with duplicates
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);
@ -141,14 +143,16 @@ public class CommandSave implements TabExecutor {
//Update the relevant book list
BooksWithoutBorders.updateBooks(player, saveToPublicFolder);
BooksWithoutBorders.sendSuccessMessage(player, "Book Saved as \"" + fileName + "\"");
} catch (IOException e) {
e.printStackTrace();
} catch (IOException exception) {
BooksWithoutBorders.getInstance().getLogger().log(Level.SEVERE, "Unable to save book");
}
}
@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,15 +1,18 @@
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;
import net.knarcraft.bookswithoutborders.utility.TabCompletionTypeHelper;
import net.knarcraft.knarlib.util.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.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
@ -20,9 +23,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 +69,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 +97,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 +119,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 +136,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(TabCompletionTypeHelper.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,8 +1,10 @@
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 net.knarcraft.knarlib.property.ColorConversion;
import net.knarcraft.knarlib.util.ColorHelper;
import org.bukkit.Material;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
@ -10,6 +12,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 +24,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;
@ -42,8 +45,8 @@ public class CommandSetLore implements TabExecutor {
String rawLore = String.join(" ", args);
//Format lore
rawLore = ChatColor.translateAlternateColorCodes('&', rawLore);
String[] loreParts = rawLore.split(BooksWithoutBorders.getLoreSeparator());
rawLore = ColorHelper.translateColorCodes(rawLore, ColorConversion.RGB);
String[] loreParts = rawLore.split(BooksWithoutBordersConfig.getLoreSeparator());
List<String> newLore = new ArrayList<>(Arrays.asList(loreParts));
//Update lore
@ -59,10 +62,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

@ -2,7 +2,8 @@ package net.knarcraft.bookswithoutborders.command;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.utility.InventoryHelper;
import org.bukkit.ChatColor;
import net.knarcraft.knarlib.property.ColorConversion;
import net.knarcraft.knarlib.util.ColorHelper;
import org.bukkit.Material;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
@ -11,6 +12,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 +23,8 @@ 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;
@ -39,7 +42,7 @@ public class CommandSetTitle implements TabExecutor {
}
String title = String.join(" ", args);
title = ChatColor.translateAlternateColorCodes('&', title);
title = ColorHelper.translateColorCodes(title, ColorConversion.RGB);
ItemMeta itemMetadata = heldItem.getItemMeta();
if (itemMetadata == null) {
@ -65,9 +68,11 @@ 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,13 @@ import org.bukkit.command.TabExecutor;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.BookMeta;
import org.bukkit.inventory.meta.WritableBookMeta;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.logging.Level;
/**
* Command executor for the unsign command
@ -20,7 +26,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;
@ -45,17 +51,42 @@ public class CommandUnSign implements TabExecutor {
*/
public void unSignHeldBook(Player player, boolean mainHand) {
//Get the old book
BookMeta oldBook = InventoryHelper.getHeldBookMetadata(player, mainHand);
BookMeta oldMetadata = InventoryHelper.getHeldBookMetadata(player, mainHand);
ItemStack heldBook = InventoryHelper.getHeldBook(player, mainHand);
//UnSign the book
ItemStack newBook = new ItemStack(Material.WRITABLE_BOOK);
newBook.setItemMeta(oldBook);
//Only allow the owner to un-sign the book
if (BooksWithoutBordersConfig.getAuthorOnlyUnsign() && !player.hasPermission("bookswithoutborders.bypassAuthorOnlyUnsign")) {
if (BookHelper.isNotAuthor(player, Objects.requireNonNull(oldMetadata))) {
return;
}
}
WritableBookMeta newMetadata = (BookMeta) BooksWithoutBorders.getItemFactory().getItemMeta(Material.WRITABLE_BOOK);
if (newMetadata == null) {
BooksWithoutBorders.getInstance().getLogger().log(Level.SEVERE, "Unable to create writable book metadata");
return;
}
//Create a new unsigned book with the same data
ItemStack newBook = new ItemStack(Material.WRITABLE_BOOK, heldBook.getAmount());
if (oldMetadata.hasLore()) {
List<String> oldLore = oldMetadata.getLore();
if (oldLore != null) {
List<String> newLore = new ArrayList<>(oldLore);
newLore.remove(0);
newMetadata.setLore(newLore);
}
}
newMetadata.setPages(oldMetadata.getPages());
newBook.setItemMeta(newMetadata);
// Give the player the new book
InventoryHelper.replaceHeldItem(player, newBook, mainHand);
}
@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,406 @@
package net.knarcraft.bookswithoutborders.config;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.utility.EconomyHelper;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.Material;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.configuration.Configuration;
import java.nio.file.FileSystems;
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 = FileSystems.getDefault().getSeparator();
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;
private static boolean enableBookshelfPeeking;
/**
* 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 players can left-click a bookshelf to peek at the contained books
*
* @return <p>True if players can peek at the contained books</p>
*/
public static boolean getEnableBookshelfPeeking() {
return enableBookshelfPeeking;
}
/**
* 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);
config.set(ConfigOption.ENABLE_BOOKSHELF_PEEKING.getConfigNode(), enableBookshelfPeeking);
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 = config.getInt(ConfigOption.MAX_DUPLICATES.getConfigNode(),
(Integer) ConfigOption.MAX_DUPLICATES.getDefaultValue());
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);
enableBookshelfPeeking = getBoolean(config, ConfigOption.ENABLE_BOOKSHELF_PEEKING);
//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 = config.getDouble(ConfigOption.PRICE_QUANTITY.getConfigNode(),
(Double) ConfigOption.PRICE_QUANTITY.getDefaultValue());
//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 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,116 @@
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),
/**
* Whether hitting a bookshelf should display information about the contained books
*/
ENABLE_BOOKSHELF_PEEKING("Options.Enable_Book_Peeking", 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

@ -1,5 +1,7 @@
package net.knarcraft.bookswithoutborders.encryption;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
@ -15,6 +17,7 @@ import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.util.Base64;
import java.util.logging.Level;
/**
* This class represents and AES encryptor/decryptor
@ -66,16 +69,16 @@ public class AES {
//Initialize cipher
try {
aes.init(mode, secretKeySpec, ivParameterSpec);
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
e.printStackTrace();
} catch (InvalidKeyException | InvalidAlgorithmParameterException exception) {
BooksWithoutBorders.getInstance().getLogger().log(Level.SEVERE, "Invalid AES input given!");
return null;
}
//Perform encryption/decryption and output result
try {
byte[] output = aes.doFinal(getInputBytes(input, encrypt));
return createResult(output, encrypt);
} catch (IllegalBlockSizeException | BadPaddingException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException | BadPaddingException exception) {
BooksWithoutBorders.getInstance().getLogger().log(Level.SEVERE, "Invalid AES block size or padding");
return null;
}
}
@ -131,8 +134,8 @@ public class AES {
Cipher aes;
try {
aes = Cipher.getInstance("AES/CBC/PKCS5Padding");
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException | NoSuchPaddingException exception) {
BooksWithoutBorders.getInstance().getLogger().log(Level.SEVERE, "Invalid AES algorithm or padding");
return null;
}
return aes;
@ -149,15 +152,15 @@ public class AES {
SecretKeyFactory keyFactory;
try {
keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException exception) {
BooksWithoutBorders.getInstance().getLogger().log(Level.SEVERE, "Invalid AES algorithm");
return null;
}
SecretKey tmp;
try {
tmp = keyFactory.generateSecret(spec);
} catch (InvalidKeySpecException e) {
e.printStackTrace();
} catch (InvalidKeySpecException exception) {
BooksWithoutBorders.getInstance().getLogger().log(Level.SEVERE, "Invalid AES key specification");
return null;
}
return new SecretKeySpec(tmp.getEncoded(), "AES");

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,13 +27,13 @@ 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++) {
for (int k = 0; k < 4; k++) {
for (int l = 0; l < 4; l++) {
originalCodonList.add("" + bases[i] + bases[j] + bases[k] + bases[l]);
originalCodonList.add(bases[i] + bases[j] + bases[k] + bases[l]);
}
}
}
@ -50,8 +48,8 @@ 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<>();
while (originalCodonList.size() > 0) {
ArrayList<String> shuffledCodonList = new ArrayList<>();
while (!originalCodonList.isEmpty()) {
int index = ranGen.nextInt(originalCodonList.size());
shuffledCodonList.add(originalCodonList.get(index));
originalCodonList.remove(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
*/
@ -117,9 +97,12 @@ public class GenenCrypt {
String s = charList[i];
String[] sa = codonTable.get(s);
switch (s) {
case "\t" -> System.out.println(i + "\t" + "\\t" + "\t" + sa[0] + ", " + sa[1] + ", " + sa[2] + ", " + sa[3]);
case "\n" -> System.out.println(i + "\t" + "\\n" + "\t" + sa[0] + ", " + sa[1] + ", " + sa[2] + ", " + sa[3]);
case " " -> System.out.println(i + "\t" + "\" \"" + "\t" + sa[0] + ", " + sa[1] + ", " + sa[2] + ", " + sa[3]);
case "\t" ->
System.out.println(i + "\t" + "\\t" + "\t" + sa[0] + ", " + sa[1] + ", " + sa[2] + ", " + sa[3]);
case "\n" ->
System.out.println(i + "\t" + "\\n" + "\t" + sa[0] + ", " + sa[1] + ", " + sa[2] + ", " + sa[3]);
case " " ->
System.out.println(i + "\t" + "\" \"" + "\t" + sa[0] + ", " + sa[1] + ", " + sa[2] + ", " + sa[3]);
default -> System.out.println(i + "\t" + s + "\t" + sa[0] + ", " + sa[1] + ", " + sa[2] + ", " + sa[3]);
}
}
@ -181,4 +164,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() {
@ -16,7 +19,7 @@ public class SubstitutionCipher {
// original message is offset by
public String encrypt(String in, String key) {
StringBuilder output = new StringBuilder();
if (key != null && key.length() > 0) {
if (key != null && !key.isEmpty()) {
StringTokenizer tokenizer = new StringTokenizer(key, ", "); // tokenizes the key
// converts each number in the key to an integer and adds to an array
int[] offsetArray = new int[tokenizer.countTokens()];
@ -49,9 +52,10 @@ public class SubstitutionCipher {
// but in reverse. Could probably be combined into one
// method with a flag for encryption / decryption, but
// I'm lazy.
@SuppressWarnings("unused")
public String decrypt(String in, String key) {
StringBuilder output = new StringBuilder();
if (key != null && key.length() > 0) {
if (key != null && !key.isEmpty()) {
StringTokenizer tokenizer = new StringTokenizer(key, ", "); // tokenizes the key
// converts each number in the key to an integer and adds to an array
int[] offsetArray = new int[tokenizer.countTokens()];
@ -76,6 +80,7 @@ public class SubstitutionCipher {
// the one time pad encryption is very secure, and
// encryption works just like decryption, but is
// vulnerable if the same key is used more than once.
@SuppressWarnings("unused")
public String oneTimePad(String in, String key) {
StringBuilder output = new StringBuilder();
for (int i = 0; i < in.length(); i++) {
@ -83,5 +88,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

@ -0,0 +1,160 @@
package net.knarcraft.bookswithoutborders.listener;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
import net.knarcraft.bookswithoutborders.utility.IntegerToRomanConverter;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.block.ChiseledBookshelf;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.Player;
import org.bukkit.event.Event;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.ChiseledBookshelfInventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.BookMeta;
import org.bukkit.inventory.meta.EnchantmentStorageMeta;
import org.bukkit.inventory.meta.ItemMeta;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getSuccessColor;
/**
* A listener for bookshelf clicking
*/
public class BookshelfListener implements Listener {
@EventHandler
public void onBookshelfClick(PlayerInteractEvent event) {
Player player = event.getPlayer();
// If left-clicking a chiseled bookshelf and sneaking, display contents
if (!event.hasBlock() || event.getClickedBlock() == null ||
!(event.getClickedBlock().getState() instanceof ChiseledBookshelf chiseledBookshelf) ||
event.getAction() != Action.LEFT_CLICK_BLOCK || !player.isSneaking()) {
return;
}
// Check if bookshelf peeking is enabled, and the player can peek
if (!BooksWithoutBordersConfig.getEnableBookshelfPeeking() ||
!event.getPlayer().hasPermission("bookswithoutborders.peekbookshelf")) {
return;
}
event.setUseInteractedBlock(Event.Result.DENY);
event.setUseItemInHand(Event.Result.DENY);
ChiseledBookshelfInventory bookshelfInventory = chiseledBookshelf.getInventory();
player.sendMessage(getBookshelfDescription(bookshelfInventory));
}
/**
* Gets the description for a bookshelf's contents
*
* @param bookshelfInventory <p>The inventory of the bookshelf to describe</p>
* @return <p>A textual description of the bookshelf's contents</p>
*/
private String getBookshelfDescription(ChiseledBookshelfInventory bookshelfInventory) {
StringBuilder builder = new StringBuilder(getSuccessColor() + "Books in shelf:");
for (ItemStack itemStack : bookshelfInventory.getStorageContents()) {
if (itemStack == null) {
continue;
}
ItemMeta meta = itemStack.getItemMeta();
builder.append("\n ").append(ChatColor.GRAY).append(" - ");
if (meta instanceof BookMeta bookMeta) {
builder.append(getBookDescription(bookMeta));
} else if (meta instanceof EnchantmentStorageMeta enchantmentStorageMeta) {
builder.append(getEnchantedBookDescription(enchantmentStorageMeta));
} else if (meta != null) {
builder.append(getPlainBookDescription(meta));
}
}
return builder.toString();
}
/**
* Gets the description of a plain (enchant-able) book
*
* @param itemMeta <p>The metadata for the book to describe</p>
* @return <p>The description of the book</p>
*/
private String getPlainBookDescription(ItemMeta itemMeta) {
String name = itemMeta.getDisplayName();
if (name.isEmpty()) {
name = "Plain book";
}
return name;
}
/**
* Gets the description of a book
*
* @param bookMeta <p>The metadata for the book to describe</p>
* @return <p>The book's description</p>
*/
private String getBookDescription(BookMeta bookMeta) {
String title;
String author;
if (!bookMeta.hasTitle() || bookMeta.getTitle() == null) {
title = "Untitled";
} else {
title = bookMeta.getTitle();
}
if (!bookMeta.hasAuthor() || bookMeta.getAuthor() == null) {
author = "Unknown";
} else {
author = bookMeta.getAuthor();
}
return title + " by " + author;
}
/**
* Gets the description of an enchanted book
*
* @param enchantmentStorageMeta <p>The metadata for the enchanted book to describe</p>
* @return <p>The enchanted book's description</p>
*/
private String getEnchantedBookDescription(EnchantmentStorageMeta enchantmentStorageMeta) {
StringBuilder builder = new StringBuilder();
builder.append("Enchanted book (");
Map<Enchantment, Integer> enchantmentMap = enchantmentStorageMeta.getStoredEnchants();
List<String> enchantments = new ArrayList<>(enchantmentMap.size());
for (Map.Entry<Enchantment, Integer> enchantmentEntry : enchantmentMap.entrySet()) {
enchantments.add(getEnchantmentName(enchantmentEntry.getKey()) + " " +
IntegerToRomanConverter.getRomanNumber(enchantmentEntry.getValue()));
}
builder.append(String.join(", ", enchantments));
builder.append(")");
return builder.toString();
}
/**
* Gets a prettified name of an enchantment
*
* @param enchantment <p>The enchantment to get the name of</p>
* @return <p>The prettified enchantment name</p>
*/
private String getEnchantmentName(Enchantment enchantment) {
return uppercaseFirst(enchantment.getKey().getKey().replace("_", " "));
}
/**
* Turns the first character of each of a string's words into uppercase
*
* @param input <p>The input to uppercase</p>
* @return <p>The input string with more uppercase</p>
*/
private String uppercaseFirst(String input) {
String[] parts = input.split(" ");
for (int i = 0; i < parts.length; i++) {
parts[i] = parts[i].substring(0, 1).toUpperCase() + parts[i].substring(1);
}
return String.join(" ", parts);
}
}

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,15 +1,20 @@
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.BookFileHelper;
import net.knarcraft.bookswithoutborders.utility.BookLoader;
import net.knarcraft.bookswithoutborders.utility.EncryptionHelper;
import net.knarcraft.bookswithoutborders.utility.FileHelper;
import net.knarcraft.bookswithoutborders.utility.InputCleaningHelper;
import org.bukkit.ChatColor;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.Material;
import org.bukkit.Tag;
import org.bukkit.block.Sign;
import org.bukkit.block.sign.Side;
import org.bukkit.block.sign.SignSide;
import org.bukkit.entity.Player;
import org.bukkit.event.Event;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
@ -22,10 +27,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.utility.FileHelper.isBookListIndex;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getBookFolder;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getSlash;
import static net.knarcraft.bookswithoutborders.utility.BookFileHelper.isBookListIndex;
/**
* A listener for relevant sign events such as clicking a decryption sign
*/
public class SignEventListener implements Listener {
private final String slash = getSlash();
@ -49,7 +57,7 @@ public class SignEventListener implements Listener {
//Check if the sign is of a valid type
if (!((lines[1].equalsIgnoreCase("[Encrypt]") || lines[1].equalsIgnoreCase("[Decrypt]") ||
lines[1].equalsIgnoreCase("[Give]")) && lines[2].trim().length() > 0)) {
lines[1].equalsIgnoreCase("[Give]")) && !lines[2].trim().isEmpty())) {
//Mark the second line as invalid
event.setLine(1, ChatColor.DARK_RED + lines[1]);
return;
@ -84,24 +92,32 @@ 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) ||
Tag.WALL_SIGNS.isTagged(clickedBlockType))) {
event.setUseItemInHand(Event.Result.DENY);
//The player right-clicked a sign
Sign sign = (Sign) event.getClickedBlock().getState();
if (signLineEquals(sign, 0, "[BwB]", ChatColor.DARK_GREEN)) {
if (signLineEquals(sign, 1, "[Encrypt]", ChatColor.DARK_BLUE)) {
encryptHeldBookUsingSign(sign, heldItemType, player, hand);
} else if (signLineEquals(sign, 1, "[Decrypt]", ChatColor.DARK_BLUE)) {
decryptHeldBookUsingSign(sign, heldItemType, player, hand);
} else if (signLineEquals(sign, 1, "[Give]", ChatColor.DARK_BLUE) &&
getSignLine2Color(sign) == ChatColor.DARK_GREEN) {
giveBook(sign, player);
} else {
player.sendMessage("Sign command " + sign.getLine(1) + " " + sign.getLine(2) + " invalid");
player.sendMessage(String.valueOf(getSignLine2Color(sign)));
}
if (!signLineEquals(sign, 0, "[BwB]", ChatColor.DARK_GREEN)) {
return;
}
if (signLineEquals(sign, 1, "[Encrypt]", ChatColor.DARK_BLUE)) {
encryptHeldBookUsingSign(sign, heldItemType, player, hand);
} else if (signLineEquals(sign, 1, "[Decrypt]", ChatColor.DARK_BLUE)) {
decryptHeldBookUsingSign(sign, heldItemType, player, hand);
} else if (signLineEquals(sign, 1, "[Give]", ChatColor.DARK_BLUE) &&
getSignLine2Color(sign) == ChatColor.DARK_GREEN) {
giveBook(sign, player);
} else {
SignSide front = sign.getSide(Side.FRONT);
player.sendMessage(String.format("Sign command %s %s is invalid", front.getLine(1),
front.getLine(2)));
player.sendMessage(String.valueOf(getSignLine2Color(sign)));
}
} else if (heldItemType == Material.WRITTEN_BOOK && (event.getAction() == Action.LEFT_CLICK_AIR
|| event.getAction() == Action.LEFT_CLICK_BLOCK)) {
@ -128,7 +144,7 @@ public class SignEventListener implements Listener {
player.closeInventory();
//Converts user supplied key into integer form
String lineText = ChatColor.stripColor(sign.getLine(2));
String lineText = ChatColor.stripColor(sign.getSide(Side.FRONT).getLine(2));
String key = EncryptionHelper.getNumberKeyFromStringKey(lineText);
ItemStack book = EncryptionHelper.loadEncryptedBook(player, key, false);
@ -146,9 +162,9 @@ public class SignEventListener implements Listener {
* @return <p>The color of the sign</p>
*/
private ChatColor getSignLine2Color(Sign sign) {
String line = sign.getLine(2);
String line = sign.getSide(Side.FRONT).getLine(2);
if (!ChatColor.stripColor(line).equals(line)) {
return ChatColor.getByChar(sign.getLine(2).substring(1, 2));
return ChatColor.getByChar(sign.getSide(Side.FRONT).getLine(2).substring(1, 2).charAt(0));
} else {
return null;
}
@ -164,7 +180,7 @@ public class SignEventListener implements Listener {
* @return <p>True if the given string is what's on the sign</p>
*/
private boolean signLineEquals(Sign sign, int lineNumber, String compareTo, ChatColor color) {
String line = sign.getLine(lineNumber);
String line = sign.getSide(Side.FRONT).getLine(lineNumber);
return line.equalsIgnoreCase(color + compareTo);
}
@ -177,14 +193,15 @@ 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;
}
//Tests if a full file name has been supplied and points to an actual file
String signFile = getBookFolder() + lines[2] + lines[3];
if (FileHelper.bookFileExists(signFile)) {
if (BookFileHelper.bookFileExists(signFile)) {
markGiveSignValidity(event, true);
return;
} else {
@ -234,11 +251,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 +266,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 +274,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!");
}
/**
@ -270,7 +287,7 @@ public class SignEventListener implements Listener {
*/
private void encryptHeldBookUsingSign(Sign sign, Material heldItemType, Player player, EquipmentSlot hand) {
ItemStack eBook;
String[] lines = sign.getLines();
String[] lines = sign.getSide(Side.FRONT).getLines();
boolean mainHand = hand == EquipmentSlot.HAND;
if (heldItemType == Material.WRITTEN_BOOK) {
player.closeInventory();
@ -289,7 +306,7 @@ public class SignEventListener implements Listener {
* @param player <p>The player which clicked the sign</p>
*/
private void giveBook(Sign sign, Player player) {
String fileName = ChatColor.stripColor(sign.getLine(2));
String fileName = ChatColor.stripColor(sign.getSide(Side.FRONT).getLine(2));
boolean isLoadListNumber = false;
try {
@ -299,18 +316,18 @@ public class SignEventListener implements Listener {
}
//Add the third line to the second line for the full filename
String thirdLine = sign.getLine(3);
String thirdLine = sign.getSide(Side.FRONT).getLine(3);
if (!isLoadListNumber && thirdLine.length() >= 2) {
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

@ -26,22 +26,22 @@ public enum BookHoldingState {
UNSIGNED_MAIN_HAND,
/**
* The player is holding one signed book in their off hand
* The player is holding one signed book in their off-hand
*/
SIGNED_OFF_HAND,
/**
* The player is holding one unsigned book in their off hand
* The player is holding one unsigned book in their off-hand
*/
UNSIGNED_OFF_HAND,
/**
* The player is holding one signed book in their main hand and one unsigned book in their off hand
* The player is holding one signed book in their main hand and one unsigned book in their off-hand
*/
SIGNED_MAIN_HAND_UNSIGNED_OFF_HAND,
/**
* The player is holding one unsigned book in their main hand and one signed book in their off hand
* The player is holding one unsigned book in their main hand and one signed book in their off-hand
*/
UNSIGNED_MAIN_HAND_SIGNED_OFF_HAND,

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

@ -11,7 +11,7 @@ public enum ItemSlot {
MAIN_HAND,
/**
* The item is in the player's off hand
* The item is in the player's off-hand
*/
OFF_HAND,

View File

@ -1,7 +1,9 @@
package net.knarcraft.bookswithoutborders.utility;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import org.bukkit.ChatColor;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
import net.knarcraft.bookswithoutborders.state.BookDirectory;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.command.CommandSender;
import java.io.File;
@ -10,17 +12,14 @@ 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
*/
public final class FileHelper {
private FileHelper() {
public final class BookFileHelper {
private BookFileHelper() {
}
/**
@ -95,13 +94,11 @@ 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);
return BookFileHelper.listFiles(sender, file);
}
/**
@ -112,7 +109,7 @@ public final class FileHelper {
*/
public static void printBooks(CommandSender sender, boolean listPublic) {
List<String> availableBooks = BooksWithoutBorders.getAvailableBooks(sender, listPublic);
FileHelper.printFiles(sender, availableBooks);
BookFileHelper.printFiles(sender, availableBooks);
}
/**
@ -123,6 +120,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,9 +146,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));
}
fileList.add(fileName);
}
return fileList;

View File

@ -1,6 +1,12 @@
package net.knarcraft.bookswithoutborders.utility;
import net.knarcraft.knarlib.property.ColorConversion;
import net.knarcraft.knarlib.util.ColorHelper;
import org.bukkit.inventory.meta.BookMeta;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* A class for formatting text to fit books
@ -8,7 +14,6 @@ import java.util.List;
public final class BookFormatter {
private BookFormatter() {
}
/**
@ -94,4 +99,19 @@ 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(ColorHelper.translateColorCodes(page, ColorConversion.RGB));
}
bookMeta.setPages(formattedPages);
return bookMeta;
}
}

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,150 @@
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.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) {
//Try converting the username to UUID
String titleAuthorSeparator = BooksWithoutBordersConfig.getTitleAuthorSeparator();
String[] data = fileName.split(titleAuthorSeparator);
String extension = data[1].substring(data[1].length() - 4);
String userName = data[1].substring(0, data[1].length() - 4);
Player player = Bukkit.getPlayer(userName);
if (player != null) {
data[1] = player.getUniqueId() + extension;
file = getFullPath(sender, String.join(titleAuthorSeparator, data), bookDirectory, directory);
if (file == null) {
BooksWithoutBorders.sendErrorMessage(sender, "Incorrect file name!");
return null;
}
} else {
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 = BookFileHelper.getBookFile(bookFolder + "Encrypted" + slash + directory + slash + fileName);
} else {
file = BookFileHelper.getBookFile(BookHelper.getBookDirectoryPathString(bookDirectory, sender) + fileName);
}
if (file == null || !file.isFile()) {
return null;
} else {
return file;
}
}
}

View File

@ -1,19 +1,24 @@
package net.knarcraft.bookswithoutborders.utility;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
import net.knarcraft.knarlib.util.FileHelper;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.inventory.meta.BookMeta;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import static net.knarcraft.bookswithoutborders.utility.BookHelper.authorFromUUID;
import static net.knarcraft.bookswithoutborders.utility.InputCleaningHelper.fixName;
/**
@ -41,6 +46,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());
}
@ -77,12 +87,18 @@ public final class BookToFromTextHelper {
* @throws IOException <p>If unable to save the book</p>
*/
public static void bookToTXT(String folderPath, String fileName, BookMeta bookMetadata) throws IOException {
FileWriter fileWriter = new FileWriter(folderPath + fileName + ".txt");
FileWriter fileWriter = new FileWriter(folderPath + fileName + ".txt", StandardCharsets.UTF_8);
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 +116,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 +138,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
@ -139,16 +159,22 @@ public final class BookToFromTextHelper {
List<String> rawPages;
try {
rawPages = readTextFile(file);
} catch (IOException e) {
e.printStackTrace();
} catch (IOException exception) {
BooksWithoutBorders.getInstance().getLogger().log(Level.SEVERE, "Unable to read text file");
return null;
}
//Parse the generation from the book data
if (rawPages != null && !rawPages.isEmpty() && 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);
@ -164,7 +190,7 @@ public final class BookToFromTextHelper {
*/
private static List<String> readTextFile(File file) throws IOException {
List<String> rawPages = new ArrayList<>();
BufferedReader bufferedReader = new BufferedReader(new FileReader(file));
BufferedReader bufferedReader = FileHelper.getBufferedReaderFromInputStream(new FileInputStream(file));
//Use the first line to decide if we are loading a book, or creating a new book
String firstLine = bufferedReader.readLine();
@ -172,8 +198,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,16 +1,20 @@
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;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory;
import org.bukkit.inventory.meta.BookMeta;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.RegisteredServiceProvider;
import org.bukkit.plugin.ServicesManager;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
@ -21,7 +25,6 @@ public final class EconomyHelper {
private static Economy economy;
private EconomyHelper() {
}
/**
@ -67,24 +70,96 @@ 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) {
return !EconomyHelper.payForBookPrintingEconomy(player, cost, numCopies);
} else {
if (player.getInventory().contains(bookCurrency, itemCost)) {
payForBookPrintingItem(player, itemCost);
return false;
if (bookCurrency == Material.WRITABLE_BOOK) {
//Writable books are treated as a special case to prevent WIP books from being used
return !takeWritableBookPayment(player, itemCost);
} else {
BooksWithoutBorders.sendErrorMessage(player, itemCost + " " + bookCurrency +
"(s) are required for this command!");
return true;
if (player.getInventory().contains(bookCurrency, itemCost)) {
payForBookPrintingItem(player, itemCost);
return false;
} else {
BooksWithoutBorders.sendErrorMessage(player, itemCost + " " + bookCurrency +
"(s) are required for this command!");
return true;
}
}
}
}
/**
* Takes a writable book payment, ignoring any books containing text
*
* @param player <p>The player to take payment from</p>
* @param itemCost <p>The number of writable books to pay</p>
* @return <p>True if the payment was successful</p>
*/
private static boolean takeWritableBookPayment(Player player, int itemCost) {
List<ItemStack> books = getPlayersEmptyBooks(player);
if (countItems(books) < itemCost) {
BooksWithoutBorders.sendErrorMessage(player, itemCost + " empty " + Material.WRITABLE_BOOK +
"(s) are required for this command!");
return false;
} else {
int clearedAmount = 0;
for (ItemStack itemStack : books) {
if (itemStack.getAmount() > itemCost) {
//If encountering an item stack with more than enough books, remove the necessary books
itemStack.setAmount(itemStack.getAmount() - itemCost);
return true;
} else {
clearedAmount += itemStack.getAmount();
player.getInventory().removeItem(itemStack);
}
if (clearedAmount >= itemCost) {
return true;
}
}
return true;
}
}
/**
* Gets the total number of items contained in the given list of items
*
* @param items <p>The items to count</p>
* @return <p>The total number of items</p>
*/
private static int countItems(List<ItemStack> items) {
int totalItems = 0;
for (ItemStack itemStack : items) {
totalItems += itemStack.getAmount();
}
return totalItems;
}
/**
* Gets all empty books in a player's inventory
*
* @param player <p>The player to get books for</p>
* @return <p>The empty books in the player's inventory</p>
*/
private static List<ItemStack> getPlayersEmptyBooks(Player player) {
List<ItemStack> validBooks = new ArrayList<>();
for (ItemStack itemStack : player.getInventory().getContents()) {
if (itemStack == null || itemStack.getType() != Material.WRITABLE_BOOK) {
continue;
}
BookMeta book = (BookMeta) itemStack.getItemMeta();
//Only accept empty books
if (book != null && (!book.hasPages() || (book.getPageCount() == 1 && book.getPage(1).trim().isEmpty()))) {
validBooks.add(itemStack);
}
}
return validBooks;
}
/**
* Uses economy to take payment for printing a number of books
*
@ -96,8 +171,10 @@ public final class EconomyHelper {
private static boolean payForBookPrintingEconomy(Player player, double cost, int numCopies) {
if ((economy.getBalance(player) - cost) >= 0) {
economy.withdrawPlayer(player, cost);
BooksWithoutBorders.sendSuccessMessage(player, economy.format(cost) + " withdrawn to create " + numCopies + " book(s)");
BooksWithoutBorders.sendSuccessMessage(player, "New balance: " + economy.format(economy.getBalance(player)));
BooksWithoutBorders.sendSuccessMessage(player, economy.format(cost) + " withdrawn to create " +
numCopies + " book(s)");
BooksWithoutBorders.sendSuccessMessage(player, "New balance: " +
economy.format(economy.getBalance(player)));
return true;
} else {
BooksWithoutBorders.sendErrorMessage(player, economy.format(cost) + " is required for this command!");
@ -116,7 +193,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,10 +1,11 @@
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;
import org.bukkit.ChatColor;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.Material;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.entity.Player;
@ -15,9 +16,10 @@ import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
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 +29,6 @@ import static net.knarcraft.bookswithoutborders.utility.InputCleaningHelper.fixN
public final class EncryptionHelper {
private EncryptionHelper() {
}
/**
@ -198,11 +199,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()) {
@ -258,17 +256,13 @@ public final class EncryptionHelper {
BooksWithoutBorders.sendErrorMessage(BooksWithoutBorders.getConsoleSender(), "Unable to create encryption group folder!");
return null;
}
} catch (Exception e) {
e.printStackTrace();
} catch (Exception exception) {
BooksWithoutBorders.getInstance().getLogger().log(Level.SEVERE, "Unable to save group encrypted book");
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,12 +275,12 @@ 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);
} catch (IOException e) {
e.printStackTrace();
} catch (IOException exception) {
BooksWithoutBorders.sendErrorMessage(player, "Group encrypted failed!");
return null;
}
@ -305,23 +299,20 @@ 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;
}
try {
BookToFromTextHelper.bookToYml(path, fileName, bookMetaData);
} catch (IOException e) {
e.printStackTrace();
} catch (IOException exception) {
BooksWithoutBorders.sendErrorMessage(player, "Encryption failed!");
return false;
}

View File

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

View File

@ -0,0 +1,90 @@
package net.knarcraft.bookswithoutborders.utility;
import java.util.ArrayList;
import java.util.List;
/**
* A converter from an integer to a roman numeral
*/
public final class IntegerToRomanConverter {
private final static List<Integer> romanValues = new ArrayList<>();
private final static List<Character> romanCharacters = new ArrayList<>();
static {
// Initialize the roman numbers
romanValues.add(1000);
romanValues.add(500);
romanValues.add(100);
romanValues.add(50);
romanValues.add(10);
romanValues.add(5);
romanValues.add(1);
romanCharacters.add('M');
romanCharacters.add('D');
romanCharacters.add('C');
romanCharacters.add('L');
romanCharacters.add('X');
romanCharacters.add('V');
romanCharacters.add('I');
}
private IntegerToRomanConverter() {
}
/**
* Gets the given number as a roman number string
*
* @param number <p>The number to convert</p>
* @return <p>The roman representation of the number</p>
*/
public static String getRomanNumber(int number) {
StringBuilder output = new StringBuilder();
int remainder = number;
for (int i = 0; i < romanCharacters.size(); i++) {
int romanValue = romanValues.get(i);
char romanCharacter = romanCharacters.get(i);
// Repeat the roman character, and calculate the new remainder
if (remainder >= romanValue) {
output.append(repeat(romanCharacter, remainder / romanValue));
remainder = remainder % romanValue;
}
// Exit early to prevent unexpected trailing characters
if (remainder == 0) {
return output.toString();
}
// Generate the special case IV and similar
for (int j = i; j < romanCharacters.size(); j++) {
int value = romanValues.get(j);
int difference = Math.max(romanValue - value, 0);
/* If the remainder is "one" less than the current roman value, we hit the IV/IX/XL case.
Note that 5 triggers the special case when 10 is tested, as 5 = 10 - 5, which requires a test, so it
can be filtered out. */
if (remainder == difference && value != romanValue / 2) {
output.append(romanCharacters.get(j)).append(romanCharacter);
remainder = 0;
}
}
}
return output.toString();
}
/**
* Repeats the given character
*
* @param character <p>The character to repeat</p>
* @param times <p>The number of times to repeat the character</p>
* @return <p>The repeated string</p>
*/
private static String repeat(char character, int times) {
return String.valueOf(character).repeat(Math.max(0, times));
}
}

View File

@ -14,7 +14,6 @@ import org.bukkit.inventory.meta.BookMeta;
public final class InventoryHelper {
private InventoryHelper() {
}
/**
@ -52,6 +51,31 @@ public final class InventoryHelper {
}
}
/**
* Performs checks to validate that a player contains exactly one unwritten book
*
* @param player <p>The player to validate</p>
* @param noBookMessage <p>The message to display if the player is not holding a book</p>
* @param twoBooksMessage <p>The message to display if the player is holding one book in each hand</p>
* @return <p>False if the player is holding exactly one book</p>
*/
public static boolean notHoldingOneWritableBookCheck(Player player, String noBookMessage, String twoBooksMessage) {
BookHoldingState holdingState = getBookHoldingState(player);
if (holdingState == BookHoldingState.NONE || holdingState == BookHoldingState.SIGNED_BOTH_HANDS ||
holdingState == BookHoldingState.SIGNED_MAIN_HAND || holdingState == BookHoldingState.SIGNED_OFF_HAND) {
BooksWithoutBorders.sendErrorMessage(player, noBookMessage);
return true;
}
if (holdingState == BookHoldingState.UNSIGNED_BOTH_HANDS) {
BooksWithoutBorders.sendErrorMessage(player, twoBooksMessage);
return true;
}
return false;
}
/**
* Performs checks to validate that a player contains exactly one written book
*
@ -97,7 +121,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

@ -6,10 +6,9 @@ import java.util.List;
/**
* Helper class for getting string lists required for auto-completion
*/
public final class TabCompletionHelper {
private TabCompletionHelper() {
public final class TabCompletionTypeHelper {
private TabCompletionTypeHelper() {
}
/**

View File

@ -0,0 +1,34 @@
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
# Whether to enable hitting a chiseled bookshelf while sneaking to see the shelf's contents.
Enable_Book_Peeking: false

View File

@ -1,18 +1,26 @@
name: BooksWithoutBorders
version: '${project.version}'
main: net.knarcraft.bookswithoutborders.BooksWithoutBorders
api-version: 1.17
prefix: Books Without Borders
api-version: '1.20'
prefix: 'Books Without Borders'
authors: [ EpicKnarvik97, AkiraAkiba ]
description: A continuation of the original Books Without Borders
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]
@ -41,6 +45,10 @@ commands:
description: Copies the book the player is holding
usage: /<command> <# of copies>
permission: bookswithoutborders.copy
clearbook:
description: Removes all text from the book the player is holding
usage: /<command>
permission: bookswithoutborders.clear
unsignbook:
description: Un-signs the book the player is holding
usage: /<command>
@ -49,12 +57,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 +86,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 +96,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 +108,42 @@ 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, and peeking at bookshelves if enabled
children:
bookswithoutborders.save: true
bookswithoutborders.load: true
bookswithoutborders.delete: true
bookswithoutborders.peekbookshelf: true
bookswithoutborders.alterbooks:
description: Allows player to change books' data such as lore/title/author/generation/formatting and unsigning books
children:
bookswithoutborders.clear: true
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:
@ -125,17 +151,19 @@ permissions:
bookswithoutborders.delete:
description: Allows player to delete books from their personal directory
bookswithoutborders.unsign:
description: Allows player to use unsign command
description: Allows player to use the unsign command
bookswithoutborders.copy:
description: Allows player to use copy command
description: Allows player to use the copy command
bookswithoutborders.clear:
description: Allows player to use the clear 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 +180,17 @@ 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)
bookswithoutborders.peekbookshelf:
description: Allows player to left-click a bookshelf to see the contents of the shelf

View File

@ -3,7 +3,7 @@ package net.knarcraft.bookswithoutborders.encryption;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotSame;
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));
assertNotSame(encrypted, plainText);
String decrypted = aes.encryptDecryptText(encrypted, password, false);
assertEquals(plainText, decrypted);
}

View File

@ -1,13 +1,13 @@
package net.knarcraft.bookswithoutborders.util;
import net.knarcraft.bookswithoutborders.utility.FileHelper;
import net.knarcraft.bookswithoutborders.utility.BookFileHelper;
import org.junit.Test;
import java.io.File;
import static org.junit.Assert.assertEquals;
public class FileHelperTest {
public class BookFileHelperTest {
@Test
public void findDuplicatesTest() {
@ -17,9 +17,9 @@ public class FileHelperTest {
files[2] = new File("test/asd/(3)book+)Crab.yml");
files[3] = new File("test/asd/(2)book+)Crab.yml");
files[4] = new File("test/asd/(1)book+)Crab.yml");
assertEquals(4, FileHelper.findDuplicates(files, "book+)Crab.yml"));
assertEquals(1, FileHelper.findDuplicates(files, "book+)Fish.yml"));
assertEquals(0, FileHelper.findDuplicates(files, "book+)Horse.yml"));
assertEquals(4, BookFileHelper.findDuplicates(files, "book+)Crab.yml"));
assertEquals(1, BookFileHelper.findDuplicates(files, "book+)Fish.yml"));
assertEquals(0, BookFileHelper.findDuplicates(files, "book+)Horse.yml"));
}
}

View File

@ -0,0 +1,40 @@
package net.knarcraft.bookswithoutborders.util;
import org.junit.Test;
import static net.knarcraft.bookswithoutborders.utility.IntegerToRomanConverter.getRomanNumber;
import static org.junit.Assert.assertEquals;
/**
* A test class for IntegerToRomanConverter
*/
public class IntegerToRomanConverterTest {
@Test
public void basicNumbersTest() {
assertEquals("I", getRomanNumber(1));
assertEquals("II", getRomanNumber(2));
assertEquals("III", getRomanNumber(3));
assertEquals("IV", getRomanNumber(4));
assertEquals("V", getRomanNumber(5));
assertEquals("X", getRomanNumber(10));
assertEquals("XV", getRomanNumber(15));
assertEquals("XX", getRomanNumber(20));
assertEquals("L", getRomanNumber(50));
assertEquals("C", getRomanNumber(100));
assertEquals("D", getRomanNumber(500));
assertEquals("M", getRomanNumber(1000));
}
@Test
public void nineFourTest() {
assertEquals("IV", getRomanNumber(4));
assertEquals("IX", getRomanNumber(9));
assertEquals("XIV", getRomanNumber(14));
assertEquals("XIX", getRomanNumber(19));
assertEquals("XXIV", getRomanNumber(24));
assertEquals("XL", getRomanNumber(40));
assertEquals("IL", getRomanNumber(49));
}
}