92 Commits
1.2.3 ... 1.3.9

Author SHA1 Message Date
ebfca05859 Release
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-08-04 02:26:18 +02:00
3e3b8e7ad2 Fixes some capitalization
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-08-03 17:27:30 +02:00
b2ce31234d Adds ability to set name and lore shown when chiseled bookshelves are peeked
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-08-03 17:23:19 +02:00
e5aaa29c66 Fixes problems in sorting and generating the character indexes
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-08-03 04:45:21 +02:00
f9674568ba Improves the books in shelf description
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-08-03 04:21:05 +02:00
2203037b00 Adds a warning when trying to save books containing the title author separator in title or author
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-08-03 02:40:13 +02:00
6b44ada84a Improves some hover text
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-08-03 02:11:50 +02:00
9f979dd56e Makes the book list's colors more consistent
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-08-03 02:07:38 +02:00
10ffd17c04 Fixes a lot of nullability issues
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-08-03 01:28:20 +02:00
0aff3fad02 Adds book reloading, and fixes a RegEx expression
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-08-03 00:47:36 +02:00
32d31fced6 Fixes som nullability, and alters special character removal somewhat
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-08-02 21:35:26 +02:00
2627407e6b Adds ability to filter books by author
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-08-02 20:00:48 +02:00
175b66465a Moves book index generation to its own class
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-08-02 19:01:15 +02:00
36b57b9191 Removes some debug output, and fixes some formatting
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-08-02 18:07:52 +02:00
0d5ad490ff Fixes sorting and character indexes for filenames with color codes
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-08-02 18:02:33 +02:00
9e300afbef Adds missing spacing when replacing underscores in book names
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-08-02 08:02:09 +02:00
a84a56391a Prevents the § from being used in filenames
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-08-02 07:52:05 +02:00
b15ad18ae3 Makes it easier to manually go to any book page
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-08-02 07:09:07 +02:00
67ccdf3b1d Removes spacing for letter search
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-08-02 06:58:36 +02:00
b9bd686ae9 Removes the unnecessary display of manual command input in the book menu
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-08-02 06:52:23 +02:00
a5be6bb72c Adds ability to easier find books by first letter
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-08-02 06:47:46 +02:00
ed0a750eb4 Improves the book list somewhat
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-08-02 03:33:44 +02:00
6aa422d461 Fixes wrong color on the inactive next button
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-08-01 21:05:03 +02:00
4be023bd63 Adds a ChatComponent-enhanced book list
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-08-01 20:59:16 +02:00
35e98e0f18 Bumps version for development
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-07-28 04:24:40 +02:00
74d59bf71b Bump version for release
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-07-28 04:18:54 +02:00
a1ed6b9566 Re-implements depreciated enchantment key getting
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-07-28 04:15:53 +02:00
3095586d2b Bumps version for development
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-07-27 21:03:15 +02:00
1da61dc820 Bumps version for release
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-07-27 20:57:16 +02:00
48f69000b0 Adds color support to book authors
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-07-27 20:45:00 +02:00
4ddfafe6ec Fixes books being loaded twice
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-07-27 18:42:43 +02:00
3f3566089e Adds gitignore to repo
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2024-11-27 12:18:23 +01:00
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
63 changed files with 2975 additions and 806 deletions

113
.gitignore vendored Normal file
View File

@@ -0,0 +1,113 @@
# User-specific stuff
.idea/
*.iml
*.ipr
*.iws
# IntelliJ
out/
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
.mvn/wrapper/maven-wrapper.jar
.flattened-pom.xml
# Common working directory
run/

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
}
}
}
}

179
README.md
View File

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

66
pom.xml
View File

@@ -6,7 +6,7 @@
<groupId>net.knarcraft</groupId> <groupId>net.knarcraft</groupId>
<artifactId>BooksWithoutBorders</artifactId> <artifactId>BooksWithoutBorders</artifactId>
<version>1.2.3</version> <version>1.3.9</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<licenses> <licenses>
@@ -20,7 +20,7 @@
<description>A continuation of the original Books Without Borders</description> <description>A continuation of the original Books Without Borders</description>
<properties> <properties>
<java.version>17</java.version> <java.version>16</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> </properties>
@@ -31,14 +31,14 @@
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version> <version>3.8.1</version>
<configuration> <configuration>
<source>17</source> <source>${java.version}</source>
<target>17</target> <target>${java.version}</target>
</configuration> </configuration>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId> <artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version> <version>3.3.0</version>
<executions> <executions>
<execution> <execution>
<phase>package</phase> <phase>package</phase>
@@ -47,6 +47,30 @@
</goals> </goals>
<configuration> <configuration>
<createDependencyReducedPom>false</createDependencyReducedPom> <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> </configuration>
</execution> </execution>
</executions> </executions>
@@ -61,6 +85,10 @@
</build> </build>
<repositories> <repositories>
<repository>
<id>knarcraft-repo</id>
<url>https://git.knarcraft.net/api/packages/EpicKnarvik97/maven</url>
</repository>
<repository> <repository>
<id>spigotmc-repo</id> <id>spigotmc-repo</id>
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url> <url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
@@ -70,25 +98,47 @@
<url>https://jitpack.io</url> <url>https://jitpack.io</url>
</repository> </repository>
</repositories> </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> <dependencies>
<dependency> <dependency>
<groupId>org.spigotmc</groupId> <groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId> <artifactId>spigot-api</artifactId>
<version>1.19.1-R0.1-SNAPSHOT</version> <version>1.21.8-R0.1-SNAPSHOT</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.github.MilkBowl</groupId> <groupId>com.github.MilkBowl</groupId>
<artifactId>VaultAPI</artifactId> <artifactId>VaultAPI</artifactId>
<version>1.7</version> <version>1.7.1</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency>
<groupId>net.knarcraft</groupId>
<artifactId>knarlib</artifactId>
<version>1.2.7</version>
<scope>compile</scope>
</dependency>
<dependency> <dependency>
<groupId>org.jetbrains</groupId> <groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId> <artifactId>annotations</artifactId>
<version>23.0.0</version> <version>24.0.1</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@@ -1,6 +1,7 @@
package net.knarcraft.bookswithoutborders; package net.knarcraft.bookswithoutborders;
import net.knarcraft.bookswithoutborders.command.CommandBooksWithoutBorders; import net.knarcraft.bookswithoutborders.command.CommandBooksWithoutBorders;
import net.knarcraft.bookswithoutborders.command.CommandClear;
import net.knarcraft.bookswithoutborders.command.CommandCopy; import net.knarcraft.bookswithoutborders.command.CommandCopy;
import net.knarcraft.bookswithoutborders.command.CommandDecrypt; import net.knarcraft.bookswithoutborders.command.CommandDecrypt;
import net.knarcraft.bookswithoutborders.command.CommandDelete; import net.knarcraft.bookswithoutborders.command.CommandDelete;
@@ -17,30 +18,40 @@ import net.knarcraft.bookswithoutborders.command.CommandSave;
import net.knarcraft.bookswithoutborders.command.CommandSavePublic; import net.knarcraft.bookswithoutborders.command.CommandSavePublic;
import net.knarcraft.bookswithoutborders.command.CommandSetAuthor; import net.knarcraft.bookswithoutborders.command.CommandSetAuthor;
import net.knarcraft.bookswithoutborders.command.CommandSetBookPrice; import net.knarcraft.bookswithoutborders.command.CommandSetBookPrice;
import net.knarcraft.bookswithoutborders.command.CommandSetBookshelfData;
import net.knarcraft.bookswithoutborders.command.CommandSetGeneration;
import net.knarcraft.bookswithoutborders.command.CommandSetLore; import net.knarcraft.bookswithoutborders.command.CommandSetLore;
import net.knarcraft.bookswithoutborders.command.CommandSetTitle; import net.knarcraft.bookswithoutborders.command.CommandSetTitle;
import net.knarcraft.bookswithoutborders.command.CommandUnSign; import net.knarcraft.bookswithoutborders.command.CommandUnSign;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig; import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
import net.knarcraft.bookswithoutborders.handler.BookshelfHandler;
import net.knarcraft.bookswithoutborders.listener.BookEventListener; import net.knarcraft.bookswithoutborders.listener.BookEventListener;
import net.knarcraft.bookswithoutborders.listener.BookshelfListener;
import net.knarcraft.bookswithoutborders.listener.PlayerEventListener; import net.knarcraft.bookswithoutborders.listener.PlayerEventListener;
import net.knarcraft.bookswithoutborders.listener.SignEventListener; import net.knarcraft.bookswithoutborders.listener.SignEventListener;
import net.knarcraft.bookswithoutborders.utility.FileHelper; import net.knarcraft.bookswithoutborders.utility.BookFileHelper;
import net.knarcraft.bookswithoutborders.utility.UpdateChecker; import net.knarcraft.knarlib.util.UpdateChecker;
import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender; import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.command.PluginCommand; import org.bukkit.command.PluginCommand;
import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemFactory; import org.bukkit.inventory.ItemFactory;
import org.bukkit.plugin.PluginDescriptionFile; import org.bukkit.plugin.PluginDescriptionFile;
import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.logging.Level;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getBookFolder; import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getBookFolder;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getErrorColor; import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getErrorColor;
@@ -53,16 +64,20 @@ import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig
public class BooksWithoutBorders extends JavaPlugin { public class BooksWithoutBorders extends JavaPlugin {
private static ItemFactory itemFactory; private static ItemFactory itemFactory;
private static Map<String, List<String>> playerBooksList; private static @NotNull Map<UUID, List<String>> playerBooksList = new HashMap<>();
private static List<String> publicBooksList; private static @NotNull List<String> publicBooksList = new ArrayList<>();
private static Map<Character, Integer> publicLetterIndex;
private static Map<UUID, Map<Character, Integer>> playerLetterIndex;
private static BooksWithoutBorders booksWithoutBorders; private static BooksWithoutBorders booksWithoutBorders;
private static ConsoleCommandSender consoleSender; private static ConsoleCommandSender consoleSender;
private static BookshelfHandler bookshelfHandler;
/** /**
* Gets the console sender for printing to the console * Gets the console sender for printing to the console
* *
* @return <p>The console's console sender</p> * @return <p>The console's console sender</p>
*/ */
@NotNull
public static ConsoleCommandSender getConsoleSender() { public static ConsoleCommandSender getConsoleSender() {
return consoleSender; return consoleSender;
} }
@@ -74,16 +89,38 @@ public class BooksWithoutBorders extends JavaPlugin {
* @param getPublic <p>Whether to get available public books</p> * @param getPublic <p>Whether to get available public books</p>
* @return <p>A list of available books</p> * @return <p>A list of available books</p>
*/ */
public static List<String> getAvailableBooks(CommandSender sender, boolean getPublic) { @NotNull
public static List<String> getAvailableBooks(@NotNull CommandSender sender, boolean getPublic) {
if (getPublic) { if (getPublic) {
return new ArrayList<>(publicBooksList); return new ArrayList<>(publicBooksList);
} else { } else if (sender instanceof Player player) {
String senderName = sender.getName(); UUID playerUUID = player.getUniqueId();
if (!playerBooksList.containsKey(senderName)) { if (!playerBooksList.containsKey(playerUUID)) {
List<String> newFiles = FileHelper.listFiles(sender, false); List<String> newFiles = BookFileHelper.listFiles(sender, false);
playerBooksList.put(senderName, newFiles); if (newFiles != null) {
playerBooksList.put(playerUUID, newFiles);
playerLetterIndex.put(playerUUID, BookFileHelper.populateLetterIndices(newFiles));
}
} }
return playerBooksList.get(senderName); return new ArrayList<>(playerBooksList.get(playerUUID));
} else {
return new ArrayList<>();
}
}
/**
* Gets the letter index map for public books, or a specific player's books
*
* @param playerIndex <p>The player to get the index for, or null for the public index</p>
* @return <p>An index mapping between a character and the first index containing that character</p>
*/
@NotNull
public static Map<Character, Integer> getLetterIndex(@Nullable UUID playerIndex) {
if (playerIndex == null) {
return publicLetterIndex;
} else {
Map<Character, Integer> letterIndex = playerLetterIndex.get(playerIndex);
return Objects.requireNonNullElseGet(letterIndex, HashMap::new);
} }
} }
@@ -93,15 +130,29 @@ public class BooksWithoutBorders extends JavaPlugin {
* @param sender <p>The sender to update books for</p> * @param sender <p>The sender to update books for</p>
* @param updatePublic <p>Whether to update public books</p> * @param updatePublic <p>Whether to update public books</p>
*/ */
public static void updateBooks(CommandSender sender, boolean updatePublic) { public static void updateBooks(@NotNull CommandSender sender, boolean updatePublic) {
List<String> newFiles = FileHelper.listFiles(sender, updatePublic); List<String> newFiles = BookFileHelper.listFiles(sender, updatePublic);
if (newFiles == null) {
return;
}
if (updatePublic) { if (updatePublic) {
publicBooksList = newFiles; publicBooksList = newFiles;
} else { publicLetterIndex = BookFileHelper.populateLetterIndices(newFiles);
playerBooksList.put(sender.getName(), newFiles); } else if (sender instanceof Player player) {
playerBooksList.put(player.getUniqueId(), newFiles);
playerLetterIndex.put(player.getUniqueId(), BookFileHelper.populateLetterIndices(newFiles));
} }
} }
/**
* Clears book data such as per-player lists and per-player character indexes
*/
public static void clearBookData() {
playerBooksList = new HashMap<>();
playerLetterIndex = new HashMap<>();
}
@Override @Override
public void onEnable() { public void onEnable() {
FileConfiguration config = this.getConfig(); FileConfiguration config = this.getConfig();
@@ -115,8 +166,15 @@ public class BooksWithoutBorders extends JavaPlugin {
booksWithoutBorders = this; booksWithoutBorders = this;
consoleSender = this.getServer().getConsoleSender(); consoleSender = this.getServer().getConsoleSender();
playerBooksList = new HashMap<>(); playerBooksList = new HashMap<>();
playerLetterIndex = new HashMap<>();
BooksWithoutBordersConfig.initialize(this); BooksWithoutBordersConfig.initialize(this);
publicBooksList = FileHelper.listFiles(consoleSender, true); @Nullable List<String> files = BookFileHelper.listFiles(consoleSender, true);
if (files != null) {
publicBooksList = files;
publicLetterIndex = BookFileHelper.populateLetterIndices(files);
}
bookshelfHandler = new BookshelfHandler();
bookshelfHandler.load();
PluginManager pluginManager = this.getServer().getPluginManager(); PluginManager pluginManager = this.getServer().getPluginManager();
@@ -124,6 +182,7 @@ public class BooksWithoutBorders extends JavaPlugin {
pluginManager.registerEvents(new PlayerEventListener(), this); pluginManager.registerEvents(new PlayerEventListener(), this);
pluginManager.registerEvents(new SignEventListener(), this); pluginManager.registerEvents(new SignEventListener(), this);
pluginManager.registerEvents(new BookEventListener(), this); pluginManager.registerEvents(new BookEventListener(), this);
pluginManager.registerEvents(new BookshelfListener(), this);
} else { } else {
this.getPluginLoader().disablePlugin(this); this.getPluginLoader().disablePlugin(this);
} }
@@ -139,6 +198,7 @@ public class BooksWithoutBorders extends JavaPlugin {
* *
* @return <p>An instance of this plugin</p> * @return <p>An instance of this plugin</p>
*/ */
@NotNull
public static BooksWithoutBorders getInstance() { public static BooksWithoutBorders getInstance() {
return booksWithoutBorders; return booksWithoutBorders;
} }
@@ -167,6 +227,9 @@ public class BooksWithoutBorders extends JavaPlugin {
registerCommand("booksWithoutBorders", new CommandBooksWithoutBorders()); registerCommand("booksWithoutBorders", new CommandBooksWithoutBorders());
registerCommand("reload", new CommandReload()); registerCommand("reload", new CommandReload());
registerCommand("formatBook", new CommandFormat()); registerCommand("formatBook", new CommandFormat());
registerCommand("setBookGeneration", new CommandSetGeneration());
registerCommand("clearBook", new CommandClear());
registerCommand("setBookshelfData", new CommandSetBookshelfData());
} }
/** /**
@@ -175,7 +238,7 @@ public class BooksWithoutBorders extends JavaPlugin {
* @param commandName <p>The name of the command to register</p> * @param commandName <p>The name of the command to register</p>
* @param executor <p>The executor to register for the command</p> * @param executor <p>The executor to register for the command</p>
*/ */
private void registerCommand(String commandName, CommandExecutor executor) { private void registerCommand(@NotNull String commandName, @NotNull CommandExecutor executor) {
PluginCommand pluginCommand = this.getCommand(commandName); PluginCommand pluginCommand = this.getCommand(commandName);
if (pluginCommand != null) { if (pluginCommand != null) {
pluginCommand.setExecutor(executor); pluginCommand.setExecutor(executor);
@@ -211,11 +274,21 @@ public class BooksWithoutBorders extends JavaPlugin {
return testFileSaving(); return testFileSaving();
} }
/**
* Gets the bookshelf handler
*
* @return <p>The bookshelf handler</p>
*/
public static BookshelfHandler getBookshelfHandler() {
return bookshelfHandler;
}
/** /**
* Gets the server's item factory * Gets the server's item factory
* *
* @return <p>The server's item factory</p> * @return <p>The server's item factory</p>
*/ */
@NotNull
public static ItemFactory getItemFactory() { public static ItemFactory getItemFactory() {
return itemFactory; return itemFactory;
} }
@@ -234,8 +307,8 @@ public class BooksWithoutBorders extends JavaPlugin {
sendErrorMessage(consoleSender, "Saving failed! Aborting..."); sendErrorMessage(consoleSender, "Saving failed! Aborting...");
return false; return false;
} }
} catch (Exception e) { } catch (Exception exception) {
e.printStackTrace(); BooksWithoutBorders.getInstance().getLogger().log(Level.SEVERE, "Unable to create necessary folders");
return false; return false;
} }
} }
@@ -245,8 +318,8 @@ public class BooksWithoutBorders extends JavaPlugin {
sendErrorMessage(consoleSender, "Saving failed! Aborting..."); sendErrorMessage(consoleSender, "Saving failed! Aborting...");
return false; return false;
} }
} catch (Exception e) { } catch (Exception exception) {
e.printStackTrace(); BooksWithoutBorders.getInstance().getLogger().log(Level.SEVERE, "Unable to create necessary folders");
return false; return false;
} }
} }
@@ -259,7 +332,7 @@ public class BooksWithoutBorders extends JavaPlugin {
* @param sender <p>The sender to send the message to</p> * @param sender <p>The sender to send the message to</p>
* @param message <p>The message to send</p> * @param message <p>The message to send</p>
*/ */
public static void sendSuccessMessage(CommandSender sender, String message) { public static void sendSuccessMessage(@NotNull CommandSender sender, @NotNull String message) {
sender.sendMessage(getSuccessColor() + message); sender.sendMessage(getSuccessColor() + message);
} }
@@ -269,7 +342,7 @@ public class BooksWithoutBorders extends JavaPlugin {
* @param sender <p>The sender to send the message to</p> * @param sender <p>The sender to send the message to</p>
* @param message <p>The message to send</p> * @param message <p>The message to send</p>
*/ */
public static void sendErrorMessage(CommandSender sender, String message) { public static void sendErrorMessage(@NotNull CommandSender sender, @NotNull String message) {
sender.sendMessage(getErrorColor() + message); sender.sendMessage(getErrorColor() + message);
} }

View File

@@ -24,7 +24,7 @@ import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig
public class CommandBooksWithoutBorders implements TabExecutor { public class CommandBooksWithoutBorders implements TabExecutor {
@Override @Override
public boolean onCommand(CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
sender.sendMessage(getCommandColor() + "[] denote optional parameters"); sender.sendMessage(getCommandColor() + "[] denote optional parameters");
sender.sendMessage(getCommandColor() + "<> denote required parameters"); sender.sendMessage(getCommandColor() + "<> denote required parameters");
sender.sendMessage(getCommandColor() + "{} denote required permission"); sender.sendMessage(getCommandColor() + "{} denote required permission");
@@ -42,7 +42,7 @@ public class CommandBooksWithoutBorders implements TabExecutor {
* *
* @param sender <p>The console which sent the command</p> * @param sender <p>The console which sent the command</p>
*/ */
private void showConsoleCommands(CommandSender sender) { private void showConsoleCommands(@NotNull CommandSender sender) {
sender.sendMessage(getCommandColor() + "Commands:"); sender.sendMessage(getCommandColor() + "Commands:");
showCommandInfo("deletePublicBook", sender); showCommandInfo("deletePublicBook", sender);
showCommandInfo("givePublicBook", sender); showCommandInfo("givePublicBook", sender);
@@ -54,7 +54,7 @@ public class CommandBooksWithoutBorders implements TabExecutor {
* *
* @param sender <p>The player which sent the command</p> * @param sender <p>The player which sent the command</p>
*/ */
private void showPlayerCommands(CommandSender sender) { private void showPlayerCommands(@NotNull CommandSender sender) {
//Lists all commands //Lists all commands
Material bookPriceType = BooksWithoutBordersConfig.getBookPriceType(); Material bookPriceType = BooksWithoutBordersConfig.getBookPriceType();
double bookPriceQuantity = BooksWithoutBordersConfig.getBookPriceQuantity(); double bookPriceQuantity = BooksWithoutBordersConfig.getBookPriceQuantity();
@@ -69,6 +69,7 @@ public class CommandBooksWithoutBorders implements TabExecutor {
} }
sender.sendMessage(getCommandColor() + "Commands:"); sender.sendMessage(getCommandColor() + "Commands:");
showCommandInfo("booksWithoutBorders", sender);
showCommandInfo("copyBook", sender); showCommandInfo("copyBook", sender);
showCommandInfo("decryptBook", sender); showCommandInfo("decryptBook", sender);
showCommandInfo("deleteBook", sender); showCommandInfo("deleteBook", sender);
@@ -84,10 +85,13 @@ public class CommandBooksWithoutBorders implements TabExecutor {
showCommandInfo("saveBook", sender); showCommandInfo("saveBook", sender);
showCommandInfo("savePublicBook", sender); showCommandInfo("savePublicBook", sender);
showCommandInfo("setAuthor", sender); showCommandInfo("setAuthor", sender);
showCommandInfo("setBookGeneration", sender);
showCommandInfo("setBookPrice", sender); showCommandInfo("setBookPrice", sender);
showCommandInfo("setLore", sender); showCommandInfo("setLore", sender);
showCommandInfo("setTitle", sender); showCommandInfo("setTitle", sender);
showCommandInfo("unsignBook", sender); showCommandInfo("unsignBook", sender);
showCommandInfo("clearBook", sender);
showCommandInfo("setBookshelfData", sender);
} }
/** /**
@@ -96,7 +100,7 @@ public class CommandBooksWithoutBorders implements TabExecutor {
* @param command <p>The command to get information about</p> * @param command <p>The command to get information about</p>
* @param sender <p>The sender asking to see command info</p> * @param sender <p>The sender asking to see command info</p>
*/ */
private void showCommandInfo(String command, CommandSender sender) { private void showCommandInfo(@NotNull String command, @NotNull CommandSender sender) {
PluginCommand pluginCommand = BooksWithoutBorders.getInstance().getCommand(command); PluginCommand pluginCommand = BooksWithoutBorders.getInstance().getCommand(command);
if (pluginCommand != null) { if (pluginCommand != null) {
String permission = pluginCommand.getPermission(); String permission = pluginCommand.getPermission();
@@ -104,6 +108,9 @@ public class CommandBooksWithoutBorders implements TabExecutor {
String commandInfo = "\n" + getCommandColor() + pluginCommand.getUsage().replace("<command>", String commandInfo = "\n" + getCommandColor() + pluginCommand.getUsage().replace("<command>",
pluginCommand.getName()) + ": " + getSuccessColor() + pluginCommand.getDescription(); pluginCommand.getName()) + ": " + getSuccessColor() + pluginCommand.getDescription();
if (sender.hasPermission("bookswithoutborders.admin")) { if (sender.hasPermission("bookswithoutborders.admin")) {
if (permission == null) {
permission = "None";
}
commandInfo += getCommandColor() + " {" + permission + "}"; commandInfo += getCommandColor() + " {" + permission + "}";
} }
sender.sendMessage(commandInfo); sender.sendMessage(commandInfo);
@@ -112,7 +119,8 @@ public class CommandBooksWithoutBorders implements TabExecutor {
} }
@Override @Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) { public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias,
@NotNull String[] arguments) {
return new ArrayList<>(); 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[] arguments) {
return new ArrayList<>();
}
}

View File

@@ -2,10 +2,12 @@ package net.knarcraft.bookswithoutborders.command;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders; import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig; import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
import net.knarcraft.bookswithoutborders.utility.BookHelper;
import net.knarcraft.bookswithoutborders.utility.EconomyHelper; import net.knarcraft.bookswithoutborders.utility.EconomyHelper;
import net.knarcraft.bookswithoutborders.utility.InputCleaningHelper;
import net.knarcraft.bookswithoutborders.utility.InventoryHelper; import net.knarcraft.bookswithoutborders.utility.InventoryHelper;
import net.knarcraft.bookswithoutborders.utility.TabCompletionHelper; import net.knarcraft.bookswithoutborders.utility.TabCompletionTypeHelper;
import net.knarcraft.knarlib.util.TabCompletionHelper;
import org.bukkit.Material;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor; import org.bukkit.command.TabExecutor;
@@ -24,7 +26,8 @@ import java.util.Objects;
public class CommandCopy implements TabExecutor { public class CommandCopy implements TabExecutor {
@Override @Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
@NotNull String[] arguments) {
if (!(sender instanceof Player player)) { if (!(sender instanceof Player player)) {
BooksWithoutBorders.sendErrorMessage(sender, "This command can only be used by a player!"); BooksWithoutBorders.sendErrorMessage(sender, "This command can only be used by a player!");
return false; return false;
@@ -35,62 +38,113 @@ public class CommandCopy implements TabExecutor {
return false; return false;
} }
if (args.length < 1) { if (arguments.length < 1) {
BooksWithoutBorders.sendErrorMessage(player, "You must specify the number of copies to be made!"); BooksWithoutBorders.sendErrorMessage(player, "You must specify the number of copies to be made!");
return false; return false;
} }
ItemStack heldBook = InventoryHelper.getHeldBook(player, true);
try { try {
int copies = Integer.parseInt(args[0]); ItemStack heldBook = InventoryHelper.getHeldBook(player, true);
if (copies > 0) { int copies = Integer.parseInt(arguments[0]);
if (BooksWithoutBordersConfig.getAuthorOnlyCopy() && !player.hasPermission("bookswithoutborders.bypassAuthorOnlyCopy")) { if (copies <= 0) {
if (!isAuthor(player, (BookMeta) Objects.requireNonNull(heldBook.getItemMeta()))) { throw new NumberFormatException("Number of copies must be larger than 0");
return false;
}
}
if (BooksWithoutBordersConfig.booksHavePrice() &&
!player.hasPermission("bookswithoutborders.bypassBookPrice") &&
EconomyHelper.cannotPayForBookPrinting(player, copies)) {
return false;
}
heldBook.setAmount(heldBook.getAmount() + copies);
BooksWithoutBorders.sendSuccessMessage(player, "Book copied!");
return true;
} }
return performCopy(copies, player, heldBook);
} catch (NumberFormatException ignored) { } catch (NumberFormatException ignored) {
BooksWithoutBorders.sendErrorMessage(player, "Book not copied!");
BooksWithoutBorders.sendErrorMessage(player, "Number specified was invalid!");
return false;
} }
BooksWithoutBorders.sendErrorMessage(player, "Book not copied!");
BooksWithoutBorders.sendErrorMessage(player, "Number specified was invalid!");
return false;
} }
/** /**
* Checks whether the given player is the author of a given book * Performs the actual copying
* *
* @param player <p>The player to check</p> * @param copies <p>The number of copies to be made</p>
* @param book <p>The book to check</p> * @param player <p>The player requesting the copies</p>
* @return <p>True if the player is the book's author</p> * @param heldBook <p>The book to be copied</p>
* @return <p>True if the copying was successful</p>
*/ */
private boolean isAuthor(Player player, BookMeta book) { private boolean performCopy(int copies, @NotNull Player player, @NotNull ItemStack heldBook) {
String author = book.getAuthor(); //Make sure the player owns the book if authorOnlyCopy is enabled
String playerName = InputCleaningHelper.cleanString(player.getName()); if (BooksWithoutBordersConfig.getAuthorOnlyCopy() &&
if (author != null && playerName.equalsIgnoreCase(InputCleaningHelper.cleanString(author))) { !player.hasPermission("bookswithoutborders.bypassAuthorOnlyCopy")) {
return true; if (BookHelper.isNotAuthor(player, (BookMeta) Objects.requireNonNull(heldBook.getItemMeta()))) {
return false;
}
} }
BooksWithoutBorders.sendErrorMessage(player, "You must be the author of this book to use this command!"); BookMeta bookMeta = (BookMeta) heldBook.getItemMeta();
return false; if (BooksWithoutBordersConfig.changeGenerationOnCopy() && bookMeta != null) {
return copyNextGenerationBook(bookMeta, player, copies);
} else {
//Make sure the player can pay for the copying
if (paymentUnSuccessful(player, copies)) {
return false;
}
heldBook.setAmount(heldBook.getAmount() + copies);
BooksWithoutBorders.sendSuccessMessage(player, "Book copied!");
return true;
}
}
/**
* Tries to take payment from a player
*
* @param player <p>The player to take the payment from</p>
* @param copies <p>The number of copies to create for the player</p>
* @return <p>True if the payment failed</p>
*/
private boolean paymentUnSuccessful(@NotNull 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(@NotNull BookMeta bookMeta, @NotNull Player player, int copies) {
//Copy the vanilla behavior of refusing copying any further
if (bookMeta.getGeneration() == BookMeta.Generation.COPY_OF_COPY ||
bookMeta.getGeneration() == BookMeta.Generation.TATTERED) {
BooksWithoutBorders.sendErrorMessage(player, "You cannot copy this book any further. " +
"You must have the original or a direct copy.");
return false;
}
//Make sure the player can fit the book in their inventory
int nextAvailableSlot = player.getInventory().firstEmpty();
if (nextAvailableSlot == -1) {
BooksWithoutBorders.sendErrorMessage(player, "You need an available slot in your inventory.");
return false;
}
//Make sure the player can pay for the copying
if (paymentUnSuccessful(player, copies)) {
return false;
}
ItemStack itemStack = new ItemStack(Material.WRITTEN_BOOK);
itemStack.setItemMeta(bookMeta);
//Increase the generation of the book
BookHelper.increaseGeneration(itemStack);
itemStack.setAmount(copies);
player.getInventory().addItem(itemStack);
return true;
} }
@Override @Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) { public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias,
int argumentCount = args.length; @NotNull String[] arguments) {
int argumentCount = arguments.length;
if (argumentCount == 1) { if (argumentCount == 1) {
return TabCompletionHelper.filterMatchingStartsWith(TabCompletionHelper.getNumbers(1, 20), args[0]); return TabCompletionHelper.filterMatchingStartsWith(TabCompletionTypeHelper.getNumbers(1, 20), arguments[0]);
} }
return new ArrayList<>(); return new ArrayList<>();
} }

View File

@@ -2,6 +2,7 @@ package net.knarcraft.bookswithoutborders.command;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders; import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig; import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
import net.knarcraft.bookswithoutborders.utility.BookHelper;
import net.knarcraft.bookswithoutborders.utility.EncryptionHelper; import net.knarcraft.bookswithoutborders.utility.EncryptionHelper;
import net.knarcraft.bookswithoutborders.utility.InventoryHelper; import net.knarcraft.bookswithoutborders.utility.InventoryHelper;
import org.bukkit.command.Command; import org.bukkit.command.Command;
@@ -47,12 +48,6 @@ public class CommandDecrypt implements TabExecutor {
//Warning: admin decrypt only allows decrypting files created by the same player. Not sure if intended //Warning: admin decrypt only allows decrypting files created by the same player. Not sure if intended
if (args.length == 0 && BooksWithoutBordersConfig.getAdminDecrypt() && player.hasPermission("bookswithoutborders.admin")) { if (args.length == 0 && BooksWithoutBordersConfig.getAdminDecrypt() && player.hasPermission("bookswithoutborders.admin")) {
String path = getBookFolder() + "Encrypted" + getSlash(); String path = getBookFolder() + "Encrypted" + getSlash();
String fileName;
if (bookMetadata.hasTitle()) {
fileName = bookMetadata.getTitle() + BooksWithoutBordersConfig.getTitleAuthorSeparator() + bookMetadata.getAuthor();
} else {
fileName = "Untitled," + player.getName();
}
File encryptedDirectory = new File(path); File encryptedDirectory = new File(path);
String[] encryptedFiles = encryptedDirectory.list(); String[] encryptedFiles = encryptedDirectory.list();
@@ -64,7 +59,7 @@ public class CommandDecrypt implements TabExecutor {
//Get the "encryption key" from the filename //Get the "encryption key" from the filename
String key = ""; String key = "";
for (String encryptedFile : encryptedFiles) { for (String encryptedFile : encryptedFiles) {
if (encryptedFile.contains(fileName)) { if (encryptedFile.contains(BookHelper.getBookFile(bookMetadata, player, true))) {
key = encryptedFile.substring(encryptedFile.indexOf("[") + 1, encryptedFile.indexOf("]")); key = encryptedFile.substring(encryptedFile.indexOf("[") + 1, encryptedFile.indexOf("]"));
break; break;
} }
@@ -104,8 +99,9 @@ public class CommandDecrypt implements TabExecutor {
} }
@Override @Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) { public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias,
int argumentCount = args.length; @NotNull String[] arguments) {
int argumentCount = arguments.length;
if (argumentCount == 1) { if (argumentCount == 1) {
List<String> info = new ArrayList<>(); List<String> info = new ArrayList<>();
info.add("<password>"); info.add("<password>");

View File

@@ -1,9 +1,11 @@
package net.knarcraft.bookswithoutborders.command; package net.knarcraft.bookswithoutborders.command;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders; import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.utility.FileHelper; import net.knarcraft.bookswithoutborders.gui.PagedBookIndex;
import net.knarcraft.bookswithoutborders.utility.InputCleaningHelper; import net.knarcraft.bookswithoutborders.state.BookDirectory;
import net.knarcraft.bookswithoutborders.utility.TabCompletionHelper; 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.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor; import org.bukkit.command.TabExecutor;
@@ -14,43 +16,41 @@ import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getBookFolder;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getSlash;
/** /**
* Command executor for the delete command * Command executor for the delete command
*/ */
public class CommandDelete implements TabExecutor { public class CommandDelete implements TabExecutor {
@Override @Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
@NotNull String[] arguments) {
if (!(sender instanceof Player)) { if (!(sender instanceof Player)) {
BooksWithoutBorders.sendErrorMessage(sender, "This command can only be used by a player!"); BooksWithoutBorders.sendErrorMessage(sender, "This command can only be used by a player!");
return false; return false;
} }
return deleteBook(sender, args, false); return deleteBook(sender, arguments, false);
} }
/** /**
* Deletes a book * Deletes a book
* *
* @param sender <p>The sender trying to delete the book</p> * @param sender <p>The sender trying to delete the book</p>
* @param args <p>The arguments given</p> * @param arguments <p>The arguments given</p>
* @param deletePublic <p>Whether to delete a public book</p> * @param deletePublic <p>Whether to delete a public book</p>
* @return <p>True if the book was deleted successfully</p> * @return <p>True if the book was deleted successfully</p>
*/ */
boolean deleteBook(CommandSender sender, String[] args, boolean deletePublic) { protected boolean deleteBook(@NotNull CommandSender sender, @NotNull String[] arguments, boolean deletePublic) {
//List deletable files String command = deletePublic ? "deletepublicbook" : "deletebook";
if (args.length == 0) { if (PagedBookIndex.displayPage(arguments, sender, deletePublic, command)) {
FileHelper.printBooks(sender, deletePublic);
return true; return true;
} }
//Delete the file //Delete the file
if (args.length == 1) { if (arguments.length == 1) {
List<String> availableBooks = BooksWithoutBorders.getAvailableBooks(sender, deletePublic); List<String> availableBooks = BooksWithoutBorders.getAvailableBooks(sender, deletePublic);
if (!availableBooks.isEmpty()) { if (!availableBooks.isEmpty()) {
performBookDeletion(sender, args[0], deletePublic); performBookDeletion(sender, arguments[0], deletePublic);
//Update the book list //Update the book list
BooksWithoutBorders.updateBooks(sender, deletePublic); BooksWithoutBorders.updateBooks(sender, deletePublic);
return true; return true;
@@ -70,7 +70,7 @@ public class CommandDelete implements TabExecutor {
* @param fileName <p>The file name of the book</p> * @param fileName <p>The file name of the book</p>
* @param isPublic <p>Whether the book to delete is public or not</p> * @param isPublic <p>Whether the book to delete is public or not</p>
*/ */
public void performBookDeletion(CommandSender sender, String fileName, Boolean isPublic) { public void performBookDeletion(@NotNull CommandSender sender, @NotNull String fileName, @NotNull Boolean isPublic) {
//If the file name is an index of the load list, load the book //If the file name is an index of the load list, load the book
try { try {
int loadListIndex = Integer.parseInt(fileName); int loadListIndex = Integer.parseInt(fileName);
@@ -82,13 +82,9 @@ public class CommandDelete implements TabExecutor {
} }
//Get the file to be deleted //Get the file to be deleted
File file; String bookDirectory = BookHelper.getBookDirectoryPathString(
if (isPublic) { isPublic ? BookDirectory.PUBLIC : BookDirectory.PLAYER, sender);
file = FileHelper.getBookFile(getBookFolder() + fileName); File file = BookFileHelper.getBookFile(bookDirectory + fileName);
} else {
file = FileHelper.getBookFile(getBookFolder() +
InputCleaningHelper.cleanString(sender.getName()) + getSlash() + fileName);
}
//Send message if no such file could be found //Send message if no such file could be found
if (file == null) { if (file == null) {
@@ -109,8 +105,9 @@ public class CommandDelete implements TabExecutor {
} }
@Override @Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) { public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias,
return doTabCompletion(sender, args, false); @NotNull String[] arguments) {
return doTabCompletion(sender, arguments, false);
} }
/** /**
@@ -121,7 +118,8 @@ public class CommandDelete implements TabExecutor {
* @param deletePublic <p>Whether to delete a public book</p> * @param deletePublic <p>Whether to delete a public book</p>
* @return <p>A list of available arguments</p> * @return <p>A list of available arguments</p>
*/ */
protected List<String> doTabCompletion(CommandSender sender, String[] args, boolean deletePublic) { @NotNull
protected List<String> doTabCompletion(@NotNull CommandSender sender, @NotNull String[] args, boolean deletePublic) {
int argumentCount = args.length; int argumentCount = args.length;
if (argumentCount == 1) { if (argumentCount == 1) {
return TabCompletionHelper.filterMatchingContains(BooksWithoutBorders.getAvailableBooks(sender, deletePublic), return TabCompletionHelper.filterMatchingContains(BooksWithoutBorders.getAvailableBooks(sender, deletePublic),

View File

@@ -13,13 +13,13 @@ import java.util.List;
public class CommandDeletePublic extends CommandDelete implements TabExecutor { public class CommandDeletePublic extends CommandDelete implements TabExecutor {
@Override @Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) { public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] arguments) {
return deleteBook(sender, args, true); return deleteBook(sender, arguments, true);
} }
@Override @Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, String[] args) { public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, String[] arguments) {
return doTabCompletion(sender, args, true); return doTabCompletion(sender, arguments, true);
} }
} }

View File

@@ -5,7 +5,7 @@ import net.knarcraft.bookswithoutborders.state.EncryptionStyle;
import net.knarcraft.bookswithoutborders.state.ItemSlot; import net.knarcraft.bookswithoutborders.state.ItemSlot;
import net.knarcraft.bookswithoutborders.utility.EncryptionHelper; import net.knarcraft.bookswithoutborders.utility.EncryptionHelper;
import net.knarcraft.bookswithoutborders.utility.InventoryHelper; import net.knarcraft.bookswithoutborders.utility.InventoryHelper;
import net.knarcraft.bookswithoutborders.utility.TabCompletionHelper; import net.knarcraft.knarlib.util.TabCompletionHelper;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor; import org.bukkit.command.TabExecutor;
@@ -13,6 +13,7 @@ import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.BookMeta; import org.bukkit.inventory.meta.BookMeta;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -23,25 +24,28 @@ import java.util.List;
public class CommandEncrypt implements TabExecutor { public class CommandEncrypt implements TabExecutor {
@Override @Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
if (performPreChecks(sender, args, 1, "You must specify a key to encrypt a book!") == null) { @NotNull String[] arguments) {
if (performPreChecks(sender, arguments, 1, "You must specify a key to encrypt a book!") == null) {
return false; return false;
} }
EncryptionStyle encryptionStyle = args.length == 2 ? EncryptionStyle.getFromString(args[1]) : EncryptionStyle.SUBSTITUTION; EncryptionStyle encryptionStyle = arguments.length == 2 ? EncryptionStyle.getFromString(arguments[1]) : EncryptionStyle.SUBSTITUTION;
return encryptBook(encryptionStyle, (Player) sender, args[0], ""); return encryptBook(encryptionStyle, (Player) sender, arguments[0], "");
} }
/** /**
* Performs necessary pre-checks before going through with the encryption * Performs necessary pre-checks before going through with the encryption
* *
* @param sender <p>The sender trying to encrypt a book</p> * @param sender <p>The sender trying to encrypt a book</p>
* @param args <p>The arguments given</p> * @param arguments <p>The arguments given</p>
* @param necessaryArguments <p>How many arguments is the minimum requirement</p> * @param necessaryArguments <p>How many arguments is the minimum requirement</p>
* @param missingArgumentsError <p>The error to show if a required argument is missing</p> * @param missingArgumentsError <p>The error to show if a required argument is missing</p>
* @return <p>The metadata of the book to encrypt, or null if any checks fail</p> * @return <p>The metadata of the book to encrypt, or null if any checks fail</p>
*/ */
BookMeta performPreChecks(CommandSender sender, String[] args, int necessaryArguments, String missingArgumentsError) { @Nullable
protected BookMeta performPreChecks(@NotNull CommandSender sender, @NotNull String[] arguments,
int necessaryArguments, @NotNull String missingArgumentsError) {
if (!(sender instanceof Player player)) { if (!(sender instanceof Player player)) {
BooksWithoutBorders.sendErrorMessage(sender, "This command can only be used by a player!"); BooksWithoutBorders.sendErrorMessage(sender, "This command can only be used by a player!");
return null; return null;
@@ -53,7 +57,7 @@ public class CommandEncrypt implements TabExecutor {
return null; return null;
} }
int argumentCount = args.length; int argumentCount = arguments.length;
if (argumentCount < necessaryArguments) { if (argumentCount < necessaryArguments) {
BooksWithoutBorders.sendErrorMessage(player, missingArgumentsError); BooksWithoutBorders.sendErrorMessage(player, missingArgumentsError);
return null; return null;
@@ -85,7 +89,8 @@ public class CommandEncrypt implements TabExecutor {
* @param group <p>The group to encrypt for</p> * @param group <p>The group to encrypt for</p>
* @return <p>True if the book was encrypted successfully</p> * @return <p>True if the book was encrypted successfully</p>
*/ */
boolean encryptBook(EncryptionStyle encryptionStyle, Player player, String key, String group) { protected boolean encryptBook(@NotNull EncryptionStyle encryptionStyle, @NotNull Player player, @NotNull String key,
@NotNull String group) {
ItemSlot heldSlot = InventoryHelper.getHeldSlotBook(player, false, false, true, true); ItemSlot heldSlot = InventoryHelper.getHeldSlotBook(player, false, false, true, true);
ItemStack encryptedBook = EncryptionHelper.encryptBook(player, heldSlot == ItemSlot.MAIN_HAND, key, encryptionStyle, group); ItemStack encryptedBook = EncryptionHelper.encryptBook(player, heldSlot == ItemSlot.MAIN_HAND, key, encryptionStyle, group);
@@ -98,8 +103,10 @@ public class CommandEncrypt implements TabExecutor {
} }
@Override @Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, String[] args) { @NotNull
return doTabCompletion(args, false); public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias,
@NotNull String[] arguments) {
return doTabCompletion(arguments, false);
} }
/** /**
@@ -109,7 +116,8 @@ public class CommandEncrypt implements TabExecutor {
* @param groupEncrypt <p>Whether to auto-complete for group encryption</p> * @param groupEncrypt <p>Whether to auto-complete for group encryption</p>
* @return <p>The strings to auto-complete</p> * @return <p>The strings to auto-complete</p>
*/ */
protected List<String> doTabCompletion(String[] args, boolean groupEncrypt) { @NotNull
protected List<String> doTabCompletion(@NotNull String[] args, boolean groupEncrypt) {
int argumentsCount = args.length; int argumentsCount = args.length;
List<String> encryptionStyles = new ArrayList<>(); List<String> encryptionStyles = new ArrayList<>();

View File

@@ -20,7 +20,8 @@ import java.util.List;
public class CommandFormat implements TabExecutor { public class CommandFormat implements TabExecutor {
@Override @Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
@NotNull String[] arguments) {
if (!(sender instanceof Player player)) { if (!(sender instanceof Player player)) {
BooksWithoutBorders.sendErrorMessage(sender, "This command can only be used by a player!"); BooksWithoutBorders.sendErrorMessage(sender, "This command can only be used by a player!");
return false; return false;
@@ -32,7 +33,14 @@ public class CommandFormat implements TabExecutor {
} }
ItemStack heldBook = InventoryHelper.getHeldBook(player, true); ItemStack heldBook = InventoryHelper.getHeldBook(player, true);
heldBook.setItemMeta(BookFormatter.formatPages((BookMeta) heldBook.getItemMeta())); BookMeta meta = (BookMeta) heldBook.getItemMeta();
if (meta == null) {
BooksWithoutBorders.sendErrorMessage(sender, "Unable to get metadata from the held book!");
return false;
}
heldBook.setItemMeta(BookFormatter.formatPages(meta));
BooksWithoutBorders.sendSuccessMessage(sender, "Book formatted!"); BooksWithoutBorders.sendSuccessMessage(sender, "Book formatted!");
@@ -40,7 +48,8 @@ public class CommandFormat implements TabExecutor {
} }
@Override @Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) { public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias,
@NotNull String[] arguments) {
return new ArrayList<>(); return new ArrayList<>();
} }

View File

@@ -1,10 +1,11 @@
package net.knarcraft.bookswithoutborders.command; package net.knarcraft.bookswithoutborders.command;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders; import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.gui.PagedBookIndex;
import net.knarcraft.bookswithoutborders.utility.BookLoader; import net.knarcraft.bookswithoutborders.utility.BookLoader;
import net.knarcraft.bookswithoutborders.utility.FileHelper;
import net.knarcraft.bookswithoutborders.utility.InputCleaningHelper; 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.Server;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
@@ -12,6 +13,7 @@ import org.bukkit.command.TabExecutor;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -24,51 +26,67 @@ public class CommandGive implements TabExecutor {
private final BooksWithoutBorders booksWithoutBorders = BooksWithoutBorders.getInstance(); private final BooksWithoutBorders booksWithoutBorders = BooksWithoutBorders.getInstance();
@Override @Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
@NotNull String[] arguments) {
if (!(sender instanceof Player)) { if (!(sender instanceof Player)) {
BooksWithoutBorders.sendErrorMessage(sender, "This command can only be used by a player!"); BooksWithoutBorders.sendErrorMessage(sender, "This command can only be used by a player!");
return false; return false;
} }
return giveBook(sender, args, false, "player"); return giveBook(sender, arguments, false, "player");
} }
/** /**
* Gives a book to another player * Gives a book to another player
* *
* @param sender <p>The sender trying to give a book</p> * @param sender <p>The sender trying to give a book</p>
* @param args <p>The arguments given</p> * @param arguments <p>The arguments given</p>
* @param givePublic <p>Whether to give a public book</p> * @param givePublic <p>Whether to give a public book</p>
* @param folder <p>The folder containing the book to load</p> * @param folder <p>The folder containing the book to load</p>
* @return <p>True if the book was given successfully</p> * @return <p>True if the book was given successfully</p>
*/ */
boolean giveBook(CommandSender sender, String[] args, boolean givePublic, String folder) { boolean giveBook(@NotNull CommandSender sender, @NotNull String[] arguments, boolean givePublic,
if (args.length == 1 || args.length > 4) { @NotNull String folder) {
String command = givePublic ? "givepublicbook" : "givebook";
if (PagedBookIndex.displayPage(arguments, sender, givePublic, command)) {
return true;
}
if (arguments.length == 1 || arguments.length > 4) {
BooksWithoutBorders.sendErrorMessage(sender, "Incorrect number of arguments for this command!"); BooksWithoutBorders.sendErrorMessage(sender, "Incorrect number of arguments for this command!");
return false; return false;
} }
if (args.length == 0) {
FileHelper.printBooks(sender, givePublic);
return true;
}
//Organize and parse input //Organize and parse input
String bookIdentifier = args[0]; String bookIdentifier = arguments[0];
String receivingPlayerName = args[1]; String receivingPlayerName = arguments[1];
String copies = "1"; String copies = "1";
String isSigned = "true"; String isSigned = "true";
if (args.length == 4) { if (arguments.length == 4) {
copies = args[2]; copies = arguments[2];
isSigned = args[3]; isSigned = arguments[3];
} else if (args.length == 3) { } else if (arguments.length == 3) {
if (args[2].equalsIgnoreCase("true") || args[2].equalsIgnoreCase("false")) { if (arguments[2].equalsIgnoreCase("true") || arguments[2].equalsIgnoreCase("false")) {
isSigned = args[2]; isSigned = arguments[2];
} else { } else {
copies = args[2]; copies = arguments[2];
} }
} }
//Try and find the target player
Player receivingPlayer = booksWithoutBorders.getServer().getPlayer(receivingPlayerName);
if (receivingPlayer == null) {
BooksWithoutBorders.sendErrorMessage(sender, "Player not found!");
return false;
}
//Make sure the receiver is able to fit the book
if (receivingPlayer.getInventory().firstEmpty() == -1) {
BooksWithoutBorders.sendErrorMessage(sender, "Receiving player must have space in their inventory" +
" to receive books!");
return false;
}
//Load books available to the player //Load books available to the player
try { try {
Integer.parseInt(bookIdentifier); Integer.parseInt(bookIdentifier);
@@ -76,29 +94,8 @@ public class CommandGive implements TabExecutor {
} catch (NumberFormatException ignored) { } catch (NumberFormatException ignored) {
} }
Player receivingPlayer = booksWithoutBorders.getServer().getPlayer(receivingPlayerName);
if (receivingPlayer == null) {
BooksWithoutBorders.sendErrorMessage(sender, "Player not found!");
return false;
}
if (receivingPlayer.getInventory().firstEmpty() == -1) {
BooksWithoutBorders.sendErrorMessage(sender, "Receiving player must have space in their inventory to receive books!");
return false;
}
String bookToLoad = InputCleaningHelper.cleanString(bookIdentifier);
try { try {
ItemStack newBook = BookLoader.loadBook(sender, bookToLoad, isSigned, folder, Integer.parseInt(copies)); return loadAndGiveBook(bookIdentifier, sender, receivingPlayer, isSigned, folder, copies);
if (newBook != null) {
receivingPlayer.getInventory().addItem(newBook);
BooksWithoutBorders.sendSuccessMessage(sender, "Book sent!");
BooksWithoutBorders.sendSuccessMessage(receivingPlayer, "Book received!");
return true;
} else {
BooksWithoutBorders.sendErrorMessage(sender, "Book failed to load!");
return false;
}
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
BooksWithoutBorders.sendErrorMessage(sender, "Invalid number of book copies specified!"); BooksWithoutBorders.sendErrorMessage(sender, "Invalid number of book copies specified!");
return false; return false;
@@ -106,8 +103,9 @@ public class CommandGive implements TabExecutor {
} }
@Override @Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) { public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias,
return doTabCompletion(sender, args, false); @NotNull String[] arguments) {
return doTabCompletion(sender, arguments, false);
} }
/** /**
@@ -118,7 +116,8 @@ public class CommandGive implements TabExecutor {
* @param listPublic <p>Whether to list public files or player files</p> * @param listPublic <p>Whether to list public files or player files</p>
* @return <p>A list of available choices</p> * @return <p>A list of available choices</p>
*/ */
protected List<String> doTabCompletion(CommandSender sender, String[] args, boolean listPublic) { @Nullable
protected List<String> doTabCompletion(@NotNull CommandSender sender, @NotNull String[] args, boolean listPublic) {
Server server = booksWithoutBorders.getServer(); Server server = booksWithoutBorders.getServer();
int argumentCount = args.length; int argumentCount = args.length;
@@ -139,12 +138,12 @@ public class CommandGive implements TabExecutor {
return null; return null;
} else if (argumentCount == 3) { } else if (argumentCount == 3) {
//Number of copies //Number of copies
return TabCompletionHelper.filterMatchingStartsWith(TabCompletionHelper.getBooleansAndNumbers(1, 3), args[2]); return TabCompletionHelper.filterMatchingStartsWith(TabCompletionTypeHelper.getBooleansAndNumbers(1, 3), args[2]);
} else if (argumentCount == 4) { } else if (argumentCount == 4) {
//Signed //Signed
try { try {
Integer.parseInt(args[2]); Integer.parseInt(args[2]);
return TabCompletionHelper.filterMatchingStartsWith(TabCompletionHelper.getBooleans(), args[3]); return TabCompletionHelper.filterMatchingStartsWith(TabCompletionTypeHelper.getBooleans(), args[3]);
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
return new ArrayList<>(); return new ArrayList<>();
} }
@@ -152,4 +151,32 @@ public class CommandGive implements TabExecutor {
return new ArrayList<>(); return new ArrayList<>();
} }
/**
* Loads a book and gives it to the correct player
*
* @param bookIdentifier <p>The file name specified by the user</p>
* @param sender <p>The player trying to give the book</p>
* @param receivingPlayer <p>The player which is the receiver of the book</p>
* @param isSigned <p>The value given for if the given book should be signed or not</p>
* @param folder <p>The folder containing the book to load</p>
* @param copies <p>The number of copies the player wants to give</p>
* @return <p>True if the book was successfully given</p>
*/
private boolean loadAndGiveBook(@NotNull String bookIdentifier, @NotNull CommandSender sender,
@NotNull Player receivingPlayer, @NotNull String isSigned, @NotNull String folder,
@NotNull 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

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

View File

@@ -17,8 +17,9 @@ import java.util.List;
public class CommandGroupEncrypt extends CommandEncrypt implements TabExecutor { public class CommandGroupEncrypt extends CommandEncrypt implements TabExecutor {
@Override @Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) { public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
BookMeta bookMetadata = performPreChecks(sender, args, 2, @NotNull String[] arguments) {
BookMeta bookMetadata = performPreChecks(sender, arguments, 2,
"You must specify a group name and key to encrypt a book!"); "You must specify a group name and key to encrypt a book!");
if (bookMetadata == null) { if (bookMetadata == null) {
@@ -32,13 +33,14 @@ public class CommandGroupEncrypt extends CommandEncrypt implements TabExecutor {
return false; return false;
} }
EncryptionStyle encryptionStyle = args.length == 3 ? EncryptionStyle.getFromString(args[2]) : EncryptionStyle.SUBSTITUTION; EncryptionStyle encryptionStyle = arguments.length == 3 ? EncryptionStyle.getFromString(arguments[2]) : EncryptionStyle.SUBSTITUTION;
return encryptBook(encryptionStyle, (Player) sender, args[1], args[0]); return encryptBook(encryptionStyle, (Player) sender, arguments[1], arguments[0]);
} }
@Override @Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, String[] args) { public @NotNull List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command,
return doTabCompletion(args, true); @NotNull String alias, @NotNull String[] arguments) {
return doTabCompletion(arguments, true);
} }
} }

View File

@@ -1,10 +1,11 @@
package net.knarcraft.bookswithoutborders.command; package net.knarcraft.bookswithoutborders.command;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders; import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.gui.PagedBookIndex;
import net.knarcraft.bookswithoutborders.utility.BookLoader; import net.knarcraft.bookswithoutborders.utility.BookLoader;
import net.knarcraft.bookswithoutborders.utility.FileHelper;
import net.knarcraft.bookswithoutborders.utility.InputCleaningHelper; 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.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor; import org.bukkit.command.TabExecutor;
@@ -21,20 +22,22 @@ import java.util.List;
public class CommandLoad implements TabExecutor { public class CommandLoad implements TabExecutor {
@Override @Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
return loadBook(sender, args, "player", false); @NotNull String[] arguments) {
return loadBook(sender, arguments, "player", false);
} }
/** /**
* Loads a stored book * Loads a stored book
* *
* @param sender <p>The sender asking to load the book</p> * @param sender <p>The sender asking to load the book</p>
* @param args <p>The arguments given</p> * @param arguments <p>The arguments given</p>
* @param directory <p>The directory to load from (public/player)</p> * @param directory <p>The directory to load from (public/player)</p>
* @param loadPublic <p>Whether to list public files as loadable</p> * @param loadPublic <p>Whether to list public files as loadable</p>
* @return <p>True if the book was loaded successfully</p> * @return <p>True if the book was loaded successfully</p>
*/ */
boolean loadBook(CommandSender sender, String[] args, String directory, boolean loadPublic) { public boolean loadBook(@NotNull CommandSender sender, @NotNull String[] arguments, @NotNull String directory,
boolean loadPublic) {
if (!(sender instanceof Player player)) { if (!(sender instanceof Player player)) {
BooksWithoutBorders.sendErrorMessage(sender, "This command can only be used by a player!"); BooksWithoutBorders.sendErrorMessage(sender, "This command can only be used by a player!");
return false; return false;
@@ -45,26 +48,25 @@ public class CommandLoad implements TabExecutor {
return false; return false;
} }
int argumentCount = args.length; int argumentCount = arguments.length;
//Show books available to the player String command = loadPublic ? "loadpublicbook" : "loadbook";
if (argumentCount == 0) { if (PagedBookIndex.displayPage(arguments, sender, loadPublic, command)) {
FileHelper.printBooks(sender, loadPublic);
return true; return true;
} }
//Organize and parse input //Organize and parse input
String bookIdentifier = args[0]; String bookIdentifier = arguments[0];
String copies = "1"; String copies = "1";
String isSigned = "true"; String isSigned = "true";
if (args.length == 3) { if (argumentCount == 3) {
copies = args[1]; copies = arguments[1];
isSigned = args[2]; isSigned = arguments[2];
} else if (args.length == 2) { } else if (argumentCount == 2) {
if (args[1].equalsIgnoreCase("true") || args[1].equalsIgnoreCase("false")) { if (arguments[1].equalsIgnoreCase("true") || arguments[1].equalsIgnoreCase("false")) {
isSigned = args[1]; isSigned = arguments[1];
} else { } else {
copies = args[1]; copies = arguments[1];
} }
} }
@@ -94,32 +96,34 @@ public class CommandLoad implements TabExecutor {
} }
@Override @Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) { public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias,
return doTabCompletion(sender, args, false); @NotNull String[] arguments) {
return doTabCompletion(sender, arguments, false);
} }
/** /**
* Performs the actual tab completion * Performs the actual tab completion
* *
* @param sender <p>The sender of the command</p> * @param sender <p>The sender of the command</p>
* @param args <p>The arguments given</p> * @param arguments <p>The arguments given</p>
* @param loadPublic <p>Whether to list public files or player files</p> * @param loadPublic <p>Whether to list public files or player files</p>
* @return <p>A list of available choices</p> * @return <p>A list of available choices</p>
*/ */
protected List<String> doTabCompletion(CommandSender sender, String[] args, boolean loadPublic) { @NotNull
int argumentCount = args.length; protected List<String> doTabCompletion(@NotNull CommandSender sender, @NotNull String[] arguments, boolean loadPublic) {
int argumentCount = arguments.length;
if (argumentCount == 1) { if (argumentCount == 1) {
//Return list of books //Return list of books
return TabCompletionHelper.filterMatchingContains(BooksWithoutBorders.getAvailableBooks(sender, loadPublic), return TabCompletionHelper.filterMatchingContains(BooksWithoutBorders.getAvailableBooks(sender, loadPublic),
args[0]); arguments[0]);
} else if (argumentCount == 2) { } else if (argumentCount == 2) {
//Number of copies //Number of copies
return TabCompletionHelper.filterMatchingStartsWith(TabCompletionHelper.getBooleansAndNumbers(1, 3), args[1]); return TabCompletionHelper.filterMatchingStartsWith(TabCompletionTypeHelper.getBooleansAndNumbers(1, 3), arguments[1]);
} else if (argumentCount == 3) { } else if (argumentCount == 3) {
//Signed //Signed
try { try {
Integer.parseInt(args[1]); Integer.parseInt(arguments[1]);
return TabCompletionHelper.filterMatchingStartsWith(TabCompletionHelper.getBooleans(), args[2]); return TabCompletionHelper.filterMatchingStartsWith(TabCompletionTypeHelper.getBooleans(), arguments[2]);
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
return new ArrayList<>(); return new ArrayList<>();
} }

View File

@@ -13,13 +13,15 @@ import java.util.List;
public class CommandLoadPublic extends CommandLoad implements CommandExecutor { public class CommandLoadPublic extends CommandLoad implements CommandExecutor {
@Override @Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) { public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
return loadBook(sender, args, "public", true); @NotNull String[] arguments) {
return loadBook(sender, arguments, "public", true);
} }
@Override @Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, String[] args) { public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias,
return doTabCompletion(sender, args, true); @NotNull String[] arguments) {
return doTabCompletion(sender, arguments, true);
} }
} }

View File

@@ -16,18 +16,24 @@ import java.util.List;
public class CommandReload implements TabExecutor { public class CommandReload implements TabExecutor {
@Override @Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
@NotNull String[] arguments) {
if (BooksWithoutBordersConfig.loadConfig()) { if (BooksWithoutBordersConfig.loadConfig()) {
BooksWithoutBorders.sendSuccessMessage(sender, "BooksWithoutBorders configuration reloaded!"); BooksWithoutBorders.sendSuccessMessage(sender, "BooksWithoutBorders configuration reloaded!");
} else { } else {
BooksWithoutBorders.sendErrorMessage(sender, "Reload Failed!"); BooksWithoutBorders.sendErrorMessage(sender, "Reload Failed!");
BooksWithoutBorders.sendErrorMessage(sender, "See console for details"); BooksWithoutBorders.sendErrorMessage(sender, "See console for details");
} }
// Reload books
BooksWithoutBorders.updateBooks(sender, true);
BooksWithoutBorders.clearBookData();
return true; return true;
} }
@Override @Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) { public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias,
@NotNull String[] arguments) {
return new ArrayList<>(); return new ArrayList<>();
} }

View File

@@ -2,10 +2,13 @@ package net.knarcraft.bookswithoutborders.command;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders; import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig; import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
import net.knarcraft.bookswithoutborders.state.BookDirectory;
import net.knarcraft.bookswithoutborders.state.ItemSlot; 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.BookToFromTextHelper;
import net.knarcraft.bookswithoutborders.utility.FileHelper;
import net.knarcraft.bookswithoutborders.utility.InventoryHelper; import net.knarcraft.bookswithoutborders.utility.InventoryHelper;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor; import org.bukkit.command.TabExecutor;
@@ -18,13 +21,11 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.logging.Level;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getBookFolder;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getCommandColor; import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getCommandColor;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getErrorColor; import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getErrorColor;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getSlash; import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getTitleAuthorSeparator;
import static net.knarcraft.bookswithoutborders.utility.InputCleaningHelper.cleanString;
import static net.knarcraft.bookswithoutborders.utility.InputCleaningHelper.fixName;
/** /**
* Command executor for the save command * Command executor for the save command
@@ -32,28 +33,28 @@ import static net.knarcraft.bookswithoutborders.utility.InputCleaningHelper.fixN
public class CommandSave implements TabExecutor { public class CommandSave implements TabExecutor {
@Override @Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) { public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] arguments) {
return saveHeldBook(sender, args, false); return saveHeldBook(sender, arguments, false);
} }
/** /**
* Saves the player's held book if it exists * Saves the player's held book if it exists
* *
* @param sender <p>The sender of the command</p> * @param sender <p>The sender of the command</p>
* @param args <p>The arguments given</p> * @param arguments <p>The arguments given</p>
* @param savePublic <p>Whether to save the book in the public directory or the player directory</p> * @param savePublic <p>Whether to save the book in the public directory or the player directory</p>
* @return <p>True if a book was saved successfully</p> * @return <p>True if a book was saved successfully</p>
*/ */
boolean saveHeldBook(CommandSender sender, String[] args, boolean savePublic) { boolean saveHeldBook(@NotNull CommandSender sender, @NotNull String[] arguments, boolean savePublic) {
if (!(sender instanceof Player player)) { if (!(sender instanceof Player player)) {
BooksWithoutBorders.sendErrorMessage(sender, "This command can only be used by a player!"); BooksWithoutBorders.sendErrorMessage(sender, "This command can only be used by a player!");
return false; return false;
} }
ItemSlot holdingSlot = InventoryHelper.getHeldSlotBook(player, false, false, false, false); ItemSlot holdingSlot = InventoryHelper.getHeldSlotBook(player, false, false, false, false);
if (holdingSlot != null && holdingSlot != ItemSlot.NONE) { if (holdingSlot != ItemSlot.NONE) {
ItemStack holdingItem = InventoryHelper.getHeldItem(player, holdingSlot == ItemSlot.MAIN_HAND); ItemStack holdingItem = InventoryHelper.getHeldItem(player, holdingSlot == ItemSlot.MAIN_HAND);
boolean duplicate = args.length == 1 && Boolean.parseBoolean(args[0]); boolean duplicate = arguments.length == 1 && Boolean.parseBoolean(arguments[0]);
saveBook(player, holdingItem, duplicate, savePublic); saveBook(player, holdingItem, duplicate, savePublic);
return true; return true;
} else { } else {
@@ -70,64 +71,76 @@ public class CommandSave implements TabExecutor {
* @param overwrite <p>Whether to overwrite any existing books</p> * @param overwrite <p>Whether to overwrite any existing books</p>
* @param saveToPublicFolder <p>Whether to save the book to the public folder instead of the player folder</p> * @param saveToPublicFolder <p>Whether to save the book to the public folder instead of the player folder</p>
*/ */
public void saveBook(Player player, ItemStack heldBook, boolean overwrite, boolean saveToPublicFolder) { public void saveBook(@NotNull Player player, @NotNull ItemStack heldBook, boolean overwrite, boolean saveToPublicFolder) {
BookMeta book = (BookMeta) heldBook.getItemMeta(); BookMeta book = (BookMeta) heldBook.getItemMeta();
if (book == null) { if (book == null) {
BooksWithoutBorders.sendErrorMessage(player, "Unable to get metadata for your held book!"); BooksWithoutBorders.sendErrorMessage(player, "Unable to get metadata for your held book!");
return; return;
} }
String savePath; //Only allow saving of own books if enabled
if (saveToPublicFolder) { if (BooksWithoutBordersConfig.getAuthorOnlySave() && !saveToPublicFolder &&
savePath = getBookFolder(); (!player.hasPermission("bookswithoutborders.bypassAuthorOnlySave") &&
} else { BookHelper.isNotAuthor(player, book))) {
savePath = getBookFolder() + cleanString(player.getName()) + getSlash(); return;
}
String savePath = BookHelper.getBookDirectoryPathString(
saveToPublicFolder ? BookDirectory.PUBLIC : BookDirectory.PLAYER, player);
if (savePath == null) {
BooksWithoutBorders.sendErrorMessage(player, "Saving Failed! Unable to find the save path!");
return;
} }
//Generate book filename //Generate book filename
String fileName; String fileName;
if (!book.hasTitle()) { try {
fileName = "Untitled," + player.getName(); fileName = BookHelper.getBookFile(book, player, saveToPublicFolder);
} else { } catch (IllegalArgumentException exception) {
fileName = book.getTitle() + BooksWithoutBordersConfig.getTitleAuthorSeparator() + book.getAuthor(); BooksWithoutBorders.sendErrorMessage(player, exception.getMessage());
return;
} }
fileName = cleanString(fileName);
fileName = fixName(fileName, false);
//Make sure the used folders exist //Make sure the used folders exist
File file = new File(savePath); File file = new File(savePath);
if (!file.exists() && !file.mkdir()) { if (!file.exists() && !file.mkdir()) {
BooksWithoutBorders.sendErrorMessage(player, "Saving Failed! If this continues to happen, consult server admin!"); BooksWithoutBorders.sendErrorMessage(player, "Saving Failed! If this continues to happen, consult" +
" a server admin!");
return; return;
} }
File[] foundFiles = file.listFiles(); File[] foundFiles = file.listFiles();
if (foundFiles == null) { if (foundFiles == null) {
BooksWithoutBorders.sendErrorMessage(player, "Saving Failed! If this continues to happen, consult server admin!"); BooksWithoutBorders.sendErrorMessage(player, "Saving Failed! If this continues to happen, consult" +
" a server admin!");
return; return;
} }
//Find any duplicates of the book //Find any duplicates of the book
int foundDuplicates = FileHelper.findDuplicates(foundFiles, fileName); int foundDuplicates = BookFileHelper.findDuplicates(foundFiles, fileName);
//Deal with duplicates //Deal with duplicates
if (foundDuplicates > 0) { if (foundDuplicates > 0) {
//TODO: Decide if this makes sense or needs to be changed //TODO: Decide if this makes sense or needs to be changed
//Skip duplicate book //Skip duplicate book
if (!fileName.contains("Untitled") && !overwrite) { if (!fileName.contains("Untitled" + getTitleAuthorSeparator()) && !overwrite) {
BooksWithoutBorders.sendErrorMessage(player, "Book is already saved!"); BooksWithoutBorders.sendErrorMessage(player, "Book is already saved!");
BooksWithoutBorders.sendErrorMessage(player, "Use " + getCommandColor() + "/savebook true " + getErrorColor() + "to overwrite!"); BooksWithoutBorders.sendErrorMessage(player, "Use " + getCommandColor() + "/savebook true " +
getErrorColor() + "to overwrite!");
return; return;
} }
//Skip if duplicate limit is reached //Skip if duplicate limit is reached
if (foundDuplicates > BooksWithoutBordersConfig.getBookDuplicateLimit()) { if (foundDuplicates > BooksWithoutBordersConfig.getBookDuplicateLimit()) {
BooksWithoutBorders.sendErrorMessage(player, "Maximum amount of " + fileName + " duplicates reached!"); BooksWithoutBorders.sendErrorMessage(player, "Maximum amount of " + fileName +
BooksWithoutBorders.sendErrorMessage(player, "Use " + getCommandColor() + "/savebook true " + getErrorColor() + "to overwrite!"); " duplicates reached!");
BooksWithoutBorders.sendErrorMessage(player, "Use " + getCommandColor() + "/savebook true " +
getErrorColor() + "to overwrite!");
return; return;
} }
//Alter duplicated filename //Alter duplicated filename
if (fileName.contains("Untitled") && !overwrite) { if (fileName.contains("Untitled" + getTitleAuthorSeparator()) && !overwrite) {
fileName = "(" + foundDuplicates + ")" + fileName; fileName = "(" + foundDuplicates + ")" + fileName;
} }
} }
@@ -141,15 +154,17 @@ public class CommandSave implements TabExecutor {
//Update the relevant book list //Update the relevant book list
BooksWithoutBorders.updateBooks(player, saveToPublicFolder); BooksWithoutBorders.updateBooks(player, saveToPublicFolder);
BooksWithoutBorders.sendSuccessMessage(player, "Book Saved as \"" + fileName + "\""); BooksWithoutBorders.sendSuccessMessage(player, "Book Saved as \"" + fileName + ChatColor.RESET + "\"");
} catch (IOException e) { } catch (IOException exception) {
e.printStackTrace(); BooksWithoutBorders.getInstance().getLogger().log(Level.SEVERE, "Unable to save book");
} }
} }
@Override @Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) { @NotNull
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias,
@NotNull String[] arguments) {
return new ArrayList<>(); return new ArrayList<>();
} }

View File

@@ -11,8 +11,9 @@ import org.jetbrains.annotations.NotNull;
public class CommandSavePublic extends CommandSave implements TabExecutor { public class CommandSavePublic extends CommandSave implements TabExecutor {
@Override @Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
return saveHeldBook(sender, args, true); @NotNull String[] arguments) {
return saveHeldBook(sender, arguments, true);
} }
} }

View File

@@ -3,6 +3,8 @@ package net.knarcraft.bookswithoutborders.command;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders; import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.state.ItemSlot; import net.knarcraft.bookswithoutborders.state.ItemSlot;
import net.knarcraft.bookswithoutborders.utility.InventoryHelper; import net.knarcraft.bookswithoutborders.utility.InventoryHelper;
import net.knarcraft.knarlib.property.ColorConversion;
import net.knarcraft.knarlib.util.ColorHelper;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor; import org.bukkit.command.TabExecutor;
@@ -10,6 +12,7 @@ import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.BookMeta; import org.bukkit.inventory.meta.BookMeta;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -20,13 +23,14 @@ import java.util.List;
public class CommandSetAuthor implements TabExecutor { public class CommandSetAuthor implements TabExecutor {
@Override @Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
@NotNull String[] arguments) {
if (!(sender instanceof Player player)) { if (!(sender instanceof Player player)) {
BooksWithoutBorders.sendErrorMessage(sender, "This command can only be used by a player!"); BooksWithoutBorders.sendErrorMessage(sender, "This command can only be used by a player!");
return false; return false;
} }
if (args.length < 1) { if (arguments.length < 1) {
BooksWithoutBorders.sendErrorMessage(player, "Too few command arguments!"); BooksWithoutBorders.sendErrorMessage(player, "Too few command arguments!");
return false; return false;
} }
@@ -41,8 +45,12 @@ public class CommandSetAuthor implements TabExecutor {
boolean mainHand = heldBookSlot == ItemSlot.MAIN_HAND; boolean mainHand = heldBookSlot == ItemSlot.MAIN_HAND;
ItemStack heldBook = InventoryHelper.getHeldItem(player, mainHand); ItemStack heldBook = InventoryHelper.getHeldItem(player, mainHand);
BookMeta bookMetaData = InventoryHelper.getHeldBookMetadata(player, mainHand); BookMeta bookMetaData = InventoryHelper.getHeldBookMetadata(player, mainHand);
if (bookMetaData == null) {
BooksWithoutBorders.sendErrorMessage(player, "Unable to get metadata for the held book!");
return false;
}
String author = String.join(" ", args); String author = ColorHelper.translateColorCodes(String.join(" ", arguments), ColorConversion.RGB);
bookMetaData.setAuthor(author); bookMetaData.setAuthor(author);
heldBook.setItemMeta(bookMetaData); heldBook.setItemMeta(bookMetaData);
BooksWithoutBorders.sendSuccessMessage(player, "Book author set to " + author + "!"); BooksWithoutBorders.sendSuccessMessage(player, "Book author set to " + author + "!");
@@ -50,8 +58,10 @@ public class CommandSetAuthor implements TabExecutor {
} }
@Override @Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, String[] args) { @Nullable
if (args.length == 1) { public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias,
@NotNull String[] arguments) {
if (arguments.length == 1) {
return null; return null;
} else { } else {
return new ArrayList<>(); return new ArrayList<>();

View File

@@ -4,7 +4,8 @@ import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig; import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
import net.knarcraft.bookswithoutborders.utility.EconomyHelper; import net.knarcraft.bookswithoutborders.utility.EconomyHelper;
import net.knarcraft.bookswithoutborders.utility.InventoryHelper; import net.knarcraft.bookswithoutborders.utility.InventoryHelper;
import net.knarcraft.bookswithoutborders.utility.TabCompletionHelper; import net.knarcraft.bookswithoutborders.utility.TabCompletionTypeHelper;
import net.knarcraft.knarlib.util.TabCompletionHelper;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
@@ -25,35 +26,36 @@ public class CommandSetBookPrice implements TabExecutor {
private List<String> paymentTypes; private List<String> paymentTypes;
@Override @Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) { public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
@NotNull String[] arguments) {
//Clear the current price //Clear the current price
if (args.length == 0) { if (arguments.length == 0) {
clearItemPrice(sender); clearItemPrice(sender);
return true; return true;
} }
//Warn about missing arguments //Warn about missing arguments
if (args.length < 2) { if (arguments.length < 2) {
BooksWithoutBorders.sendErrorMessage(sender, "[Item/Eco] and [quantity] must be specified!"); BooksWithoutBorders.sendErrorMessage(sender, "[Item/Eco] and [quantity] must be specified!");
return false; return false;
} }
//Warn about invalid argument //Warn about invalid argument
if (!args[0].equalsIgnoreCase("Item") && !args[0].equalsIgnoreCase("Eco")) { if (!arguments[0].equalsIgnoreCase("Item") && !arguments[0].equalsIgnoreCase("Eco")) {
BooksWithoutBorders.sendErrorMessage(sender, "Price type must be \"Item\" or \"Eco\"!"); BooksWithoutBorders.sendErrorMessage(sender, "Price type must be \"Item\" or \"Eco\"!");
return false; return false;
} }
try { try {
double price = Double.parseDouble(args[1]); double price = Double.parseDouble(arguments[1]);
if (price <= 0) { if (price <= 0) {
BooksWithoutBorders.sendErrorMessage(sender, "[quantity] must be greater than 0!"); BooksWithoutBorders.sendErrorMessage(sender, "[quantity] must be greater than 0!");
return false; return false;
} }
if (args[0].equalsIgnoreCase("Item")) { if (arguments[0].equalsIgnoreCase("Item")) {
return setItemPrice(sender, price); return setItemPrice(sender, price);
} else if (args[0].equalsIgnoreCase("Eco")) { } else if (arguments[0].equalsIgnoreCase("Eco")) {
return setEconomyPrice(sender, price); return setEconomyPrice(sender, price);
} }
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
@@ -67,7 +69,7 @@ public class CommandSetBookPrice implements TabExecutor {
* *
* @param sender <p>The sender of the command</p> * @param sender <p>The sender of the command</p>
*/ */
private void clearItemPrice(CommandSender sender) { private void clearItemPrice(@NotNull CommandSender sender) {
BooksWithoutBordersConfig.setBookPriceType(null); BooksWithoutBordersConfig.setBookPriceType(null);
BooksWithoutBordersConfig.setBookPriceQuantity(0); BooksWithoutBordersConfig.setBookPriceQuantity(0);
booksWithoutBorders.getConfig().set("Options.Price_to_create_book.Item_type", "Item type name"); booksWithoutBorders.getConfig().set("Options.Price_to_create_book.Item_type", "Item type name");
@@ -84,7 +86,7 @@ public class CommandSetBookPrice implements TabExecutor {
* @param price <p>The new price</p> * @param price <p>The new price</p>
* @return <p>True if the price was changed successfully</p> * @return <p>True if the price was changed successfully</p>
*/ */
private boolean setItemPrice(CommandSender sender, double price) { private boolean setItemPrice(@NotNull CommandSender sender, double price) {
if (!(sender instanceof Player player)) { if (!(sender instanceof Player player)) {
BooksWithoutBorders.sendErrorMessage(sender, "[Item] price can only be used by a player!"); BooksWithoutBorders.sendErrorMessage(sender, "[Item] price can only be used by a player!");
return false; return false;
@@ -116,7 +118,7 @@ public class CommandSetBookPrice implements TabExecutor {
* @param price <p>The new price</p> * @param price <p>The new price</p>
* @return <p>True if the price was changed successfully</p> * @return <p>True if the price was changed successfully</p>
*/ */
private boolean setEconomyPrice(CommandSender sender, double price) { private boolean setEconomyPrice(@NotNull CommandSender sender, double price) {
if (EconomyHelper.setupEconomy()) { if (EconomyHelper.setupEconomy()) {
BooksWithoutBordersConfig.setBookPriceQuantity(price); BooksWithoutBordersConfig.setBookPriceQuantity(price);
BooksWithoutBordersConfig.setBookPriceType(Material.AIR); BooksWithoutBordersConfig.setBookPriceType(Material.AIR);
@@ -135,16 +137,17 @@ public class CommandSetBookPrice implements TabExecutor {
} }
@Override @Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, String[] args) { public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias,
@NotNull String[] arguments) {
if (paymentTypes == null) { if (paymentTypes == null) {
initializeTabCompleteLists(); initializeTabCompleteLists();
} }
int argumentCount = args.length; int argumentCount = arguments.length;
if (argumentCount == 1) { if (argumentCount == 1) {
return TabCompletionHelper.filterMatchingStartsWith(paymentTypes, args[0]); return TabCompletionHelper.filterMatchingStartsWith(paymentTypes, arguments[0]);
} else if (argumentCount == 2) { } else if (argumentCount == 2) {
return TabCompletionHelper.filterMatchingStartsWith(TabCompletionHelper.getNumbers(1, 3), args[1]); return TabCompletionHelper.filterMatchingStartsWith(TabCompletionTypeHelper.getNumbers(1, 3), arguments[1]);
} }
return new ArrayList<>(); return new ArrayList<>();
} }

View File

@@ -0,0 +1,111 @@
package net.knarcraft.bookswithoutborders.command;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
import net.knarcraft.bookswithoutborders.container.Bookshelf;
import net.knarcraft.bookswithoutborders.handler.BookshelfHandler;
import net.knarcraft.knarlib.util.TabCompletionHelper;
import org.bukkit.Material;
import org.bukkit.block.Block;
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 org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* The command for setting information for a chiseled bookshelf
*/
public class CommandSetBookshelfData implements TabExecutor {
@Override
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@NotNull String[] arguments) {
if (!(commandSender instanceof Player player)) {
BooksWithoutBorders.sendErrorMessage(commandSender, "This command must be used by a player!");
return false;
}
Block targetBlock = player.getTargetBlockExact(7);
if (targetBlock == null || targetBlock.getType() != Material.CHISELED_BOOKSHELF) {
BooksWithoutBorders.sendErrorMessage(commandSender, "You are not looking at a bookshelf!");
return false;
}
BookshelfHandler shelfHandler = BooksWithoutBorders.getBookshelfHandler();
Bookshelf bookshelf = shelfHandler.getFromLocation(targetBlock.getLocation());
if (arguments.length < 2) {
if (arguments.length == 1 && arguments[0].equalsIgnoreCase("delete")) {
if (bookshelf != null) {
shelfHandler.unregisterBookshelf(bookshelf);
shelfHandler.save();
BooksWithoutBorders.sendSuccessMessage(commandSender, "Bookshelf successfully deleted");
} else {
BooksWithoutBorders.sendErrorMessage(commandSender, "The block you are looking at is not a registered bookshelf");
}
return true;
} else {
return false;
}
}
// Get all arguments as a space-separated string
StringBuilder builder = new StringBuilder(arguments[1]);
for (int i = 2; i < arguments.length; i++) {
builder.append(" ").append(arguments[i]);
}
switch (arguments[0].toLowerCase()) {
case "name":
if (bookshelf == null) {
Bookshelf newShelf = new Bookshelf(targetBlock.getLocation(), arguments[1], new ArrayList<>());
shelfHandler.registerBookshelf(newShelf);
} else {
bookshelf.setTitle(builder.toString());
}
shelfHandler.save();
BooksWithoutBorders.sendSuccessMessage(commandSender, "Title successfully saved");
return true;
case "lore":
if (bookshelf == null) {
BooksWithoutBorders.sendErrorMessage(commandSender, "You must name the bookshelf before " +
"assigning lore!");
} else {
List<String> loreParts = Arrays.asList(builder.toString().split(BooksWithoutBordersConfig.getLoreSeparator()));
bookshelf.setLore(loreParts);
shelfHandler.save();
BooksWithoutBorders.sendSuccessMessage(commandSender, "Lore successfully saved");
}
return true;
}
return false;
}
@Nullable
@Override
public List<String> onTabComplete(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@NotNull String[] arguments) {
if (arguments.length == 1) {
return TabCompletionHelper.filterMatchingStartsWith(List.of("delete", "name", "lore"), arguments[0]);
} else if (arguments.length == 2) {
return switch (arguments[0].toLowerCase()) {
case "delete" -> new ArrayList<>();
case "name" ->
TabCompletionHelper.filterMatchingStartsWith(List.of("Epic Title", "Lame Title"), arguments[1]);
case "lore" ->
TabCompletionHelper.filterMatchingStartsWith(List.of("Interesting lore", "Line1~Line2~Line3"), arguments[1]);
default -> null;
};
} else {
return List.of();
}
}
}

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[] arguments) {
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 (arguments.length < 1) {
BooksWithoutBorders.sendErrorMessage(player, "You must specify the new generation for your book!");
return false;
}
BookMeta.Generation generation;
try {
generation = BookMeta.Generation.valueOf(arguments[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[] arguments) {
if (arguments.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

@@ -3,7 +3,8 @@ package net.knarcraft.bookswithoutborders.command;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders; import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig; import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
import net.knarcraft.bookswithoutborders.utility.InventoryHelper; 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.Material;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
@@ -23,13 +24,14 @@ import java.util.List;
public class CommandSetLore implements TabExecutor { public class CommandSetLore implements TabExecutor {
@Override @Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
@NotNull String[] arguments) {
if (!(sender instanceof Player player)) { if (!(sender instanceof Player player)) {
BooksWithoutBorders.sendErrorMessage(sender, "This command can only be used by a player!"); BooksWithoutBorders.sendErrorMessage(sender, "This command can only be used by a player!");
return false; return false;
} }
if (args.length < 1) { if (arguments.length < 1) {
BooksWithoutBorders.sendErrorMessage(player, "Missing a command argument!"); BooksWithoutBorders.sendErrorMessage(player, "Missing a command argument!");
return false; return false;
} }
@@ -41,12 +43,11 @@ public class CommandSetLore implements TabExecutor {
} }
//Treat all arguments as lore input //Treat all arguments as lore input
String rawLore = String.join(" ", args); String rawLore = String.join(" ", arguments);
//Format lore //Format lore
rawLore = ChatColor.translateAlternateColorCodes('&', rawLore); rawLore = ColorHelper.translateColorCodes(rawLore, ColorConversion.RGB);
String[] loreParts = rawLore.split(BooksWithoutBordersConfig.getLoreSeparator()); List<String> newLore = Arrays.asList(rawLore.split(BooksWithoutBordersConfig.getLoreSeparator()));
List<String> newLore = new ArrayList<>(Arrays.asList(loreParts));
//Update lore //Update lore
ItemMeta meta = heldItem.getItemMeta(); ItemMeta meta = heldItem.getItemMeta();
@@ -61,7 +62,8 @@ public class CommandSetLore implements TabExecutor {
} }
@Override @Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, String[] args) { public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias,
@NotNull String[] arguments) {
//TODO: Figure out if there is a better way to display that an argument is required //TODO: Figure out if there is a better way to display that an argument is required
List<String> options = new ArrayList<>(); List<String> options = new ArrayList<>();
options.add("<new lore>"); options.add("<new lore>");

View File

@@ -2,7 +2,8 @@ package net.knarcraft.bookswithoutborders.command;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders; import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.utility.InventoryHelper; 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.Material;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
@@ -22,13 +23,14 @@ import java.util.List;
public class CommandSetTitle implements TabExecutor { public class CommandSetTitle implements TabExecutor {
@Override @Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
@NotNull String[] arguments) {
if (!(sender instanceof Player player)) { if (!(sender instanceof Player player)) {
BooksWithoutBorders.sendErrorMessage(sender, "This command can only be used by a player!"); BooksWithoutBorders.sendErrorMessage(sender, "This command can only be used by a player!");
return false; return false;
} }
if (args.length < 1) { if (arguments.length < 1) {
BooksWithoutBorders.sendErrorMessage(player, "Too few command arguments!"); BooksWithoutBorders.sendErrorMessage(player, "Too few command arguments!");
return false; return false;
} }
@@ -39,8 +41,8 @@ public class CommandSetTitle implements TabExecutor {
return false; return false;
} }
String title = String.join(" ", args); String title = String.join(" ", arguments);
title = ChatColor.translateAlternateColorCodes('&', title); title = ColorHelper.translateColorCodes(title, ColorConversion.RGB);
ItemMeta itemMetadata = heldItem.getItemMeta(); ItemMeta itemMetadata = heldItem.getItemMeta();
if (itemMetadata == null) { if (itemMetadata == null) {
@@ -66,7 +68,8 @@ public class CommandSetTitle implements TabExecutor {
} }
@Override @Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) { public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias,
@NotNull String[] arguments) {
List<String> options = new ArrayList<>(); List<String> options = new ArrayList<>();
options.add("<new title>"); options.add("<new title>");
return options; return options;

View File

@@ -1,7 +1,9 @@
package net.knarcraft.bookswithoutborders.command; package net.knarcraft.bookswithoutborders.command;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders; import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
import net.knarcraft.bookswithoutborders.state.ItemSlot; import net.knarcraft.bookswithoutborders.state.ItemSlot;
import net.knarcraft.bookswithoutborders.utility.BookHelper;
import net.knarcraft.bookswithoutborders.utility.InventoryHelper; import net.knarcraft.bookswithoutborders.utility.InventoryHelper;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.command.Command; import org.bukkit.command.Command;
@@ -10,10 +12,13 @@ import org.bukkit.command.TabExecutor;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.BookMeta; import org.bukkit.inventory.meta.BookMeta;
import org.bukkit.inventory.meta.WritableBookMeta;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.logging.Level;
/** /**
* Command executor for the unsign command * Command executor for the unsign command
@@ -21,7 +26,8 @@ import java.util.List;
public class CommandUnSign implements TabExecutor { public class CommandUnSign implements TabExecutor {
@Override @Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
@NotNull String[] arguments) {
if (!(sender instanceof Player player)) { if (!(sender instanceof Player player)) {
BooksWithoutBorders.sendErrorMessage(sender, "This command can only be used by a player!"); BooksWithoutBorders.sendErrorMessage(sender, "This command can only be used by a player!");
return false; return false;
@@ -44,19 +50,49 @@ public class CommandUnSign implements TabExecutor {
* @param player <p>The player holding the book</p> * @param player <p>The player holding the book</p>
* @param mainHand <p>Whether the player is holding the book in its main hand or its off hand</p> * @param mainHand <p>Whether the player is holding the book in its main hand or its off hand</p>
*/ */
public void unSignHeldBook(Player player, boolean mainHand) { public void unSignHeldBook(@NotNull Player player, boolean mainHand) {
//Get the old book //Get the old book
BookMeta oldBook = InventoryHelper.getHeldBookMetadata(player, mainHand); BookMeta oldMetadata = InventoryHelper.getHeldBookMetadata(player, mainHand);
if (oldMetadata == null) {
BooksWithoutBorders.sendErrorMessage(player, "Unable to get metadata from the held book!");
return;
}
ItemStack heldBook = InventoryHelper.getHeldBook(player, mainHand);
//UnSign the book //Only allow the owner to un-sign the book
ItemStack newBook = new ItemStack(Material.WRITABLE_BOOK); if (BooksWithoutBordersConfig.getAuthorOnlyUnsign() && !player.hasPermission("bookswithoutborders.bypassAuthorOnlyUnsign")) {
newBook.setItemMeta(oldBook); 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); InventoryHelper.replaceHeldItem(player, newBook, mainHand);
} }
@Override @Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) { @NotNull
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias,
@NotNull String[] arguments) {
return new ArrayList<>(); return new ArrayList<>();
} }

View File

@@ -2,11 +2,13 @@ package net.knarcraft.bookswithoutborders.config;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders; import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.utility.EconomyHelper; import net.knarcraft.bookswithoutborders.utility.EconomyHelper;
import org.bukkit.ChatColor; import net.md_5.bungee.api.ChatColor;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.command.ConsoleCommandSender; import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.configuration.Configuration; import org.bukkit.configuration.Configuration;
import org.jetbrains.annotations.NotNull;
import java.nio.file.FileSystems;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -22,7 +24,7 @@ public class BooksWithoutBordersConfig {
private static final ChatColor errorColor = ChatColor.RED; private static final ChatColor errorColor = ChatColor.RED;
private static final ChatColor successColor = ChatColor.GREEN; private static final ChatColor successColor = ChatColor.GREEN;
private static final ChatColor commandColor = ChatColor.YELLOW; private static final ChatColor commandColor = ChatColor.YELLOW;
private static final String SLASH = System.getProperty("file.separator"); private static final String SLASH = FileSystems.getDefault().getSeparator();
private static boolean isInitialized; private static boolean isInitialized;
public static String bookFolder; public static String bookFolder;
@@ -34,16 +36,20 @@ public class BooksWithoutBordersConfig {
private static Material bookPriceType = null; private static Material bookPriceType = null;
private static double bookPriceQuantity; private static double bookPriceQuantity;
private static boolean authorOnlyCopy; private static boolean authorOnlyCopy;
private static boolean authorOnlyUnsign;
private static boolean authorOnlySave;
private static boolean useYml; private static boolean useYml;
private static boolean adminDecrypt; private static boolean adminDecrypt;
private static boolean formatBooks; private static boolean formatBooks;
private static boolean changeGenerationOnCopy;
private static boolean enableBookshelfPeeking;
/** /**
* Initializes the books without borders settings class * Initializes the books without borders settings class
* *
* @param booksWithoutBorders <p>The books without borders object used for getting required data</p> * @param booksWithoutBorders <p>The books without borders object used for getting required data</p>
*/ */
public static void initialize(BooksWithoutBorders booksWithoutBorders) { public static void initialize(@NotNull BooksWithoutBorders booksWithoutBorders) {
if (isInitialized) { if (isInitialized) {
throw new IllegalArgumentException("Settings class initialized twice. This should not happen!"); throw new IllegalArgumentException("Settings class initialized twice. This should not happen!");
} }
@@ -106,6 +112,33 @@ public class BooksWithoutBordersConfig {
return authorOnlyCopy; return authorOnlyCopy;
} }
/**
* Gets whether only the author of a book should be able to unsign it
*
* @return <p>Whether only the book author can unsign it</p>
*/
public static boolean getAuthorOnlyUnsign() {
return authorOnlyUnsign;
}
/**
* Gets whether a player can only save their own books with /savebook
*
* @return <p>Whether a player can only save their own books</p>
*/
public static boolean getAuthorOnlySave() {
return authorOnlySave;
}
/**
* Gets whether 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 * Gets whether to use YML, not TXT, for saving books
* *
@@ -184,6 +217,15 @@ public class BooksWithoutBordersConfig {
return bookDuplicateLimit; return bookDuplicateLimit;
} }
/**
* Gets whether books should change their generation during copy
*
* @return <p>True if books should change their generation</p>
*/
public static boolean changeGenerationOnCopy() {
return changeGenerationOnCopy;
}
/** /**
* Gets the separator used to split book title from book author * Gets the separator used to split book title from book author
* *
@@ -242,6 +284,7 @@ public class BooksWithoutBordersConfig {
config.set(ConfigOption.BOOKS_FOR_NEW_PLAYERS.getConfigNode(), firstBooks); config.set(ConfigOption.BOOKS_FOR_NEW_PLAYERS.getConfigNode(), firstBooks);
config.set(ConfigOption.MESSAGE_FOR_NEW_PLAYERS.getConfigNode(), welcomeMessage); config.set(ConfigOption.MESSAGE_FOR_NEW_PLAYERS.getConfigNode(), welcomeMessage);
config.set(ConfigOption.FORMAT_AFTER_SIGNING.getConfigNode(), formatBooks); config.set(ConfigOption.FORMAT_AFTER_SIGNING.getConfigNode(), formatBooks);
config.set(ConfigOption.ENABLE_BOOKSHELF_PEEKING.getConfigNode(), enableBookshelfPeeking);
String itemTypeNode = ConfigOption.PRICE_ITEM_TYPE.getConfigNode(); String itemTypeNode = ConfigOption.PRICE_ITEM_TYPE.getConfigNode();
if (bookPriceType != null) { if (bookPriceType != null) {
@@ -257,10 +300,14 @@ public class BooksWithoutBordersConfig {
config.set(ConfigOption.PRICE_QUANTITY.getConfigNode(), bookPriceQuantity); config.set(ConfigOption.PRICE_QUANTITY.getConfigNode(), bookPriceQuantity);
config.set(ConfigOption.ADMIN_AUTO_DECRYPT.getConfigNode(), adminDecrypt); config.set(ConfigOption.ADMIN_AUTO_DECRYPT.getConfigNode(), adminDecrypt);
config.set(ConfigOption.AUTHOR_ONLY_COPY.getConfigNode(), authorOnlyCopy); config.set(ConfigOption.AUTHOR_ONLY_COPY.getConfigNode(), authorOnlyCopy);
config.set(ConfigOption.AUTHOR_ONLY_UNSIGN.getConfigNode(), authorOnlyUnsign);
config.set(ConfigOption.AUTHOR_ONLY_SAVE.getConfigNode(), authorOnlySave);
config.set(ConfigOption.CHANGE_GENERATION_ON_COPY.getConfigNode(), changeGenerationOnCopy);
//Handles old book and quill settings //Handles old book and quill settings
if (config.contains("Options.Require_book_and_quill_to_create_book")) { if (config.contains("Options.Require_book_and_quill_to_create_book")) {
sendSuccessMessage(consoleSender, "[BooksWithoutBorders] Found old config setting \"Require_book_and_quill_to_create_book\""); sendSuccessMessage(consoleSender, "[BooksWithoutBorders] Found old config setting " +
"\"Require_book_and_quill_to_create_book\"");
sendSuccessMessage(consoleSender, "Updating to \"Price_to_create_book\" settings"); sendSuccessMessage(consoleSender, "Updating to \"Price_to_create_book\" settings");
if (config.getBoolean("Options.Require_book_and_quill_to_create_book")) { if (config.getBoolean("Options.Require_book_and_quill_to_create_book")) {
@@ -284,32 +331,29 @@ public class BooksWithoutBordersConfig {
BooksWithoutBorders.getInstance().reloadConfig(); BooksWithoutBorders.getInstance().reloadConfig();
Configuration config = BooksWithoutBorders.getInstance().getConfig(); Configuration config = BooksWithoutBorders.getInstance().getConfig();
try { try {
useYml = config.getBoolean(ConfigOption.USE_YAML.getConfigNode(), useYml = getBoolean(config, ConfigOption.USE_YAML);
(Boolean) ConfigOption.USE_YAML.getDefaultValue());
bookDuplicateLimit = config.getInt(ConfigOption.MAX_DUPLICATES.getConfigNode(), bookDuplicateLimit = config.getInt(ConfigOption.MAX_DUPLICATES.getConfigNode(),
(Integer) ConfigOption.MAX_DUPLICATES.getDefaultValue()); (Integer) ConfigOption.MAX_DUPLICATES.getDefaultValue());
titleAuthorSeparator = config.getString(ConfigOption.TITLE_AUTHOR_SEPARATOR.getConfigNode(), titleAuthorSeparator = getString(config, ConfigOption.TITLE_AUTHOR_SEPARATOR);
(String) ConfigOption.TITLE_AUTHOR_SEPARATOR.getDefaultValue()); loreSeparator = getString(config, ConfigOption.LORE_LINE_SEPARATOR);
loreSeparator = config.getString(ConfigOption.LORE_LINE_SEPARATOR.getConfigNode(), adminDecrypt = getBoolean(config, ConfigOption.ADMIN_AUTO_DECRYPT);
(String) ConfigOption.LORE_LINE_SEPARATOR.getDefaultValue()); authorOnlyCopy = getBoolean(config, ConfigOption.AUTHOR_ONLY_COPY);
adminDecrypt = config.getBoolean(ConfigOption.ADMIN_AUTO_DECRYPT.getConfigNode(), authorOnlyUnsign = getBoolean(config, ConfigOption.AUTHOR_ONLY_UNSIGN);
(Boolean) ConfigOption.ADMIN_AUTO_DECRYPT.getDefaultValue()); authorOnlySave = getBoolean(config, ConfigOption.AUTHOR_ONLY_SAVE);
authorOnlyCopy = config.getBoolean(ConfigOption.AUTHOR_ONLY_COPY.getConfigNode(),
(Boolean) ConfigOption.AUTHOR_ONLY_COPY.getDefaultValue());
firstBooks = config.getStringList(ConfigOption.BOOKS_FOR_NEW_PLAYERS.getConfigNode()); firstBooks = config.getStringList(ConfigOption.BOOKS_FOR_NEW_PLAYERS.getConfigNode());
welcomeMessage = config.getString(ConfigOption.MESSAGE_FOR_NEW_PLAYERS.getConfigNode(), welcomeMessage = getString(config, ConfigOption.MESSAGE_FOR_NEW_PLAYERS);
(String) ConfigOption.MESSAGE_FOR_NEW_PLAYERS.getDefaultValue()); formatBooks = getBoolean(config, ConfigOption.FORMAT_AFTER_SIGNING);
formatBooks = config.getBoolean(ConfigOption.FORMAT_AFTER_SIGNING.getConfigNode(), changeGenerationOnCopy = getBoolean(config, ConfigOption.CHANGE_GENERATION_ON_COPY);
(Boolean) ConfigOption.FORMAT_AFTER_SIGNING.getDefaultValue()); enableBookshelfPeeking = getBoolean(config, ConfigOption.ENABLE_BOOKSHELF_PEEKING);
//Convert string into material //Convert string into material
String paymentMaterial = config.getString(ConfigOption.PRICE_ITEM_TYPE.getConfigNode(), String paymentMaterial = getString(config, ConfigOption.PRICE_ITEM_TYPE);
(String) ConfigOption.PRICE_ITEM_TYPE.getDefaultValue());
if (paymentMaterial.equalsIgnoreCase("Economy")) { if (paymentMaterial.equalsIgnoreCase("Economy")) {
if (EconomyHelper.setupEconomy()) { if (EconomyHelper.setupEconomy()) {
bookPriceType = Material.AIR; bookPriceType = Material.AIR;
} else { } else {
sendErrorMessage(consoleSender, "BooksWithoutBorders failed to hook into Vault! Book price not set!"); sendErrorMessage(consoleSender,
"BooksWithoutBorders failed to hook into Vault! Book price not set!");
bookPriceType = null; bookPriceType = null;
} }
} else if (!paymentMaterial.trim().isEmpty()) { } else if (!paymentMaterial.trim().isEmpty()) {
@@ -338,4 +382,26 @@ public class BooksWithoutBordersConfig {
return true; 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(@NotNull Configuration config, @NotNull 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(@NotNull Configuration config, @NotNull ConfigOption configOption) {
return config.getBoolean(configOption.getConfigNode(), (Boolean) configOption.getDefaultValue());
}
} }

View File

@@ -1,5 +1,7 @@
package net.knarcraft.bookswithoutborders.config; package net.knarcraft.bookswithoutborders.config;
import org.jetbrains.annotations.NotNull;
/** /**
* A representation of the different available config options * A representation of the different available config options
*/ */
@@ -55,10 +57,31 @@ public enum ConfigOption {
*/ */
AUTHOR_ONLY_COPY("Options.Author_Only_Copy", false), AUTHOR_ONLY_COPY("Options.Author_Only_Copy", false),
/**
* Whether only the book author should be able to unsign a book
*/
AUTHOR_ONLY_UNSIGN("Options.Author_Only_Unsign", false),
/**
* Whether a player can only save their own books with /savebook
*/
AUTHOR_ONLY_SAVE("Options.Author_Only_Save", false),
/**
* Whether to turn Original into Copy when copying books
*/
CHANGE_GENERATION_ON_COPY("Options.Change_Generation_On_Copy", false),
/** /**
* Whether to automatically format every signed book * Whether to automatically format every signed book
*/ */
FORMAT_AFTER_SIGNING("Options.Format_Book_After_Signing", false); 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 String configNode;
private final Object defaultValue; private final Object defaultValue;
@@ -69,7 +92,7 @@ public enum ConfigOption {
* @param configNode <p>The config node in the config file this option represents</p> * @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> * @param defaultValue <p>The default value for this config option</p>
*/ */
ConfigOption(String configNode, Object defaultValue) { ConfigOption(@NotNull String configNode, @NotNull Object defaultValue) {
this.configNode = configNode; this.configNode = configNode;
this.defaultValue = defaultValue; this.defaultValue = defaultValue;
} }

View File

@@ -0,0 +1,81 @@
package net.knarcraft.bookswithoutborders.container;
import org.bukkit.Location;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* A representation of a bookshelf with extra data used when displaying its contents
*/
public class Bookshelf {
private final @NotNull Location location;
private @NotNull String title;
private @NotNull List<String> lore;
/**
* Instantiates a new bookshelf
*
* @param location <p>The location of the bookshelf</p>
* @param title <p>The title of the bookshelf</p>
* @param lore <p>The lore of the bookshelf</p>
*/
public Bookshelf(@NotNull Location location, @NotNull String title, @NotNull List<String> lore) {
this.location = location;
this.title = title;
this.lore = lore;
}
/**
* Gets the location of this bookshelf
*
* @return <p>The location of this bookshelf</p>
*/
@NotNull
public Location getLocation() {
return this.location;
}
/**
* Gets the title of this bookshelf
*
* @return <p>The title of this bookshelf</p>
*/
@NotNull
public String getTitle() {
return this.title;
}
/**
* Gets the lore of this bookshelf
*
* @return <p>The lore of this bookshelf</p>
*/
@NotNull
public List<String> getLore() {
return this.lore;
}
/**
* Sets the title of this bookshelf
*
* @param title <p>The new title</p>
*/
public void setTitle(@NotNull String title) {
if (title.isBlank()) {
throw new IllegalArgumentException("Bookshelves cannot have empty titles!");
}
this.title = title;
}
/**
* Sets the lore of this bookshelf
*
* @param lore <p>The new lore</p>
*/
public void setLore(@NotNull List<String> lore) {
this.lore = lore;
}
}

View File

@@ -1,5 +1,9 @@
package net.knarcraft.bookswithoutborders.encryption; package net.knarcraft.bookswithoutborders.encryption;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.crypto.BadPaddingException; import javax.crypto.BadPaddingException;
import javax.crypto.Cipher; import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException; import javax.crypto.IllegalBlockSizeException;
@@ -15,6 +19,7 @@ import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException; import java.security.spec.InvalidKeySpecException;
import java.util.Base64; import java.util.Base64;
import java.util.logging.Level;
/** /**
* This class represents and AES encryptor/decryptor * This class represents and AES encryptor/decryptor
@@ -49,7 +54,8 @@ public class AES {
* @param encrypt <p>Whether to encrypt or decrypt the input</p> * @param encrypt <p>Whether to encrypt or decrypt the input</p>
* @return <p>The encrypted/decrypted input, or null if anything went wrong</p> * @return <p>The encrypted/decrypted input, or null if anything went wrong</p>
*/ */
public String encryptDecryptText(String input, String password, boolean encrypt) { @Nullable
public String encryptDecryptText(@NotNull String input, @NotNull String password, boolean encrypt) {
//Make a key from the password //Make a key from the password
SecretKeySpec secretKeySpec = getKeyFromPassword(password); SecretKeySpec secretKeySpec = getKeyFromPassword(password);
//Get cipher instance //Get cipher instance
@@ -66,16 +72,16 @@ public class AES {
//Initialize cipher //Initialize cipher
try { try {
aes.init(mode, secretKeySpec, ivParameterSpec); aes.init(mode, secretKeySpec, ivParameterSpec);
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) { } catch (InvalidKeyException | InvalidAlgorithmParameterException exception) {
e.printStackTrace(); BooksWithoutBorders.getInstance().getLogger().log(Level.SEVERE, "Invalid AES input given!");
return null; return null;
} }
//Perform encryption/decryption and output result //Perform encryption/decryption and output result
try { try {
byte[] output = aes.doFinal(getInputBytes(input, encrypt)); byte[] output = aes.doFinal(getInputBytes(input, encrypt));
return createResult(output, encrypt); return createResult(output, encrypt);
} catch (IllegalBlockSizeException | BadPaddingException e) { } catch (IllegalBlockSizeException | BadPaddingException exception) {
e.printStackTrace(); BooksWithoutBorders.getInstance().getLogger().log(Level.SEVERE, "Invalid AES block size or padding");
return null; return null;
} }
} }
@@ -99,7 +105,7 @@ public class AES {
* @param encryption <p>Whether the input should be encrypted or decrypted</p> * @param encryption <p>Whether the input should be encrypted or decrypted</p>
* @return <p>The input in byte format</p> * @return <p>The input in byte format</p>
*/ */
private byte[] getInputBytes(String input, boolean encryption) { private byte[] getInputBytes(@NotNull String input, boolean encryption) {
if (encryption) { if (encryption) {
return input.getBytes(); return input.getBytes();
} else { } else {
@@ -114,6 +120,7 @@ public class AES {
* @param encryption <p>Whether the output came from encryption or decryption</p> * @param encryption <p>Whether the output came from encryption or decryption</p>
* @return <p>The output as a string</p> * @return <p>The output as a string</p>
*/ */
@NotNull
private String createResult(byte[] output, boolean encryption) { private String createResult(byte[] output, boolean encryption) {
if (encryption) { if (encryption) {
return Base64.getEncoder().encodeToString(output); return Base64.getEncoder().encodeToString(output);
@@ -127,12 +134,13 @@ public class AES {
* *
* @return <p>An AES cipher instance, or null if something went wrong</p> * @return <p>An AES cipher instance, or null if something went wrong</p>
*/ */
@Nullable
private Cipher getAESCipher() { private Cipher getAESCipher() {
Cipher aes; Cipher aes;
try { try {
aes = Cipher.getInstance("AES/CBC/PKCS5Padding"); aes = Cipher.getInstance("AES/CBC/PKCS5Padding");
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) { } catch (NoSuchAlgorithmException | NoSuchPaddingException exception) {
e.printStackTrace(); BooksWithoutBorders.getInstance().getLogger().log(Level.SEVERE, "Invalid AES algorithm or padding");
return null; return null;
} }
return aes; return aes;
@@ -144,20 +152,21 @@ public class AES {
* @param password <p>A user supplied password</p> * @param password <p>A user supplied password</p>
* @return <p>A secret key spec or null if something went wrong</p> * @return <p>A secret key spec or null if something went wrong</p>
*/ */
private SecretKeySpec getKeyFromPassword(String password) { @Nullable
private SecretKeySpec getKeyFromPassword(@NotNull String password) {
PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), this.passwordSalt, 1000, 128); PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), this.passwordSalt, 1000, 128);
SecretKeyFactory keyFactory; SecretKeyFactory keyFactory;
try { try {
keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
} catch (NoSuchAlgorithmException e) { } catch (NoSuchAlgorithmException exception) {
e.printStackTrace(); BooksWithoutBorders.getInstance().getLogger().log(Level.SEVERE, "Invalid AES algorithm");
return null; return null;
} }
SecretKey tmp; SecretKey tmp;
try { try {
tmp = keyFactory.generateSecret(spec); tmp = keyFactory.generateSecret(spec);
} catch (InvalidKeySpecException e) { } catch (InvalidKeySpecException exception) {
e.printStackTrace(); BooksWithoutBorders.getInstance().getLogger().log(Level.SEVERE, "Invalid AES key specification");
return null; return null;
} }
return new SecretKeySpec(tmp.getEncoded(), "AES"); return new SecretKeySpec(tmp.getEncoded(), "AES");

View File

@@ -1,5 +1,7 @@
package net.knarcraft.bookswithoutborders.encryption; package net.knarcraft.bookswithoutborders.encryption;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.Random; import java.util.Random;
@@ -24,7 +26,7 @@ public class GenenCrypt {
* *
* @param key <p>The key used to generate the codon table</p> * @param key <p>The key used to generate the codon table</p>
*/ */
public GenenCrypt(String key) { public GenenCrypt(@NotNull String key) {
// define the initial, unshuffled codon list of 4 base codons // define the initial, unshuffled codon list of 4 base codons
ArrayList<String> originalCodonList = new ArrayList<>(); ArrayList<String> originalCodonList = new ArrayList<>();
@@ -33,7 +35,7 @@ public class GenenCrypt {
for (int j = 0; j < 4; j++) { for (int j = 0; j < 4; j++) {
for (int k = 0; k < 4; k++) { for (int k = 0; k < 4; k++) {
for (int l = 0; l < 4; l++) { 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]);
} }
} }
} }
@@ -49,7 +51,7 @@ public class GenenCrypt {
// use the random number generator and the originalCodonList to make a shuffled list // use the random number generator and the originalCodonList to make a shuffled list
ArrayList<String> shuffledCodonList = new ArrayList<>(); ArrayList<String> shuffledCodonList = new ArrayList<>();
while (originalCodonList.size() > 0) { while (!originalCodonList.isEmpty()) {
int index = ranGen.nextInt(originalCodonList.size()); int index = ranGen.nextInt(originalCodonList.size());
shuffledCodonList.add(originalCodonList.get(index)); shuffledCodonList.add(originalCodonList.get(index));
originalCodonList.remove(index); originalCodonList.remove(index);
@@ -97,9 +99,12 @@ public class GenenCrypt {
String s = charList[i]; String s = charList[i];
String[] sa = codonTable.get(s); String[] sa = codonTable.get(s);
switch (s) { switch (s) {
case "\t" -> System.out.println(i + "\t" + "\\t" + "\t" + sa[0] + ", " + sa[1] + ", " + sa[2] + ", " + sa[3]); case "\t" ->
case "\n" -> System.out.println(i + "\t" + "\\n" + "\t" + sa[0] + ", " + sa[1] + ", " + sa[2] + ", " + sa[3]); System.out.println(i + "\t" + "\\t" + "\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 "\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]); default -> System.out.println(i + "\t" + s + "\t" + sa[0] + ", " + sa[1] + ", " + sa[2] + ", " + sa[3]);
} }
} }
@@ -111,7 +116,8 @@ public class GenenCrypt {
* @param input <p>The input to encrypt</p> * @param input <p>The input to encrypt</p>
* @return <p>The encrypted input</p> * @return <p>The encrypted input</p>
*/ */
public String encrypt(String input) { @NotNull
public String encrypt(@NotNull String input) {
StringBuilder output = new StringBuilder(); StringBuilder output = new StringBuilder();
for (int i = 0; i < input.length(); i++) { for (int i = 0; i < input.length(); i++) {
// insert junk bases // insert junk bases
@@ -144,7 +150,8 @@ public class GenenCrypt {
* @param input <p>The input to decrypt</p> * @param input <p>The input to decrypt</p>
* @return <p>The decrypted input</p> * @return <p>The decrypted input</p>
*/ */
public String decrypt(String input) { @NotNull
public String decrypt(@NotNull String input) {
StringBuilder output = new StringBuilder(); StringBuilder output = new StringBuilder();
int keyCount = 0; int keyCount = 0;
int junk = key.charAt(0); int junk = key.charAt(0);

View File

@@ -1,5 +1,7 @@
package net.knarcraft.bookswithoutborders.encryption; package net.knarcraft.bookswithoutborders.encryption;
import org.jetbrains.annotations.NotNull;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.StringTokenizer; import java.util.StringTokenizer;
@@ -17,9 +19,10 @@ public class SubstitutionCipher {
// using a string for the key, it is converted // using a string for the key, it is converted
// a series of offsets that each character in the // a series of offsets that each character in the
// original message is offset by // original message is offset by
public String encrypt(String in, String key) { @NotNull
public String encrypt(@NotNull String in, @NotNull String key) {
StringBuilder output = new StringBuilder(); StringBuilder output = new StringBuilder();
if (key != null && key.length() > 0) { if (!key.isEmpty()) {
StringTokenizer tokenizer = new StringTokenizer(key, ", "); // tokenizes the key StringTokenizer tokenizer = new StringTokenizer(key, ", "); // tokenizes the key
// converts each number in the key to an integer and adds to an array // converts each number in the key to an integer and adds to an array
int[] offsetArray = new int[tokenizer.countTokens()]; int[] offsetArray = new int[tokenizer.countTokens()];
@@ -52,9 +55,11 @@ public class SubstitutionCipher {
// but in reverse. Could probably be combined into one // but in reverse. Could probably be combined into one
// method with a flag for encryption / decryption, but // method with a flag for encryption / decryption, but
// I'm lazy. // I'm lazy.
public String decrypt(String in, String key) { @SuppressWarnings("unused")
@NotNull
public String decrypt(@NotNull String in, @NotNull String key) {
StringBuilder output = new StringBuilder(); StringBuilder output = new StringBuilder();
if (key != null && key.length() > 0) { if (!key.isEmpty()) {
StringTokenizer tokenizer = new StringTokenizer(key, ", "); // tokenizes the key StringTokenizer tokenizer = new StringTokenizer(key, ", "); // tokenizes the key
// converts each number in the key to an integer and adds to an array // converts each number in the key to an integer and adds to an array
int[] offsetArray = new int[tokenizer.countTokens()]; int[] offsetArray = new int[tokenizer.countTokens()];
@@ -79,7 +84,9 @@ public class SubstitutionCipher {
// the one time pad encryption is very secure, and // the one time pad encryption is very secure, and
// encryption works just like decryption, but is // encryption works just like decryption, but is
// vulnerable if the same key is used more than once. // vulnerable if the same key is used more than once.
public String oneTimePad(String in, String key) { @SuppressWarnings("unused")
@NotNull
public String oneTimePad(@NotNull String in, @NotNull String key) {
StringBuilder output = new StringBuilder(); StringBuilder output = new StringBuilder();
for (int i = 0; i < in.length(); i++) { for (int i = 0; i < in.length(); i++) {
output.append((char) (in.charAt(i) ^ key.charAt(i % key.length()))); output.append((char) (in.charAt(i) ^ key.charAt(i % key.length())));

View File

@@ -0,0 +1,87 @@
package net.knarcraft.bookswithoutborders.gui;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.utility.BookFormatter;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.ClickEvent;
import net.md_5.bungee.api.chat.ComponentBuilder;
import net.md_5.bungee.api.chat.HoverEvent;
import net.md_5.bungee.api.chat.hover.content.Text;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class AuthorBookIndex extends BookIndex {
private final static int booksPerPage = 10;
/**
* Prints the available books
*
* @param sender <p>The sender to display the books to</p>
* @param listPublic <p>Whether to display public books</p>
* @param command <p>The base command causing this to be called</p>
* @param page <p>The page of the book list to display</p>
*/
public static void printBooks(@NotNull CommandSender sender, boolean listPublic, @NotNull String command, int page,
@NotNull String authorName) {
List<String> availableBooks = BooksWithoutBorders.getAvailableBooks(sender, listPublic);
availableBooks.removeIf((bookPath) -> !BookFormatter.stripColor(bookPath.substring(0, bookPath.length() - 4).split(",")[1]).equalsIgnoreCase(authorName));
int totalPages = (int) Math.ceil((double) availableBooks.size() / booksPerPage);
if (page > totalPages) {
sender.sendMessage(ChatColor.GRAY + "No such page");
} else {
showAuthorBooks(sender, command, page, totalPages, availableBooks, authorName);
}
}
/**
* Shows a menu listing available books from an author
*
* @param sender <p>The sender wanting to see the book menu</p>
* @param command <p>The main command used to trigger display of the book menu</p>
* @param page <p>The currently selected page</p>
* @param totalPages <p>The total amount of pages</p>
* @param availableBooks <p>All books available to the sender</p>
* @param authorName <p>The name of the author currently shown</p>
*/
private static void showAuthorBooks(@NotNull CommandSender sender, @NotNull String command, int page,
int totalPages, @NotNull List<String> availableBooks, @NotNull String authorName) {
ComponentBuilder componentBuilder = new ComponentBuilder();
// Display the list of books, with the next and previous buttons
displayPreviousButton(componentBuilder, command + " author" + authorName, page);
componentBuilder.append("\n");
displayBookList(componentBuilder, command, page, availableBooks);
displayNextButton(componentBuilder, command + " author" + authorName, page, totalPages);
// Display total pages and the manual change page command suggestion
componentBuilder.append(" ", ComponentBuilder.FormatRetention.NONE);
displayTotalPages(componentBuilder, page, totalPages);
componentBuilder.append(" ", ComponentBuilder.FormatRetention.NONE);
sender.spigot().sendMessage(componentBuilder.create());
}
/**
* Displays the list of books on the current page
*
* @param componentBuilder <p>The component builder to append to</p>
* @param command <p>The command used for switching pages</p>
* @param page <p>The current page</p>
* @param availableBooks <p>All available books</p>
*/
protected static void displayBookList(@NotNull ComponentBuilder componentBuilder, @NotNull String command, int page,
@NotNull List<String> availableBooks) {
int startIndex = (page - 1) * booksPerPage;
for (int bookIndex = startIndex; bookIndex < Math.min(startIndex + booksPerPage, availableBooks.size()); bookIndex++) {
componentBuilder.append(getNiceName(availableBooks.get(bookIndex))).color(ChatColor.WHITE).event(
new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/" + command + " " +
availableBooks.get(bookIndex))).event(
new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text("Select book by path")));
componentBuilder.append("\n");
}
}
}

View File

@@ -0,0 +1,163 @@
package net.knarcraft.bookswithoutborders.gui;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
import net.knarcraft.bookswithoutborders.utility.InputCleaningHelper;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.ClickEvent;
import net.md_5.bungee.api.chat.ComponentBuilder;
import net.md_5.bungee.api.chat.HoverEvent;
import net.md_5.bungee.api.chat.hover.content.Text;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import java.util.Map;
public abstract class BookIndex {
protected final static int booksPerPage = 10;
protected final static @NotNull ChatColor interactColor = ChatColor.of("#FFD700");
protected final static @NotNull ChatColor inactiveColor = ChatColor.of("#999999");
/**
* Displays the correct GUI, if specified in the given arguments
*
* @param arguments <p>The arguments given by a command sender</p>
* @param sender <p>The sender executing the command</p>
* @param selectPublic <p>Whether to display public books, or only those available to the command sender</p>
* @param command <p>The command used for changing pages and making the final selection</p>
* @return <p>True if the GUI was displayed</p>
*/
public static boolean displayPage(@NotNull String[] arguments, @NotNull CommandSender sender, boolean selectPublic,
@NotNull String command) {
if (arguments.length == 0) {
PagedBookIndex.printBooks(sender, selectPublic, command, 1);
return true;
} else if (arguments.length == 1) {
int page = InputCleaningHelper.parsePageNumber(arguments[0]);
if (page > 0) {
PagedBookIndex.printBooks(sender, selectPublic, command, page);
return true;
}
} else if (arguments.length == 2) {
String author = InputCleaningHelper.parseAuthorSpecifier(arguments[0]);
if (author != null) {
int page = InputCleaningHelper.parsePageNumber(arguments[1]);
if (page > 0) {
AuthorBookIndex.printBooks(sender, selectPublic, command, page, author);
}
return true;
}
}
return false;
}
/**
* Displays the suggestion for manually going to any page
*
* @param componentBuilder <p>The component builder to append to</p>
* @param command <p>The command used for switching pages</p>
* @param page <p>The current page</p>
*/
protected static void displayPageCommand(@NotNull ComponentBuilder componentBuilder, @NotNull String command, int page) {
componentBuilder.append("[Page Command]", ComponentBuilder.FormatRetention.NONE).color(interactColor).event(new HoverEvent(
HoverEvent.Action.SHOW_TEXT, new Text("/" + command + " page" + page))).event(
new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/" + command + " page" + page));
}
/**
* Displays the current page and total amount of pages
*
* @param componentBuilder <p>The component builder to append to</p>
* @param page <p>The current page</p>
* @param totalPages <p>The total amount of pages</p>
*/
protected static void displayTotalPages(@NotNull ComponentBuilder componentBuilder, int page, int totalPages) {
componentBuilder.append("Page " + page + " of " + totalPages,
ComponentBuilder.FormatRetention.NONE).color(inactiveColor);
}
/**
* Displays the alphabet-based page index
*
* @param componentBuilder <p>The component builder to append to</p>
* @param command <p>The command used for switching pages</p>
* @param firstInstances <p>The map of where the first index of a letter is found</p>
*/
protected static void displayAlphabetIndex(@NotNull ComponentBuilder componentBuilder,
@NotNull String command, @NotNull Map<Character, Integer> firstInstances) {
componentBuilder.append("[index] <", ComponentBuilder.FormatRetention.NONE).color(inactiveColor);
for (int characterIndex = 0; characterIndex <= 25; characterIndex++) {
char character = (char) ('a' + characterIndex);
if (firstInstances.containsKey(character)) {
int pageIndex = (firstInstances.get(character) / booksPerPage) + 1;
componentBuilder.append(character + "").color(interactColor).event(
new ClickEvent(ClickEvent.Action.RUN_COMMAND,
"/" + command + " page" + pageIndex)).event(new HoverEvent(
HoverEvent.Action.SHOW_TEXT, new Text("Books starting with " + character)));
} else {
componentBuilder.append(character + "", ComponentBuilder.FormatRetention.NONE).color(inactiveColor);
}
}
componentBuilder.append(">", ComponentBuilder.FormatRetention.NONE).color(inactiveColor);
}
/**
* Displays the previous page button
*
* @param componentBuilder <p>The component builder to append to</p>
* @param command <p>The command used for switching pages</p>
* @param page <p>The current page</p>
*/
protected static void displayPreviousButton(@NotNull ComponentBuilder componentBuilder,
@NotNull String command, int page) {
if (page > 1) {
String fullCommand = "/" + command + " page" + (page - 1);
HoverEvent prevPagePreview = new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text("To page " + (page - 1)));
ClickEvent prevPageClick = new ClickEvent(ClickEvent.Action.RUN_COMMAND, fullCommand);
componentBuilder.append("Previous [<]", ComponentBuilder.FormatRetention.NONE).color(interactColor)
.event(prevPagePreview).event(prevPageClick);
} else {
componentBuilder.append("Previous [<]", ComponentBuilder.FormatRetention.NONE).color(inactiveColor);
}
}
/**
* Displays the next page button
*
* @param componentBuilder <p>The component builder to append to</p>
* @param command <p>The command used for switching pages</p>
* @param page <p>The current page</p>
* @param totalPages <p>The total amount of pages</p>
*/
protected static void displayNextButton(@NotNull ComponentBuilder componentBuilder,
@NotNull String command, int page, int totalPages) {
if (page < totalPages) {
String fullCommand = "/" + command + " page" + (page + 1);
HoverEvent nextPagePreview = new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text("To page " + (page + 1)));
ClickEvent nextPageClick = new ClickEvent(ClickEvent.Action.RUN_COMMAND, fullCommand);
componentBuilder.append("Next [>]", ComponentBuilder.FormatRetention.NONE).color(interactColor)
.event(nextPagePreview).event(nextPageClick);
} else {
componentBuilder.append("Next [>]", ComponentBuilder.FormatRetention.NONE).color(inactiveColor);
}
}
/**
* Gets a nice name from a book's path
*
* @param bookPath <p>The path of a book</p>
* @return <p>The prettified book name</p>
*/
@NotNull
protected static String getNiceName(@NotNull String bookPath) {
String separator = BooksWithoutBordersConfig.getTitleAuthorSeparator();
bookPath = ChatColor.translateAlternateColorCodes('&', bookPath.substring(0, bookPath.length() - 4));
if (bookPath.contains(separator)) {
String[] parts = bookPath.split(separator);
return parts[0].replace("_", " ") + ChatColor.RESET + " by " + parts[1] + ChatColor.RESET;
} else {
return bookPath + ChatColor.RESET + " by Unknown" + ChatColor.RESET;
}
}
}

View File

@@ -0,0 +1,118 @@
package net.knarcraft.bookswithoutborders.gui;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.utility.BookFormatter;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.ClickEvent;
import net.md_5.bungee.api.chat.ComponentBuilder;
import net.md_5.bungee.api.chat.HoverEvent;
import net.md_5.bungee.api.chat.hover.content.Text;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* A class for displaying a paged index of all available books
*/
public class PagedBookIndex extends BookIndex {
/**
* Prints the available books
*
* @param sender <p>The sender to display the books to</p>
* @param listPublic <p>Whether to display public books</p>
* @param command <p>The base command causing this to be called</p>
* @param page <p>The page of the book list to display</p>
*/
public static void printBooks(@NotNull CommandSender sender, boolean listPublic, @NotNull String command, int page) {
List<String> availableBooks = BooksWithoutBorders.getAvailableBooks(sender, listPublic);
Map<Character, Integer> firstInstances;
if (listPublic) {
firstInstances = BooksWithoutBorders.getLetterIndex(null);
} else if (sender instanceof Player player) {
firstInstances = BooksWithoutBorders.getLetterIndex(player.getUniqueId());
} else {
firstInstances = new HashMap<>();
}
int totalPages = (int) Math.ceil((double) availableBooks.size() / booksPerPage);
if (page > totalPages) {
sender.sendMessage(ChatColor.GRAY + "No such page");
} else {
showBookMenu(sender, command, page, totalPages, availableBooks, firstInstances);
}
}
/**
* Shows a menu listing available books
*
* @param sender <p>The sender wanting to see the book menu</p>
* @param command <p>The main command used to trigger display of the book menu</p>
* @param page <p>The currently selected page</p>
* @param totalPages <p>The total amount of pages</p>
* @param availableBooks <p>All books available to the sender</p>
* @param firstInstances <p>The map between a character, and the index of the first instance of that character in the book list</p>
*/
private static void showBookMenu(@NotNull CommandSender sender, @NotNull String command, int page,
int totalPages, @NotNull List<String> availableBooks,
@NotNull Map<Character, Integer> firstInstances) {
ComponentBuilder componentBuilder = new ComponentBuilder();
// Display the list of books, with the next and previous buttons
displayPreviousButton(componentBuilder, command, page);
componentBuilder.append("\n");
displayBookList(componentBuilder, command, page, availableBooks);
displayNextButton(componentBuilder, command, page, totalPages);
// Display total pages and the manual change page command suggestion
componentBuilder.append(" ", ComponentBuilder.FormatRetention.NONE);
displayTotalPages(componentBuilder, page, totalPages);
componentBuilder.append(" ", ComponentBuilder.FormatRetention.NONE);
displayPageCommand(componentBuilder, command, page);
componentBuilder.append("\n");
// Display the alphabet index as the header
displayAlphabetIndex(componentBuilder, command, firstInstances);
sender.spigot().sendMessage(componentBuilder.create());
}
/**
* Displays the list of books on the current page
*
* @param componentBuilder <p>The component builder to append to</p>
* @param command <p>The command used for switching pages</p>
* @param page <p>The current page</p>
* @param availableBooks <p>All available books</p>
*/
protected static void displayBookList(@NotNull ComponentBuilder componentBuilder, @NotNull String command, int page,
@NotNull List<String> availableBooks) {
int startIndex = (page - 1) * booksPerPage;
for (int bookIndex = startIndex; bookIndex < Math.min(startIndex + booksPerPage, availableBooks.size()); bookIndex++) {
componentBuilder.append("[" + (bookIndex + 1) + "]").color(interactColor).event(
new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/" + command + " " +
(bookIndex + 1))).event(new HoverEvent(HoverEvent.Action.SHOW_TEXT,
new Text("Select book by index")));
componentBuilder.append(" ", ComponentBuilder.FormatRetention.NONE);
String[] parts = getNiceName(availableBooks.get(bookIndex)).split(" by ");
componentBuilder.append(parts[0]).color(ChatColor.WHITE).event(
new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/" + command + " " +
availableBooks.get(bookIndex))).event(
new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text("Select book by path")));
componentBuilder.append(" by ", ComponentBuilder.FormatRetention.NONE).color(ChatColor.WHITE);
componentBuilder.append(parts[1]).color(ChatColor.WHITE).event(
new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/" + command + " author" +
BookFormatter.stripColor(parts[1]) + " page1")).event(
new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text("Books by " +
BookFormatter.stripColor(parts[1]))));
componentBuilder.append("\n");
}
}
}

View File

@@ -0,0 +1,145 @@
package net.knarcraft.bookswithoutborders.handler;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.container.Bookshelf;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.logging.Level;
/**
* A handler keeping track of all bookshelves
*/
public class BookshelfHandler {
private static final File bookshelfFile = new File(BooksWithoutBorders.getInstance().getDataFolder(),
"bookshelves.yml");
private Set<Bookshelf> bookshelves;
private Map<Location, Bookshelf> locationLookup;
/**
* Gets a bookshelf from the given location
*
* @param location <p>The location of the bookshelf</p>
* @return <p>The bookshelf at the location, or null if no such bookshelf exists</p>
*/
@Nullable
public Bookshelf getFromLocation(@NotNull Location location) {
return locationLookup.get(location);
}
/**
* Registers the given bookshelf to this handler
*
* @param bookshelf <p>The bookshelf to register</p>
*/
public void registerBookshelf(@NotNull Bookshelf bookshelf) {
this.bookshelves.add(bookshelf);
this.locationLookup.put(bookshelf.getLocation(), bookshelf);
}
/**
* Unregisters the given bookshelf from this handler
*
* @param bookshelf <p>The bookshelf to unregister</p>
*/
public void unregisterBookshelf(@NotNull Bookshelf bookshelf) {
this.locationLookup.remove(bookshelf.getLocation());
this.bookshelves.remove(bookshelf);
}
/**
* Loads all stored bookshelves
*/
public void load() {
this.bookshelves = new HashSet<>();
this.locationLookup = new HashMap<>();
YamlConfiguration configuration = YamlConfiguration.loadConfiguration(bookshelfFile);
ConfigurationSection bookshelfSection = configuration.getConfigurationSection("bookshelves");
if (bookshelfSection == null) {
BooksWithoutBorders.getInstance().getLogger().log(Level.INFO,
"BooksWithoutBorders found no bookshelves to load");
return;
}
for (String key : bookshelfSection.getKeys(false)) {
String[] locationInfo = key.split(",");
World world = Bukkit.getWorld(UUID.fromString(locationInfo[0]));
double x = Integer.parseInt(locationInfo[1]);
double y = Integer.parseInt(locationInfo[2]);
double z = Integer.parseInt(locationInfo[3]);
Location bookshelfLocation = new Location(world, x, y, z);
String titleKey = key + ".title";
String loreKey = key + ".lore";
String title = bookshelfSection.getString(titleKey, null);
List<String> loreStrings = new ArrayList<>();
List<?> lore = bookshelfSection.getList(loreKey);
if (lore == null) {
throw new IllegalArgumentException("Lore is missing from bookshelf data!");
}
lore.forEach((item) -> {
if (item instanceof String) {
loreStrings.add((String) item);
}
});
if (title != null) {
registerBookshelf(new Bookshelf(bookshelfLocation, title, loreStrings));
}
}
}
/**
* Saves all current bookshelves
*/
public void save() {
try {
YamlConfiguration configuration = new YamlConfiguration();
ConfigurationSection bookshelfSection = configuration.createSection("bookshelves");
for (Bookshelf bookshelf : bookshelves) {
saveBookshelf(bookshelfSection, bookshelf);
}
configuration.save(bookshelfFile);
} catch (IOException exception) {
BooksWithoutBorders.getInstance().getLogger().log(Level.SEVERE, "Unable to save bookshelves!");
}
}
/**
* Saves a bookshelf to the given configuration section
*
* @param section <p>The configuration section to save to</p>
* @param bookshelf <p>The bookshelf to save</p>
*/
private void saveBookshelf(@NotNull ConfigurationSection section, @NotNull Bookshelf bookshelf) {
Location location = bookshelf.getLocation();
if (location.getWorld() == null) {
return;
}
String key = location.getWorld().getUID() + "," + location.getBlockX() + "," + location.getBlockY() +
"," + location.getBlockZ();
String titleKey = key + ".title";
String loreKey = key + ".lore";
section.set(titleKey, bookshelf.getTitle());
section.set(loreKey, bookshelf.getLore());
}
}

View File

@@ -5,6 +5,7 @@ import net.knarcraft.bookswithoutborders.utility.BookFormatter;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerEditBookEvent; import org.bukkit.event.player.PlayerEditBookEvent;
import org.jetbrains.annotations.NotNull;
/** /**
* A listener for listening to book events * A listener for listening to book events
@@ -14,7 +15,7 @@ import org.bukkit.event.player.PlayerEditBookEvent;
public class BookEventListener implements Listener { public class BookEventListener implements Listener {
@EventHandler @EventHandler
public void onBookSign(PlayerEditBookEvent event) { public void onBookSign(@NotNull PlayerEditBookEvent event) {
if (event.isCancelled() || !event.isSigning() || !BooksWithoutBordersConfig.formatBooks()) { if (event.isCancelled() || !event.isSigning() || !BooksWithoutBordersConfig.formatBooks()) {
return; return;
} }

View File

@@ -0,0 +1,212 @@
package net.knarcraft.bookswithoutborders.listener;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
import net.knarcraft.bookswithoutborders.container.Bookshelf;
import net.knarcraft.bookswithoutborders.handler.BookshelfHandler;
import net.knarcraft.bookswithoutborders.utility.IntegerToRomanConverter;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
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.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.block.BlockBreakEvent;
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 org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* A listener for bookshelf clicking
*/
public class BookshelfListener implements Listener {
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
public void onBookshelfBreak(@NotNull BlockBreakEvent event) {
Block block = event.getBlock();
if (block.getType() != Material.CHISELED_BOOKSHELF) {
return;
}
BookshelfHandler bookshelfHandler = BooksWithoutBorders.getBookshelfHandler();
Bookshelf bookshelf = bookshelfHandler.getFromLocation(block.getLocation());
if (bookshelf != null) {
bookshelfHandler.unregisterBookshelf(bookshelf);
bookshelfHandler.save();
}
}
@EventHandler
public void onBookshelfClick(@NotNull 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, event.getClickedBlock().getLocation()));
}
/**
* Gets the description for a bookshelf's contents
*
* @param bookshelfInventory <p>The inventory of the bookshelf to describe</p>
* @param location <p>The location of the clicked bookshelf</p>
* @return <p>A textual description of the bookshelf's contents</p>
*/
@NotNull
private String getBookshelfDescription(@NotNull ChiseledBookshelfInventory bookshelfInventory, @NotNull Location location) {
StringBuilder builder = new StringBuilder();
Bookshelf bookshelf = BooksWithoutBorders.getBookshelfHandler().getFromLocation(location);
if (bookshelf != null) {
builder.append(ChatColor.of("#FF5700")).append("Books in ").append(bookshelf.getTitle())
.append(":").append(ChatColor.RESET);
for (String lore : bookshelf.getLore()) {
builder.append("\n ").append(ChatColor.LIGHT_PURPLE).append(lore);
}
} else {
builder.append(ChatColor.of("#FF5700")).append("Books in shelf:").append(ChatColor.RESET);
}
for (int i = 0; i < bookshelfInventory.getSize(); i++) {
int index = (i % 3) + 1;
if (i % 3 == 0) {
builder.append("\n ").append(ChatColor.of("#FF5700")).append(
i < 3 ? "Top Row:" : "Bottom Row:").append(ChatColor.RESET);
}
builder.append("\n ").append(ChatColor.of("#ffd700")).append(index).append(". ").append(ChatColor.RESET);
ItemStack itemStack = bookshelfInventory.getItem(i);
if (itemStack == null) {
builder.append(ChatColor.GRAY).append("<empty>");
continue;
}
ItemMeta meta = itemStack.getItemMeta();
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(ChatColor.of("#A5682A")).append("[P]").append(ChatColor.RESET).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>
*/
@NotNull
private String getPlainBookDescription(@NotNull 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>
*/
@NotNull
private String getBookDescription(@NotNull 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 ChatColor.of("#686868") + "[Q]" + ChatColor.RESET + title + ChatColor.RESET + " 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>
*/
@NotNull
private String getEnchantedBookDescription(@NotNull EnchantmentStorageMeta enchantmentStorageMeta) {
StringBuilder builder = new StringBuilder();
builder.append(ChatColor.of("#A64CFF")).append("[E]").append(ChatColor.RESET);
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));
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>
*/
@NotNull
private String getEnchantmentName(@NotNull Enchantment enchantment) {
// Note: While depreciated, changing this is incompatible with Paper
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>
*/
@NotNull
private String uppercaseFirst(@NotNull 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

@@ -2,6 +2,8 @@ package net.knarcraft.bookswithoutborders.listener;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders; import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig; import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
import net.knarcraft.bookswithoutborders.state.BookDirectory;
import net.knarcraft.bookswithoutborders.utility.BookHelper;
import net.knarcraft.bookswithoutborders.utility.BookLoader; import net.knarcraft.bookswithoutborders.utility.BookLoader;
import net.knarcraft.bookswithoutborders.utility.InputCleaningHelper; import net.knarcraft.bookswithoutborders.utility.InputCleaningHelper;
import net.knarcraft.bookswithoutborders.utility.InventoryHelper; import net.knarcraft.bookswithoutborders.utility.InventoryHelper;
@@ -15,22 +17,21 @@ import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory; import org.bukkit.inventory.PlayerInventory;
import org.bukkit.inventory.meta.BookMeta; import org.bukkit.inventory.meta.BookMeta;
import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.ItemMeta;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File; import java.io.File;
import java.util.logging.Level;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getBookFolder;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getSlash;
/** /**
* A listener for listening to player-related events such as joining or holding a book * A listener for listening to player-related events such as joining or holding a book
*/ */
public class PlayerEventListener implements Listener { public class PlayerEventListener implements Listener {
private final String slash = getSlash();
private final BooksWithoutBorders booksWithoutBorders = BooksWithoutBorders.getInstance(); private final BooksWithoutBorders booksWithoutBorders = BooksWithoutBorders.getInstance();
@EventHandler @EventHandler
public void onHold(PlayerItemHeldEvent event) { public void onHold(@NotNull PlayerItemHeldEvent event) {
if (event.isCancelled()) { if (event.isCancelled()) {
return; return;
} }
@@ -55,9 +56,19 @@ public class PlayerEventListener implements Listener {
} }
@EventHandler @EventHandler
public void onPlayerJoin(PlayerJoinEvent event) { public void onPlayerJoin(@NotNull PlayerJoinEvent event) {
Player player = event.getPlayer(); Player player = event.getPlayer();
//If a book directory exists with this player's name, move it to this player's UUID
String bookFolder = BooksWithoutBordersConfig.getBookFolder();
File file = new File(bookFolder + InputCleaningHelper.cleanString(player.getName()));
if (file.exists()) {
if (!file.renameTo(new File(bookFolder + player.getUniqueId()))) {
BooksWithoutBorders.getInstance().getLogger().log(Level.WARNING, "Unable to migrate player book " +
"directory for player " + player.getName());
}
}
//Handle new players //Handle new players
if (!player.hasPlayedBefore()) { if (!player.hasPlayedBefore()) {
boolean sendMessage = true; boolean sendMessage = true;
@@ -73,11 +84,15 @@ public class PlayerEventListener implements Listener {
ItemStack offHandItem = InventoryHelper.getHeldItem(player, false); ItemStack offHandItem = InventoryHelper.getHeldItem(player, false);
if (mainHandItem.getType() == Material.WRITTEN_BOOK) { if (mainHandItem.getType() == Material.WRITTEN_BOOK) {
ItemMeta itemMetadata = mainHandItem.getItemMeta(); ItemMeta itemMetadata = mainHandItem.getItemMeta();
updateBookInHand(player, itemMetadata, true); if (itemMetadata != null) {
updateBookInHand(player, itemMetadata, true);
}
} }
if (offHandItem.getType() == Material.WRITTEN_BOOK) { if (offHandItem.getType() == Material.WRITTEN_BOOK) {
ItemMeta itemMetadata = offHandItem.getItemMeta(); ItemMeta itemMetadata = offHandItem.getItemMeta();
updateBookInHand(player, itemMetadata, false); if (itemMetadata != null) {
updateBookInHand(player, itemMetadata, false);
}
} }
} }
@@ -89,7 +104,7 @@ public class PlayerEventListener implements Listener {
* @param sendMessage <p>Whether to send a message to the joining player</p> * @param sendMessage <p>Whether to send a message to the joining player</p>
* @return <p>True if a message has yet to be sent</p> * @return <p>True if a message has yet to be sent</p>
*/ */
private boolean giveBookToNewPlayer(String bookName, Player player, boolean sendMessage) { private boolean giveBookToNewPlayer(@NotNull String bookName, @NotNull Player player, boolean sendMessage) {
if (!bookName.trim().isEmpty()) { if (!bookName.trim().isEmpty()) {
//Give the book to the player if it exists //Give the book to the player if it exists
@@ -116,7 +131,7 @@ public class PlayerEventListener implements Listener {
* @param itemMetadata <p>Information about the held book</p> * @param itemMetadata <p>Information about the held book</p>
* @param mainHand <p>Whether to update the book in the player's main hand</p> * @param mainHand <p>Whether to update the book in the player's main hand</p>
*/ */
private void updateBookInHand(Player player, ItemMeta itemMetadata, boolean mainHand) { private void updateBookInHand(@NotNull Player player, @NotNull ItemMeta itemMetadata, boolean mainHand) {
PlayerInventory playerInventory = player.getInventory(); PlayerInventory playerInventory = player.getInventory();
ItemStack updatedBook = updateBook(player, (BookMeta) itemMetadata); ItemStack updatedBook = updateBook(player, (BookMeta) itemMetadata);
if (updatedBook != null) { if (updatedBook != null) {
@@ -135,7 +150,8 @@ public class PlayerEventListener implements Listener {
* @param oldBook <p>Metadata about the held book</p> * @param oldBook <p>Metadata about the held book</p>
* @return <p>An updated book</p> * @return <p>An updated book</p>
*/ */
public ItemStack updateBook(Player player, BookMeta oldBook) { @Nullable
public ItemStack updateBook(@NotNull Player player, @NotNull BookMeta oldBook) {
//handles hacked title-less books //handles hacked title-less books
if (oldBook.getTitle() == null || oldBook.getTitle().length() < 3) { if (oldBook.getTitle() == null || oldBook.getTitle().length() < 3) {
return null; return null;
@@ -151,13 +167,14 @@ public class PlayerEventListener implements Listener {
fileName = oldBook.getTitle() + BooksWithoutBordersConfig.getTitleAuthorSeparator() + oldBook.getAuthor(); fileName = oldBook.getTitle() + BooksWithoutBordersConfig.getTitleAuthorSeparator() + oldBook.getAuthor();
} }
String cleanPlayerName = InputCleaningHelper.cleanString(player.getName()); String playerFolderPath = BookHelper.getBookDirectoryPathString(BookDirectory.PLAYER, player);
String publicFolderPath = BookHelper.getBookDirectoryPathString(BookDirectory.PUBLIC, player);
String[] possiblePaths = new String[]{ String[] possiblePaths = new String[]{
getBookFolder() + fileName + ".yml", publicFolderPath + fileName + ".yml",
getBookFolder() + fileName + ".txt", publicFolderPath + fileName + ".txt",
getBookFolder() + cleanPlayerName + slash + fileName + ".yml", playerFolderPath + fileName + ".yml",
getBookFolder() + cleanPlayerName + slash + fileName + ".txt" playerFolderPath + fileName + ".txt"
}; };
for (String path : possiblePaths) { for (String path : possiblePaths) {

View File

@@ -3,15 +3,19 @@ package net.knarcraft.bookswithoutborders.listener;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders; import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig; import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
import net.knarcraft.bookswithoutborders.state.EncryptionStyle; import net.knarcraft.bookswithoutborders.state.EncryptionStyle;
import net.knarcraft.bookswithoutborders.utility.BookFileHelper;
import net.knarcraft.bookswithoutborders.utility.BookFormatter;
import net.knarcraft.bookswithoutborders.utility.BookLoader; import net.knarcraft.bookswithoutborders.utility.BookLoader;
import net.knarcraft.bookswithoutborders.utility.EncryptionHelper; import net.knarcraft.bookswithoutborders.utility.EncryptionHelper;
import net.knarcraft.bookswithoutborders.utility.FileHelper;
import net.knarcraft.bookswithoutborders.utility.InputCleaningHelper; import net.knarcraft.bookswithoutborders.utility.InputCleaningHelper;
import org.bukkit.ChatColor; import net.md_5.bungee.api.ChatColor;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.Tag; import org.bukkit.Tag;
import org.bukkit.block.Sign; 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.entity.Player;
import org.bukkit.event.Event;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.block.Action; import org.bukkit.event.block.Action;
@@ -21,12 +25,14 @@ import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory; import org.bukkit.inventory.PlayerInventory;
import org.bukkit.inventory.meta.BookMeta; import org.bukkit.inventory.meta.BookMeta;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File; import java.io.File;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getBookFolder; import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getBookFolder;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getSlash; import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getSlash;
import static net.knarcraft.bookswithoutborders.utility.FileHelper.isBookListIndex; import static net.knarcraft.bookswithoutborders.utility.BookFileHelper.isBookListIndex;
/** /**
* A listener for relevant sign events such as clicking a decryption sign * A listener for relevant sign events such as clicking a decryption sign
@@ -36,7 +42,7 @@ public class SignEventListener implements Listener {
private final String slash = getSlash(); private final String slash = getSlash();
@EventHandler @EventHandler
public void onSignChange(SignChangeEvent event) { public void onSignChange(@NotNull SignChangeEvent event) {
if (event.isCancelled()) { if (event.isCancelled()) {
return; return;
} }
@@ -54,7 +60,7 @@ public class SignEventListener implements Listener {
//Check if the sign is of a valid type //Check if the sign is of a valid type
if (!((lines[1].equalsIgnoreCase("[Encrypt]") || lines[1].equalsIgnoreCase("[Decrypt]") || 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 //Mark the second line as invalid
event.setLine(1, ChatColor.DARK_RED + lines[1]); event.setLine(1, ChatColor.DARK_RED + lines[1]);
return; return;
@@ -76,7 +82,7 @@ public class SignEventListener implements Listener {
} }
@EventHandler @EventHandler
public void onClick(PlayerInteractEvent event) { public void onClick(@NotNull PlayerInteractEvent event) {
if (event.getClickedBlock() == null) { if (event.getClickedBlock() == null) {
return; return;
} }
@@ -96,20 +102,25 @@ public class SignEventListener implements Listener {
if (event.getAction() == Action.RIGHT_CLICK_BLOCK && (Tag.SIGNS.isTagged(clickedBlockType) || if (event.getAction() == Action.RIGHT_CLICK_BLOCK && (Tag.SIGNS.isTagged(clickedBlockType) ||
Tag.WALL_SIGNS.isTagged(clickedBlockType))) { Tag.WALL_SIGNS.isTagged(clickedBlockType))) {
event.setUseItemInHand(Event.Result.DENY);
//The player right-clicked a sign //The player right-clicked a sign
Sign sign = (Sign) event.getClickedBlock().getState(); Sign sign = (Sign) event.getClickedBlock().getState();
if (signLineEquals(sign, 0, "[BwB]", ChatColor.DARK_GREEN)) { if (!signLineEquals(sign, 0, "[BwB]", ChatColor.DARK_GREEN)) {
if (signLineEquals(sign, 1, "[Encrypt]", ChatColor.DARK_BLUE)) { return;
encryptHeldBookUsingSign(sign, heldItemType, player, hand); }
} else if (signLineEquals(sign, 1, "[Decrypt]", ChatColor.DARK_BLUE)) {
decryptHeldBookUsingSign(sign, heldItemType, player, hand); if (signLineEquals(sign, 1, "[Encrypt]", ChatColor.DARK_BLUE)) {
} else if (signLineEquals(sign, 1, "[Give]", ChatColor.DARK_BLUE) && encryptHeldBookUsingSign(sign, heldItemType, player, hand);
getSignLine2Color(sign) == ChatColor.DARK_GREEN) { } else if (signLineEquals(sign, 1, "[Decrypt]", ChatColor.DARK_BLUE)) {
giveBook(sign, player); decryptHeldBookUsingSign(sign, heldItemType, player, hand);
} else { } else if (signLineEquals(sign, 1, "[Give]", ChatColor.DARK_BLUE) &&
player.sendMessage("Sign command " + sign.getLine(1) + " " + sign.getLine(2) + " invalid"); getSignLine2Color(sign) == ChatColor.DARK_GREEN) {
player.sendMessage(String.valueOf(getSignLine2Color(sign))); 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 } else if (heldItemType == Material.WRITTEN_BOOK && (event.getAction() == Action.LEFT_CLICK_AIR
|| event.getAction() == Action.LEFT_CLICK_BLOCK)) { || event.getAction() == Action.LEFT_CLICK_BLOCK)) {
@@ -130,13 +141,14 @@ public class SignEventListener implements Listener {
* @param player <p>The player which clicked the sign</p> * @param player <p>The player which clicked the sign</p>
* @param hand <p>The EquipmentSlot of the used hand</p> * @param hand <p>The EquipmentSlot of the used hand</p>
*/ */
private void decryptHeldBookUsingSign(Sign sign, Material heldItemType, Player player, EquipmentSlot hand) { private void decryptHeldBookUsingSign(@NotNull Sign sign, @NotNull Material heldItemType, @NotNull Player player,
@NotNull EquipmentSlot hand) {
//Decrypt the held book and replace it //Decrypt the held book and replace it
if (heldItemType == Material.WRITTEN_BOOK) { if (heldItemType == Material.WRITTEN_BOOK) {
player.closeInventory(); player.closeInventory();
//Converts user supplied key into integer form //Converts user supplied key into integer form
String lineText = ChatColor.stripColor(sign.getLine(2)); String lineText = BookFormatter.stripColor(sign.getSide(Side.FRONT).getLine(2));
String key = EncryptionHelper.getNumberKeyFromStringKey(lineText); String key = EncryptionHelper.getNumberKeyFromStringKey(lineText);
ItemStack book = EncryptionHelper.loadEncryptedBook(player, key, false); ItemStack book = EncryptionHelper.loadEncryptedBook(player, key, false);
@@ -153,10 +165,11 @@ public class SignEventListener implements Listener {
* @param sign <p>The sign to check</p> * @param sign <p>The sign to check</p>
* @return <p>The color of the sign</p> * @return <p>The color of the sign</p>
*/ */
private ChatColor getSignLine2Color(Sign sign) { @Nullable
String line = sign.getLine(2); private ChatColor getSignLine2Color(@NotNull Sign sign) {
if (!ChatColor.stripColor(line).equals(line)) { String line = sign.getSide(Side.FRONT).getLine(2);
return ChatColor.getByChar(sign.getLine(2).substring(1, 2)); if (!BookFormatter.stripColor(line).equals(line)) {
return ChatColor.getByChar(sign.getSide(Side.FRONT).getLine(2).substring(1, 2).charAt(0));
} else { } else {
return null; return null;
} }
@@ -171,8 +184,9 @@ public class SignEventListener implements Listener {
* @param color <p>The color to match</p> * @param color <p>The color to match</p>
* @return <p>True if the given string is what's on the sign</p> * @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) { private boolean signLineEquals(@NotNull Sign sign, int lineNumber, @NotNull String compareTo,
String line = sign.getLine(lineNumber); @NotNull ChatColor color) {
String line = sign.getSide(Side.FRONT).getLine(lineNumber);
return line.equalsIgnoreCase(color + compareTo); return line.equalsIgnoreCase(color + compareTo);
} }
@@ -183,16 +197,18 @@ public class SignEventListener implements Listener {
* @param lines <p>The lines on the sign</p> * @param lines <p>The lines on the sign</p>
* @param player <p>The player which edited the sign</p> * @param player <p>The player which edited the sign</p>
*/ */
private void generateGiveSign(SignChangeEvent event, String[] lines, Player player) { private void generateGiveSign(@NotNull SignChangeEvent event, @NotNull String[] lines,
@NotNull Player player) {
if (lines[2].length() > 13 || lines[3].length() > 13) { if (lines[2].length() > 13 || lines[3].length() > 13) {
player.sendMessage(ChatColor.RED + "[Give] signs' 3rd and 4th lines must be 13 characters or less!"); BooksWithoutBorders.sendErrorMessage(player,
"[Give] signs' 3rd and 4th lines must be 13 characters or less!");
markGiveSignValidity(event, false); markGiveSignValidity(event, false);
return; return;
} }
//Tests if a full file name has been supplied and points to an actual file //Tests if a full file name has been supplied and points to an actual file
String signFile = getBookFolder() + lines[2] + lines[3]; String signFile = getBookFolder() + lines[2] + lines[3];
if (FileHelper.bookFileExists(signFile)) { if (BookFileHelper.bookFileExists(signFile)) {
markGiveSignValidity(event, true); markGiveSignValidity(event, true);
return; return;
} else { } else {
@@ -211,7 +227,7 @@ public class SignEventListener implements Listener {
* @param event <p>The event causing the creation of the give sign</p> * @param event <p>The event causing the creation of the give sign</p>
* @param isValid <p>Whether the created sign is valid</p> * @param isValid <p>Whether the created sign is valid</p>
*/ */
private void markGiveSignValidity(SignChangeEvent event, boolean isValid) { private void markGiveSignValidity(@NotNull SignChangeEvent event, boolean isValid) {
String[] lines = event.getLines(); String[] lines = event.getLines();
if (isValid) { if (isValid) {
event.setLine(2, ChatColor.DARK_GREEN + lines[2]); event.setLine(2, ChatColor.DARK_GREEN + lines[2]);
@@ -230,7 +246,8 @@ public class SignEventListener implements Listener {
* @param heldItem <p>The type of the held book</p> * @param heldItem <p>The type of the held book</p>
* @param hand <p>The hand the player is using to hold the book</p> * @param hand <p>The hand the player is using to hold the book</p>
*/ */
private void decryptBook(BookMeta oldBook, Player player, ItemStack heldItem, EquipmentSlot hand) { private void decryptBook(@NotNull BookMeta oldBook, @NotNull Player player, @NotNull ItemStack heldItem,
@NotNull EquipmentSlot hand) {
ItemStack newBook; ItemStack newBook;
//Check if the book is encrypted by Books Without Borders //Check if the book is encrypted by Books Without Borders
@@ -265,7 +282,7 @@ public class SignEventListener implements Listener {
player.getInventory().setItem(hand, newBook); player.getInventory().setItem(hand, newBook);
player.closeInventory(); player.closeInventory();
player.sendMessage(ChatColor.GREEN + "Book auto-decrypted!"); BooksWithoutBorders.sendSuccessMessage(player, "Book auto-decrypted!");
} }
/** /**
@@ -276,14 +293,15 @@ public class SignEventListener implements Listener {
* @param player <p>The player which clicked the sign</p> * @param player <p>The player which clicked the sign</p>
* @param hand <p>The EquipmentSlot of the used hand</p> * @param hand <p>The EquipmentSlot of the used hand</p>
*/ */
private void encryptHeldBookUsingSign(Sign sign, Material heldItemType, Player player, EquipmentSlot hand) { private void encryptHeldBookUsingSign(@NotNull Sign sign, @NotNull Material heldItemType, @NotNull Player player,
@NotNull EquipmentSlot hand) {
ItemStack eBook; ItemStack eBook;
String[] lines = sign.getLines(); String[] lines = sign.getSide(Side.FRONT).getLines();
boolean mainHand = hand == EquipmentSlot.HAND; boolean mainHand = hand == EquipmentSlot.HAND;
if (heldItemType == Material.WRITTEN_BOOK) { if (heldItemType == Material.WRITTEN_BOOK) {
player.closeInventory(); player.closeInventory();
eBook = EncryptionHelper.encryptBook(player, mainHand, ChatColor.stripColor(lines[2]), eBook = EncryptionHelper.encryptBook(player, mainHand, BookFormatter.stripColor(lines[2]),
EncryptionStyle.getFromString(ChatColor.stripColor(lines[3]))); EncryptionStyle.getFromString(BookFormatter.stripColor(lines[3])));
if (eBook != null) { if (eBook != null) {
player.getInventory().setItem(hand, eBook); player.getInventory().setItem(hand, eBook);
} }
@@ -296,8 +314,8 @@ public class SignEventListener implements Listener {
* @param sign <p>The sign the user clicked</p> * @param sign <p>The sign the user clicked</p>
* @param player <p>The player which clicked the sign</p> * @param player <p>The player which clicked the sign</p>
*/ */
private void giveBook(Sign sign, Player player) { private void giveBook(@NotNull Sign sign, @NotNull Player player) {
String fileName = ChatColor.stripColor(sign.getLine(2)); String fileName = BookFormatter.stripColor(sign.getSide(Side.FRONT).getLine(2));
boolean isLoadListNumber = false; boolean isLoadListNumber = false;
try { try {
@@ -307,18 +325,18 @@ public class SignEventListener implements Listener {
} }
//Add the third line to the second line for the full filename //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) { if (!isLoadListNumber && thirdLine.length() >= 2) {
fileName += ChatColor.stripColor(thirdLine); fileName += BookFormatter.stripColor(thirdLine);
} }
ItemStack newBook = BookLoader.loadBook(player, fileName, "true", "public"); ItemStack newBook = BookLoader.loadBook(player, fileName, "true", "public");
if (newBook != null) { if (newBook != null) {
player.getInventory().addItem(newBook); player.getInventory().addItem(newBook);
player.sendMessage(ChatColor.GREEN + "Received book!"); BooksWithoutBorders.sendSuccessMessage(player, "Received book!");
} else { } else {
player.sendMessage(ChatColor.RED + "Book failed to load!"); BooksWithoutBorders.sendErrorMessage(player, "Book failed to load!");
} }
} }

View File

@@ -1,5 +1,8 @@
package net.knarcraft.bookswithoutborders.state; package net.knarcraft.bookswithoutborders.state;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/** /**
* This enum represents the different directories books can be saved in * This enum represents the different directories books can be saved in
*/ */
@@ -26,7 +29,8 @@ public enum BookDirectory {
* @param directory <p>The directory to transform</p> * @param directory <p>The directory to transform</p>
* @return <p>A book directory, or null if the given directory is empty</p> * @return <p>A book directory, or null if the given directory is empty</p>
*/ */
public static BookDirectory getFromString(String directory) { @Nullable
public static BookDirectory getFromString(@NotNull String directory) {
if (directory.equalsIgnoreCase("public")) { if (directory.equalsIgnoreCase("public")) {
return BookDirectory.PUBLIC; return BookDirectory.PUBLIC;
} else if (directory.equalsIgnoreCase("player")) { } else if (directory.equalsIgnoreCase("player")) {

View File

@@ -26,22 +26,22 @@ public enum BookHoldingState {
UNSIGNED_MAIN_HAND, 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, 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, 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, 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, UNSIGNED_MAIN_HAND_SIGNED_OFF_HAND,

View File

@@ -1,5 +1,7 @@
package net.knarcraft.bookswithoutborders.state; package net.knarcraft.bookswithoutborders.state;
import org.jetbrains.annotations.NotNull;
/** /**
* This enum represents the different available encryption styles * This enum represents the different available encryption styles
*/ */
@@ -10,7 +12,7 @@ public enum EncryptionStyle {
private final String name; private final String name;
EncryptionStyle(String name) { EncryptionStyle(@NotNull String name) {
this.name = name; this.name = name;
} }
@@ -20,7 +22,8 @@ public enum EncryptionStyle {
* @param name <p>The name of the encryption style</p> * @param name <p>The name of the encryption style</p>
* @return <p>An encryption style or null if no match is found</p> * @return <p>An encryption style or null if no match is found</p>
*/ */
public static EncryptionStyle getFromString(String name) { @NotNull
public static EncryptionStyle getFromString(@NotNull String name) {
for (EncryptionStyle style : EncryptionStyle.values()) { for (EncryptionStyle style : EncryptionStyle.values()) {
if (style.name.equalsIgnoreCase(name)) { if (style.name.equalsIgnoreCase(name)) {
return style; return style;

View File

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

View File

@@ -1,29 +1,29 @@
package net.knarcraft.bookswithoutborders.utility; package net.knarcraft.bookswithoutborders.utility;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders; import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import org.bukkit.ChatColor; import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
import net.knarcraft.bookswithoutborders.state.BookDirectory;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.BufferedReader;
import java.io.File; import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getBookFolder; import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getBookFolder;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getSlash;
import static net.knarcraft.bookswithoutborders.utility.InputCleaningHelper.cleanString;
/** /**
* Helper class for dealing with files * Helper class for dealing with files
*/ */
public final class FileHelper { public final class BookFileHelper {
private FileHelper() { private BookFileHelper() {
} }
/** /**
@@ -32,7 +32,7 @@ public final class FileHelper {
* @param possibleIndex <p>The string which might be a book index</p> * @param possibleIndex <p>The string which might be a book index</p>
* @return <p>True if the number is a book index</p> * @return <p>True if the number is a book index</p>
*/ */
public static boolean isBookListIndex(String possibleIndex) { public static boolean isBookListIndex(@NotNull String possibleIndex) {
File bookDirectory = new File(getBookFolder().replaceAll("[\\\\/]$", "")); File bookDirectory = new File(getBookFolder().replaceAll("[\\\\/]$", ""));
try { try {
@@ -53,7 +53,7 @@ public final class FileHelper {
* @param bookFile <p>The path to a book</p> * @param bookFile <p>The path to a book</p>
* @return <p>True if the file exists and points to a book file</p> * @return <p>True if the file exists and points to a book file</p>
*/ */
public static boolean bookFileExists(String bookFile) { public static boolean bookFileExists(@NotNull String bookFile) {
return ((new File(bookFile).isFile() && (bookFile.endsWith(".txt") || return ((new File(bookFile).isFile() && (bookFile.endsWith(".txt") ||
bookFile.endsWith(".yml"))) || new File(bookFile + ".txt").isFile() || bookFile.endsWith(".yml"))) || new File(bookFile + ".txt").isFile() ||
new File(bookFile + ".yml").isFile()) && !bookFile.contains("../") && !bookFile.contains("..\\"); new File(bookFile + ".yml").isFile()) && !bookFile.contains("../") && !bookFile.contains("..\\");
@@ -67,7 +67,8 @@ public final class FileHelper {
* @param bookPath <p>The path of the book to get</p> * @param bookPath <p>The path of the book to get</p>
* @return <p>The file the path points to, or null otherwise</p> * @return <p>The file the path points to, or null otherwise</p>
*/ */
public static File getBookFile(String bookPath) { @Nullable
public static File getBookFile(@NotNull String bookPath) {
if (!bookFileExists(bookPath)) { if (!bookFileExists(bookPath)) {
return null; return null;
} }
@@ -97,42 +98,33 @@ public final class FileHelper {
* @param listPublic <p>Whether to list public or personal files</p> * @param listPublic <p>Whether to list public or personal files</p>
* @return <p>A list of available files</p> * @return <p>A list of available files</p>
*/ */
public static List<String> listFiles(CommandSender sender, Boolean listPublic) { @Nullable
File file; public static List<String> listFiles(@NotNull CommandSender sender, @NotNull Boolean listPublic) {
if (listPublic) { File file = BookHelper.getBookDirectoryPath(listPublic ? BookDirectory.PUBLIC : BookDirectory.PLAYER, sender);
file = new File(getBookFolder()); if (file == null) {
} else { return new ArrayList<>();
file = new File(getBookFolder() + cleanString(sender.getName()) + getSlash());
} }
return FileHelper.listFiles(sender, file); return BookFileHelper.listFiles(sender, file);
} }
/** /**
* Prints the available books * Gets a map between characters, and the first instance of a book's title starting with that character
* *
* @param sender <p>The sender to display the books to</p> * @param books <p>The books to look through</p>
* @param listPublic <p>Whether to display public books</p> * @return <p>The map of the first index containing each character</p>
*/ */
public static void printBooks(CommandSender sender, boolean listPublic) { @NotNull
List<String> availableBooks = BooksWithoutBorders.getAvailableBooks(sender, listPublic); public static Map<Character, Integer> populateLetterIndices(@NotNull List<String> books) {
FileHelper.printFiles(sender, availableBooks); Map<Character, Integer> firstEncounter = new HashMap<>();
} Character current = null;
for (int i = 0; i < books.size(); i++) {
/** char first = BookFormatter.stripColor(books.get(i)).toLowerCase().charAt(0);
* Prints a list of files if (current == null || current != first) {
* current = first;
* @param sender <p>The command sender to show the list to</p> firstEncounter.put(first, i);
* @param fileList <p>The files to list</p> }
*/
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));
} }
return firstEncounter;
} }
/** /**
@@ -142,7 +134,8 @@ public final class FileHelper {
* @param searchDirectory <p>The directory to search for files</p> * @param searchDirectory <p>The directory to search for files</p>
* @return <p>A list of available files</p> * @return <p>A list of available files</p>
*/ */
private static List<String> listFiles(CommandSender sender, File searchDirectory) { @Nullable
private static List<String> listFiles(@NotNull CommandSender sender, @NotNull File searchDirectory) {
List<String> fileList = new ArrayList<>(); List<String> fileList = new ArrayList<>();
File[] existingFiles = searchDirectory.listFiles(); File[] existingFiles = searchDirectory.listFiles();
@@ -152,11 +145,28 @@ public final class FileHelper {
} }
for (File foundFile : existingFiles) { for (File foundFile : existingFiles) {
if (foundFile.isFile()) { // Filter out invalid files
fileList.add(foundFile.getName()); if (!foundFile.isFile() || foundFile.getName().contains(" ") || foundFile.getName().contains("§")) {
continue;
}
String fileName = foundFile.getName();
String separator = BooksWithoutBordersConfig.getTitleAuthorSeparator();
if (fileName.contains(separator)) {
//Convert the UUID into a username if necessary
String[] data = fileName.split(separator);
String extension = data[1].substring(data[1].length() - 4);
String userName = data[1].substring(0, data[1].length() - 4);
data[1] = BookHelper.authorFromUUID(userName) + extension;
fileList.add(String.join(separator, data));
} else {
fileList.add(fileName);
} }
} }
// Sort the book list
Comparator<String> bookComparator = Comparator.naturalOrder();
fileList.sort((a, b) -> bookComparator.compare(BookFormatter.stripColor(a).toLowerCase(),
BookFormatter.stripColor(b).toLowerCase()));
return fileList; return fileList;
} }
@@ -167,7 +177,7 @@ public final class FileHelper {
* @param fileName <p>The name of the file which might already exist</p> * @param fileName <p>The name of the file which might already exist</p>
* @return <p>The number of found duplicates</p> * @return <p>The number of found duplicates</p>
*/ */
public static int findDuplicates(File[] foundFiles, String fileName) { public static int findDuplicates(@NotNull File[] foundFiles, @NotNull String fileName) {
int foundDuplicates = 0; int foundDuplicates = 0;
for (File foundFile : foundFiles) { for (File foundFile : foundFiles) {
if (foundFile.getName().matches("(\\([0-9]+\\))?" + Pattern.quote(fileName) + "(\\.yml|\\.txt)?")) { if (foundFile.getName().matches("(\\([0-9]+\\))?" + Pattern.quote(fileName) + "(\\.yml|\\.txt)?")) {
@@ -177,15 +187,4 @@ public final class FileHelper {
return foundDuplicates; return foundDuplicates;
} }
/**
* Gets a buffered reader given an input stream
*
* @param inputStream <p>The input stream to read</p>
* @return <p>A buffered reader reading the input stream</p>
*/
public static BufferedReader getBufferedReaderFromInputStream(InputStream inputStream) {
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
return new BufferedReader(inputStreamReader);
}
} }

View File

@@ -1,13 +1,13 @@
package net.knarcraft.bookswithoutborders.utility; package net.knarcraft.bookswithoutborders.utility;
import net.md_5.bungee.api.ChatColor; import net.knarcraft.knarlib.property.ColorConversion;
import net.knarcraft.knarlib.util.ColorHelper;
import org.bukkit.inventory.meta.BookMeta; import org.bukkit.inventory.meta.BookMeta;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/** /**
* A class for formatting text to fit books * A class for formatting text to fit books
@@ -25,7 +25,7 @@ public final class BookFormatter {
* *
* @param rawPages <p>A list of pages</p> * @param rawPages <p>A list of pages</p>
*/ */
public static void formatLastPage(List<String> rawPages) { public static void formatLastPage(@NotNull List<String> rawPages) {
int maxPageText = 256; int maxPageText = 256;
int fitsNewline = maxPageText - 2; int fitsNewline = maxPageText - 2;
@@ -50,7 +50,7 @@ public final class BookFormatter {
* @param maxPageText <p>The max number of characters which fit on a page</p> * @param maxPageText <p>The max number of characters which fit on a page</p>
* @param fitsNewline <p>The max number of characters on a page which still fits a newline character</p> * @param fitsNewline <p>The max number of characters on a page which still fits a newline character</p>
*/ */
public static void formatLastPageSplitOverflow(List<String> rawPages, int maxPageText, int fitsNewline) { public static void formatLastPageSplitOverflow(@NotNull List<String> rawPages, int maxPageText, int fitsNewline) {
while (rawPages.get(rawPages.size() - 1).length() > maxPageText) { while (rawPages.get(rawPages.size() - 1).length() > maxPageText) {
int splitPosition; int splitPosition;
String fittingText = rawPages.get(rawPages.size() - 1).substring(0, maxPageText); String fittingText = rawPages.get(rawPages.size() - 1).substring(0, maxPageText);
@@ -78,7 +78,7 @@ public final class BookFormatter {
* @param rawPages <p>The raw pages to format</p> * @param rawPages <p>The raw pages to format</p>
* @param maxPageText <p>The max number of characters which fit on a page</p> * @param maxPageText <p>The max number of characters which fit on a page</p>
*/ */
public static void formatLastPageCombinePages(List<String> rawPages, int maxPageText) { public static void formatLastPageCombinePages(@NotNull List<String> rawPages, int maxPageText) {
int lastPageIndex = rawPages.size() - 1; int lastPageIndex = rawPages.size() - 1;
int nextToLastIndex = rawPages.size() - 2; int nextToLastIndex = rawPages.size() - 2;
if (rawPages.get(nextToLastIndex).length() + rawPages.get(lastPageIndex).length() <= maxPageText) { if (rawPages.get(nextToLastIndex).length() + rawPages.get(lastPageIndex).length() <= maxPageText) {
@@ -93,7 +93,7 @@ public final class BookFormatter {
* @param rawPages <p>The raw pages to format</p> * @param rawPages <p>The raw pages to format</p>
* @param fitsNewline <p>The max number of characters on a page which still fits a newline character</p> * @param fitsNewline <p>The max number of characters on a page which still fits a newline character</p>
*/ */
public static void formatLastPageAddNewline(List<String> rawPages, int fitsNewline) { public static void formatLastPageAddNewline(@NotNull List<String> rawPages, int fitsNewline) {
int pageIndex = rawPages.size() - 1; int pageIndex = rawPages.size() - 1;
if (rawPages.get(pageIndex).length() <= fitsNewline && !rawPages.get(pageIndex).isEmpty()) { if (rawPages.get(pageIndex).length() <= fitsNewline && !rawPages.get(pageIndex).isEmpty()) {
rawPages.set(pageIndex, (rawPages.get(pageIndex)) + "\n"); rawPages.set(pageIndex, (rawPages.get(pageIndex)) + "\n");
@@ -106,29 +106,25 @@ public final class BookFormatter {
* @param bookMeta <p>The book meta to change</p> * @param bookMeta <p>The book meta to change</p>
* @return <p>The changed book meta</p> * @return <p>The changed book meta</p>
*/ */
public static BookMeta formatPages(BookMeta bookMeta) { @NotNull
public static BookMeta formatPages(@NotNull BookMeta bookMeta) {
List<String> formattedPages = new ArrayList<>(Objects.requireNonNull(bookMeta).getPageCount()); List<String> formattedPages = new ArrayList<>(Objects.requireNonNull(bookMeta).getPageCount());
for (String page : bookMeta.getPages()) { for (String page : bookMeta.getPages()) {
formattedPages.add(BookFormatter.translateAllColorCodes(page)); formattedPages.add(ColorHelper.translateColorCodes(page, ColorConversion.RGB));
} }
bookMeta.setPages(formattedPages); bookMeta.setPages(formattedPages);
return bookMeta; return bookMeta;
} }
/** /**
* Translates all found color codes to formatting in a string * Strips the color from the given input
* *
* @param message <p>The string to search for color codes</p> * @param input <p>The input to strip</p>
* @return <p>The message with color codes translated</p> * @return <p>The color stripped input</p>
*/ */
private static String translateAllColorCodes(String message) { @NotNull
message = ChatColor.translateAlternateColorCodes('&', message); public static String stripColor(@NotNull String input) {
Pattern pattern = Pattern.compile("(#[a-fA-F0-9]{6})"); return ColorHelper.stripColorCodes(input, ColorConversion.RGB);
Matcher matcher = pattern.matcher(message);
while (matcher.find()) {
message = message.replace(matcher.group(), "" + ChatColor.of(matcher.group()));
}
return message;
} }
} }

View File

@@ -0,0 +1,189 @@
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 org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
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>
*/
@NotNull
public static String authorFromUUID(@NotNull 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>
*/
@Nullable
public static File getBookDirectoryPath(@NotNull BookDirectory bookDirectory, @NotNull 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>
*/
@Nullable
public static String getBookDirectoryPathString(@NotNull BookDirectory bookDirectory, @NotNull 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(@NotNull 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>
*/
@NotNull
public static BookMeta.Generation getNextGeneration(@Nullable 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>
* @throws IllegalArgumentException <p>If the book title or author contains the title author separator</p>
*/
@NotNull
public static String getBookFile(@NotNull BookMeta book, @NotNull Player player, boolean isPublic) throws IllegalArgumentException {
String titleAuthorSeparator = BooksWithoutBordersConfig.getTitleAuthorSeparator();
String bookName;
if (book.hasTitle()) {
bookName = book.getTitle();
if (bookName == null) {
bookName = "Untitled";
}
} 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();
if (authorName == null) {
authorName = "Unknown";
}
}
if (bookName.contains(titleAuthorSeparator) || authorName.contains(titleAuthorSeparator)) {
throw new IllegalArgumentException("The author or title contains the title author separator. Saving this " +
"book would lead to unexpected problems.");
}
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(@NotNull Player player, @NotNull 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(@NotNull String playerName, @Nullable String author) {
playerName = InputCleaningHelper.cleanString(playerName);
return author != null && playerName.equalsIgnoreCase(InputCleaningHelper.cleanString(author));
}
}

View File

@@ -3,11 +3,14 @@ package net.knarcraft.bookswithoutborders.utility;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders; import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig; import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
import net.knarcraft.bookswithoutborders.state.BookDirectory; import net.knarcraft.bookswithoutborders.state.BookDirectory;
import org.bukkit.Bukkit;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.BookMeta; import org.bukkit.inventory.meta.BookMeta;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
@@ -30,7 +33,9 @@ public final class BookLoader {
* @param directory <p>The directory to save the book in</p> * @param directory <p>The directory to save the book in</p>
* @return <p>The loaded book</p> * @return <p>The loaded book</p>
*/ */
public static ItemStack loadBook(CommandSender sender, String fileName, String isSigned, String directory) { @Nullable
public static ItemStack loadBook(@NotNull CommandSender sender, @NotNull String fileName, @NotNull String isSigned,
@NotNull String directory) {
return loadBook(sender, fileName, isSigned, directory, 1); return loadBook(sender, fileName, isSigned, directory, 1);
} }
@@ -44,8 +49,14 @@ public final class BookLoader {
* @param numCopies <p>The number of copies to load</p> * @param numCopies <p>The number of copies to load</p>
* @return <p>The loaded book</p> * @return <p>The loaded book</p>
*/ */
public static ItemStack loadBook(CommandSender sender, String fileName, String isSigned, String directory, int numCopies) { @Nullable
public static ItemStack loadBook(@NotNull CommandSender sender, @NotNull String fileName, @NotNull String isSigned,
@NotNull String directory, int numCopies) {
BookDirectory bookDirectory = BookDirectory.getFromString(directory); BookDirectory bookDirectory = BookDirectory.getFromString(directory);
if (bookDirectory == null) {
BooksWithoutBorders.sendErrorMessage(sender, "Unrecognized book directory!");
return null;
}
//Find the filename if a book index is given //Find the filename if a book index is given
try { try {
@@ -60,11 +71,27 @@ public final class BookLoader {
//Get the full path of the book to load //Get the full path of the book to load
File file = getFullPath(sender, fileName, bookDirectory, directory); File file = getFullPath(sender, fileName, bookDirectory, directory);
if (file == null) { if (file == null) {
return 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 //Make sure the player can pay for the book
if (BooksWithoutBordersConfig.booksHavePrice() && !sender.hasPermission("bookswithoutborders.bypassBookPrice") && if (BooksWithoutBordersConfig.booksHavePrice() &&
!sender.hasPermission("bookswithoutborders.bypassBookPrice") &&
(bookDirectory == BookDirectory.PUBLIC || bookDirectory == BookDirectory.PLAYER) && (bookDirectory == BookDirectory.PUBLIC || bookDirectory == BookDirectory.PLAYER) &&
EconomyHelper.cannotPayForBookPrinting((Player) sender, numCopies)) { EconomyHelper.cannotPayForBookPrinting((Player) sender, numCopies)) {
return null; return null;
@@ -79,8 +106,13 @@ public final class BookLoader {
book = new ItemStack(Material.WRITABLE_BOOK); book = new ItemStack(Material.WRITABLE_BOOK);
} }
if (bookMetadata == null) {
BooksWithoutBorders.sendErrorMessage(sender, "Unable to create blank book metadata!");
return null;
}
//Load the book from the given file //Load the book from the given file
BookToFromTextHelper.bookFromFile(file, bookMetadata); bookMetadata = BookToFromTextHelper.bookFromFile(file, bookMetadata);
if (bookMetadata == null) { if (bookMetadata == null) {
BooksWithoutBorders.sendErrorMessage(sender, "File was blank!!"); BooksWithoutBorders.sendErrorMessage(sender, "File was blank!!");
return null; return null;
@@ -98,6 +130,8 @@ public final class BookLoader {
//Set the metadata and amount to the new book //Set the metadata and amount to the new book
book.setItemMeta(bookMetadata); book.setItemMeta(bookMetadata);
//Increase book generation if enabled
BookHelper.increaseGeneration(book);
book.setAmount(numCopies); book.setAmount(numCopies);
return book; return book;
@@ -112,19 +146,18 @@ public final class BookLoader {
* @param directory <p>The relative directory given</p> * @param directory <p>The relative directory given</p>
* @return <p>A file or null if it does not exist</p> * @return <p>A file or null if it does not exist</p>
*/ */
private static File getFullPath(CommandSender sender, String fileName, BookDirectory bookDirectory, String directory) { @Nullable
File file = null; private static File getFullPath(@NotNull CommandSender sender, @NotNull String fileName,
@NotNull BookDirectory bookDirectory, @NotNull String directory) {
File file;
String slash = BooksWithoutBordersConfig.getSlash(); String slash = BooksWithoutBordersConfig.getSlash();
String bookFolder = BooksWithoutBordersConfig.getBookFolder(); String bookFolder = BooksWithoutBordersConfig.getBookFolder();
if (bookDirectory == BookDirectory.PUBLIC) { if (bookDirectory == BookDirectory.ENCRYPTED) {
file = FileHelper.getBookFile(bookFolder + fileName); file = BookFileHelper.getBookFile(bookFolder + "Encrypted" + slash + directory + slash + fileName);
} else if (bookDirectory == BookDirectory.PLAYER) { } else {
file = FileHelper.getBookFile(bookFolder + InputCleaningHelper.cleanString(sender.getName()) + slash + fileName); file = BookFileHelper.getBookFile(BookHelper.getBookDirectoryPathString(bookDirectory, sender) + fileName);
} else if (bookDirectory == BookDirectory.ENCRYPTED) {
file = FileHelper.getBookFile(bookFolder + "Encrypted" + slash + directory + slash + fileName);
} }
if (file == null || !file.isFile()) { if (file == null || !file.isFile()) {
BooksWithoutBorders.sendErrorMessage(sender, "Incorrect file name!");
return null; return null;
} else { } else {
return file; return file;

View File

@@ -1,19 +1,26 @@
package net.knarcraft.bookswithoutborders.utility; package net.knarcraft.bookswithoutborders.utility;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig; import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
import net.knarcraft.knarlib.util.FileHelper;
import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.inventory.meta.BookMeta; import org.bukkit.inventory.meta.BookMeta;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.File; import java.io.File;
import java.io.FileReader; import java.io.FileInputStream;
import java.io.FileWriter; import java.io.FileWriter;
import java.io.IOException; import java.io.IOException;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.logging.Level;
import static net.knarcraft.bookswithoutborders.utility.BookHelper.authorFromUUID;
import static net.knarcraft.bookswithoutborders.utility.InputCleaningHelper.fixName; import static net.knarcraft.bookswithoutborders.utility.InputCleaningHelper.fixName;
/** /**
@@ -32,7 +39,7 @@ public final class BookToFromTextHelper {
* @param bookMetadata <p>Metadata about the book to save</p> * @param bookMetadata <p>Metadata about the book to save</p>
* @throws IOException <p>If unable to save the book</p> * @throws IOException <p>If unable to save the book</p>
*/ */
public static void bookToYml(String path, String fileName, BookMeta bookMetadata) throws IOException { public static void bookToYml(@NotNull String path, @NotNull String fileName, @NotNull BookMeta bookMetadata) throws IOException {
FileConfiguration bookYml = YamlConfiguration.loadConfiguration(new File(path, "blank")); FileConfiguration bookYml = YamlConfiguration.loadConfiguration(new File(path, "blank"));
if (bookMetadata.hasTitle()) { if (bookMetadata.hasTitle()) {
@@ -41,6 +48,11 @@ public final class BookToFromTextHelper {
if (bookMetadata.hasAuthor()) { if (bookMetadata.hasAuthor()) {
bookYml.set("Author", bookMetadata.getAuthor()); bookYml.set("Author", bookMetadata.getAuthor());
} }
BookMeta.Generation generation = bookMetadata.getGeneration();
if (generation == null) {
generation = BookMeta.Generation.ORIGINAL;
}
bookYml.set("Generation", generation.name());
if (bookMetadata.hasPages()) { if (bookMetadata.hasPages()) {
bookYml.set("Pages", bookMetadata.getPages()); bookYml.set("Pages", bookMetadata.getPages());
} }
@@ -48,7 +60,7 @@ public final class BookToFromTextHelper {
bookYml.set("Lore", bookMetadata.getLore()); bookYml.set("Lore", bookMetadata.getLore());
} }
bookYml.save(path + fileName + ".yml"); bookYml.save(path + InputCleaningHelper.cleanString(fileName) + ".yml");
} }
/** /**
@@ -58,7 +70,8 @@ public final class BookToFromTextHelper {
* @param bookMetadata <p>The book metadata to use for saving the book</p> * @param bookMetadata <p>The book metadata to use for saving the book</p>
* @return <p>The book metadata of the loaded book</p> * @return <p>The book metadata of the loaded book</p>
*/ */
public static BookMeta bookFromFile(File file, BookMeta bookMetadata) { @Nullable
public static BookMeta bookFromFile(@NotNull File file, @NotNull BookMeta bookMetadata) {
if (file.getName().endsWith(".txt")) { if (file.getName().endsWith(".txt")) {
return bookFromTXT(file.getName(), file, bookMetadata); return bookFromTXT(file.getName(), file, bookMetadata);
} else if (file.getName().endsWith(".yml")) { } else if (file.getName().endsWith(".yml")) {
@@ -76,13 +89,19 @@ public final class BookToFromTextHelper {
* @param bookMetadata <p>Metadata about the book to save</p> * @param bookMetadata <p>Metadata about the book to save</p>
* @throws IOException <p>If unable to save the book</p> * @throws IOException <p>If unable to save the book</p>
*/ */
public static void bookToTXT(String folderPath, String fileName, BookMeta bookMetadata) throws IOException { public static void bookToTXT(@NotNull String folderPath, @NotNull String fileName, @NotNull BookMeta bookMetadata) throws IOException {
FileWriter fileWriter = new FileWriter(folderPath + fileName + ".txt"); FileWriter fileWriter = new FileWriter(folderPath + InputCleaningHelper.cleanString(fileName) + ".txt", StandardCharsets.UTF_8);
PrintWriter printWriter = new PrintWriter(fileWriter); PrintWriter printWriter = new PrintWriter(fileWriter);
List<String> pages = bookMetadata.getPages(); List<String> pages = bookMetadata.getPages();
BookMeta.Generation generation = bookMetadata.getGeneration();
if (generation == null) {
generation = BookMeta.Generation.ORIGINAL;
}
String generationString = ":" + generation.name();
//Save each page of the book as a text line //Save each page of the book as a text line
printWriter.println("[Book]"); printWriter.println("[Book]" + generationString);
for (String page : pages) { for (String page : pages) {
printWriter.println(page); printWriter.println(page);
} }
@@ -96,12 +115,14 @@ public final class BookToFromTextHelper {
* @param bookMetadata <p>Metadata which will be altered with the book's contents</p> * @param bookMetadata <p>Metadata which will be altered with the book's contents</p>
* @return <p>Metadata for the loaded book</p> * @return <p>Metadata for the loaded book</p>
*/ */
private static BookMeta bookFromYml(File file, BookMeta bookMetadata) { @Nullable
private static BookMeta bookFromYml(@NotNull File file, @NotNull BookMeta bookMetadata) {
try { try {
FileConfiguration bookYml = YamlConfiguration.loadConfiguration(file); FileConfiguration bookYml = YamlConfiguration.loadConfiguration(file);
bookMetadata.setGeneration(BookMeta.Generation.valueOf(bookYml.getString("Generation", "ORIGINAL")));
bookMetadata.setTitle(bookYml.getString("Title", "Untitled")); bookMetadata.setTitle(bookYml.getString("Title", "Untitled"));
bookMetadata.setAuthor(bookYml.getString("Author", "Unknown")); bookMetadata.setAuthor(authorFromUUID(bookYml.getString("Author", "Unknown")));
bookMetadata.setPages(bookYml.getStringList("Pages")); bookMetadata.setPages(bookYml.getStringList("Pages"));
bookMetadata.setLore(bookYml.getStringList("Lore")); bookMetadata.setLore(bookYml.getStringList("Lore"));
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
@@ -118,18 +139,22 @@ public final class BookToFromTextHelper {
* @param bookMetadata <p>Metadata which will be altered with the book's contents</p> * @param bookMetadata <p>Metadata which will be altered with the book's contents</p>
* @return <p>Metadata for the loaded book</p> * @return <p>Metadata for the loaded book</p>
*/ */
private static BookMeta bookFromTXT(String fileName, File file, BookMeta bookMetadata) { @Nullable
private static BookMeta bookFromTXT(@NotNull String fileName, @NotNull File file, @NotNull BookMeta bookMetadata) {
String author; String author;
String title; String title;
String titleAuthorSeparator = BooksWithoutBordersConfig.getTitleAuthorSeparator(); String titleAuthorSeparator = BooksWithoutBordersConfig.getTitleAuthorSeparator();
//Remove .txt extension
fileName = fileName.substring(0, fileName.length() - 4);
//Get title and author from the file name //Get title and author from the file name
if (fileName.contains(titleAuthorSeparator)) { if (fileName.contains(titleAuthorSeparator)) {
author = fileName.substring(fileName.indexOf(titleAuthorSeparator) + 1, fileName.length() - 4); String[] titleAuthor = fileName.split(titleAuthorSeparator);
title = fileName.substring(0, fileName.indexOf(titleAuthorSeparator)); title = titleAuthor[0];
author = titleAuthor[1];
} else { } else {
author = "Unknown"; author = "Unknown";
title = fileName.substring(0, fileName.length() - 4); title = fileName;
} }
//Replace underscores with spaces //Replace underscores with spaces
@@ -139,16 +164,26 @@ public final class BookToFromTextHelper {
List<String> rawPages; List<String> rawPages;
try { try {
rawPages = readTextFile(file); rawPages = readTextFile(file);
} catch (IOException e) { if (rawPages == null) {
e.printStackTrace(); BooksWithoutBorders.getInstance().getLogger().log(Level.SEVERE, "Text file's first line was null");
return null;
}
} catch (IOException exception) {
BooksWithoutBorders.getInstance().getLogger().log(Level.SEVERE, "Unable to read text file");
return null; return null;
} }
//Parse the generation from the book data
if (!rawPages.isEmpty() && rawPages.get(0).startsWith("Generation:")) {
bookMetadata.setGeneration(BookMeta.Generation.valueOf(rawPages.get(0).split(":")[1]));
rawPages.remove(0);
}
//Remove any empty pages //Remove any empty pages
List<String> pages = new ArrayList<>(InputCleaningHelper.cleanList(rawPages)); List<String> pages = new ArrayList<>(InputCleaningHelper.cleanList(rawPages));
//Update the metadata of the book with its new values //Update the metadata of the book with its new values
bookMetadata.setAuthor(author); bookMetadata.setAuthor(authorFromUUID(author));
bookMetadata.setTitle(title); bookMetadata.setTitle(title);
bookMetadata.setPages(pages); bookMetadata.setPages(pages);
@@ -162,9 +197,10 @@ public final class BookToFromTextHelper {
* @return <p>A string list where each string is the text on one page</p> * @return <p>A string list where each string is the text on one page</p>
* @throws IOException <p>If unable to read from the file</p> * @throws IOException <p>If unable to read from the file</p>
*/ */
private static List<String> readTextFile(File file) throws IOException { @Nullable
private static List<String> readTextFile(@NotNull File file) throws IOException {
List<String> rawPages = new ArrayList<>(); 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 //Use the first line to decide if we are loading a book, or creating a new book
String firstLine = bufferedReader.readLine(); String firstLine = bufferedReader.readLine();
@@ -172,8 +208,11 @@ public final class BookToFromTextHelper {
bufferedReader.close(); bufferedReader.close();
return null; return null;
} }
if (firstLine.equalsIgnoreCase("[Book]")) { if (firstLine.toLowerCase().startsWith("[book]")) {
//Read every line directly as a page, as this is a saved book //Read every line directly as a page, as this is a saved book
if (firstLine.contains(":")) {
rawPages.add("Generation:" + firstLine.split(":")[1]);
}
String readLine; String readLine;
do { do {
readLine = bufferedReader.readLine(); readLine = bufferedReader.readLine();

View File

@@ -8,10 +8,14 @@ import org.bukkit.Server;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory; import org.bukkit.inventory.PlayerInventory;
import org.bukkit.inventory.meta.BookMeta;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.RegisteredServiceProvider; import org.bukkit.plugin.RegisteredServiceProvider;
import org.bukkit.plugin.ServicesManager; import org.bukkit.plugin.ServicesManager;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects; import java.util.Objects;
/** /**
@@ -29,6 +33,7 @@ public final class EconomyHelper {
* *
* @return <p>An economy instance, or null if it's not initialized</p> * @return <p>An economy instance, or null if it's not initialized</p>
*/ */
@NotNull
public static Economy getEconomy() { public static Economy getEconomy() {
return economy; return economy;
} }
@@ -64,7 +69,7 @@ public final class EconomyHelper {
* @param numCopies <p>The number of copies the player is trying to print</p> * @param numCopies <p>The number of copies the player is trying to print</p>
* @return <p>True if the player cannot pay for the printing of the books</p> * @return <p>True if the player cannot pay for the printing of the books</p>
*/ */
public static boolean cannotPayForBookPrinting(Player player, int numCopies) { public static boolean cannotPayForBookPrinting(@NotNull Player player, int numCopies) {
//BookPriceQuantity: How many items are required to pay for each book //BookPriceQuantity: How many items are required to pay for each book
//BookPriceType: Which item is used to pay for the books. AIR = use economy //BookPriceType: Which item is used to pay for the books. AIR = use economy
Material bookCurrency = BooksWithoutBordersConfig.getBookPriceType(); Material bookCurrency = BooksWithoutBordersConfig.getBookPriceType();
@@ -74,17 +79,90 @@ public final class EconomyHelper {
if (bookCurrency == Material.AIR) { if (bookCurrency == Material.AIR) {
return !EconomyHelper.payForBookPrintingEconomy(player, cost, numCopies); return !EconomyHelper.payForBookPrintingEconomy(player, cost, numCopies);
} else { } else {
if (player.getInventory().contains(bookCurrency, itemCost)) { if (bookCurrency == Material.WRITABLE_BOOK) {
payForBookPrintingItem(player, itemCost); //Writable books are treated as a special case to prevent WIP books from being used
return false; return !takeWritableBookPayment(player, itemCost);
} else { } else {
BooksWithoutBorders.sendErrorMessage(player, itemCost + " " + bookCurrency + if (player.getInventory().contains(bookCurrency, itemCost)) {
"(s) are required for this command!"); payForBookPrintingItem(player, itemCost);
return true; 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(@NotNull 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(@NotNull 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>
*/
@NotNull
private static List<ItemStack> getPlayersEmptyBooks(@NotNull 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 * Uses economy to take payment for printing a number of books
* *
@@ -93,11 +171,13 @@ public final class EconomyHelper {
* @param numCopies <p>The number of books the player is printing</p> * @param numCopies <p>The number of books the player is printing</p>
* @return <p>True if the player had the money and it has been withdrawn</p> * @return <p>True if the player had the money and it has been withdrawn</p>
*/ */
private static boolean payForBookPrintingEconomy(Player player, double cost, int numCopies) { private static boolean payForBookPrintingEconomy(@NotNull Player player, double cost, int numCopies) {
if ((economy.getBalance(player) - cost) >= 0) { if ((economy.getBalance(player) - cost) >= 0) {
economy.withdrawPlayer(player, cost); economy.withdrawPlayer(player, cost);
BooksWithoutBorders.sendSuccessMessage(player, economy.format(cost) + " withdrawn to create " + numCopies + " book(s)"); BooksWithoutBorders.sendSuccessMessage(player, economy.format(cost) + " withdrawn to create " +
BooksWithoutBorders.sendSuccessMessage(player, "New balance: " + economy.format(economy.getBalance(player))); numCopies + " book(s)");
BooksWithoutBorders.sendSuccessMessage(player, "New balance: " +
economy.format(economy.getBalance(player)));
return true; return true;
} else { } else {
BooksWithoutBorders.sendErrorMessage(player, economy.format(cost) + " is required for this command!"); BooksWithoutBorders.sendErrorMessage(player, economy.format(cost) + " is required for this command!");
@@ -111,7 +191,7 @@ public final class EconomyHelper {
* @param player <p>The player which needs to pay</p> * @param player <p>The player which needs to pay</p>
* @param itemCost <p>The number of items to pay</p> * @param itemCost <p>The number of items to pay</p>
*/ */
private static void payForBookPrintingItem(Player player, int itemCost) { private static void payForBookPrintingItem(@NotNull Player player, int itemCost) {
PlayerInventory playerInventory = player.getInventory(); PlayerInventory playerInventory = player.getInventory();
int clearedAmount = 0; int clearedAmount = 0;

View File

@@ -5,17 +5,20 @@ import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
import net.knarcraft.bookswithoutborders.encryption.GenenCrypt; import net.knarcraft.bookswithoutborders.encryption.GenenCrypt;
import net.knarcraft.bookswithoutborders.encryption.SubstitutionCipher; import net.knarcraft.bookswithoutborders.encryption.SubstitutionCipher;
import net.knarcraft.bookswithoutborders.state.EncryptionStyle; import net.knarcraft.bookswithoutborders.state.EncryptionStyle;
import org.bukkit.ChatColor; import net.md_5.bungee.api.ChatColor;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.command.ConsoleCommandSender; import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.BookMeta; import org.bukkit.inventory.meta.BookMeta;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.logging.Level;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getBookFolder; import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getBookFolder;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getSlash; import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getSlash;
@@ -36,7 +39,8 @@ public final class EncryptionHelper {
* @param key <p>The key to transform</p> * @param key <p>The key to transform</p>
* @return <p>The numbers representing the key's characters</p> * @return <p>The numbers representing the key's characters</p>
*/ */
public static String getNumberKeyFromStringKey(String key) { @NotNull
public static String getNumberKeyFromStringKey(@NotNull String key) {
StringBuilder integerKey = new StringBuilder(); StringBuilder integerKey = new StringBuilder();
for (int x = 0; x < key.length(); x++) { for (int x = 0; x < key.length(); x++) {
integerKey.append(Character.getNumericValue(Character.codePointAt(key, x))); integerKey.append(Character.getNumericValue(Character.codePointAt(key, x)));
@@ -53,7 +57,9 @@ public final class EncryptionHelper {
* @param player <p>The player trying to encrypt a book</p> * @param player <p>The player trying to encrypt a book</p>
* @return <p>The pages of the book in encrypted form</p> * @return <p>The pages of the book in encrypted form</p>
*/ */
public static List<String> encryptBookPages(BookMeta book, EncryptionStyle style, String integerKey, Player player) { @Nullable
public static List<String> encryptBookPages(@NotNull BookMeta book, @NotNull EncryptionStyle style,
@NotNull String integerKey, @NotNull Player player) {
List<String> encryptedPages = new ArrayList<>(); List<String> encryptedPages = new ArrayList<>();
//Scramble the book's contents //Scramble the book's contents
if (style == EncryptionStyle.DNA) { if (style == EncryptionStyle.DNA) {
@@ -85,7 +91,9 @@ public final class EncryptionHelper {
* @param style <p>The encryption style to use</p> * @param style <p>The encryption style to use</p>
* @return <p>An encrypted version of the book</p> * @return <p>An encrypted version of the book</p>
*/ */
public static ItemStack encryptBook(Player player, boolean mainHand, String key, EncryptionStyle style) { @Nullable
public static ItemStack encryptBook(@NotNull Player player, boolean mainHand, @NotNull String key,
@NotNull EncryptionStyle style) {
return encryptBook(player, mainHand, key, style, ""); return encryptBook(player, mainHand, key, style, "");
} }
@@ -99,11 +107,17 @@ public final class EncryptionHelper {
* @param groupName <p>The name of the group to encrypt for, or "" otherwise</p> * @param groupName <p>The name of the group to encrypt for, or "" otherwise</p>
* @return <p>An encrypted version of the book</p> * @return <p>An encrypted version of the book</p>
*/ */
public static ItemStack encryptBook(Player player, boolean mainHand, String key, EncryptionStyle style, String groupName) { @Nullable
public static ItemStack encryptBook(Player player, boolean mainHand, @NotNull String key,
@NotNull EncryptionStyle style, @NotNull String groupName) {
//converts user supplied key into integer form //converts user supplied key into integer form
String integerKey = EncryptionHelper.getNumberKeyFromStringKey(key); String integerKey = EncryptionHelper.getNumberKeyFromStringKey(key);
BookMeta book = InventoryHelper.getHeldBookMetadata(player, mainHand); BookMeta book = InventoryHelper.getHeldBookMetadata(player, mainHand);
if (book == null) {
BooksWithoutBorders.sendErrorMessage(player, "Unable to get metadata from the held book!");
return null;
}
if (!book.hasPages()) { if (!book.hasPages()) {
BooksWithoutBorders.sendErrorMessage(player, "Book is empty!"); BooksWithoutBorders.sendErrorMessage(player, "Book is empty!");
@@ -142,7 +156,9 @@ public final class EncryptionHelper {
* @param newMetadata <p>The new metadata of the book</p> * @param newMetadata <p>The new metadata of the book</p>
* @return <p>An encrypted version of the book</p> * @return <p>An encrypted version of the book</p>
*/ */
private static ItemStack createEncryptedBook(BookMeta book, List<String> newPages, Player player, BookMeta newMetadata) { @NotNull
private static ItemStack createEncryptedBook(@NotNull BookMeta book, @NotNull List<String> newPages,
@NotNull Player player, @NotNull BookMeta newMetadata) {
//Create the encrypted book //Create the encrypted book
ItemStack encryptedBook = new ItemStack(Material.WRITTEN_BOOK); ItemStack encryptedBook = new ItemStack(Material.WRITTEN_BOOK);
book.setPages(newPages); book.setPages(newPages);
@@ -165,7 +181,9 @@ public final class EncryptionHelper {
* @param integerKey <p>The key used to encrypt the book</p> * @param integerKey <p>The key used to encrypt the book</p>
* @return <p>The new metadata for the book, or null if it could not be saved</p> * @return <p>The new metadata for the book, or null if it could not be saved</p>
*/ */
private static BookMeta saveBookPlaintext(String groupName, Player player, BookMeta book, String integerKey) { @Nullable
private static BookMeta saveBookPlaintext(@NotNull String groupName, @NotNull Player player,
@NotNull BookMeta book, @NotNull String integerKey) {
BookMeta newMetadata = book; BookMeta newMetadata = book;
boolean wasSaved; boolean wasSaved;
if (groupName.trim().isEmpty()) { if (groupName.trim().isEmpty()) {
@@ -189,7 +207,8 @@ public final class EncryptionHelper {
* @param deleteEncryptedFile <p>Whether to delete the plaintext file after decryption is finished</p> * @param deleteEncryptedFile <p>Whether to delete the plaintext file after decryption is finished</p>
* @return <p>The loaded book, or null if no book could be loaded</p> * @return <p>The loaded book, or null if no book could be loaded</p>
*/ */
public static ItemStack loadEncryptedBook(Player player, String key, boolean deleteEncryptedFile) { @Nullable
public static ItemStack loadEncryptedBook(@NotNull Player player, @NotNull String key, boolean deleteEncryptedFile) {
ItemStack heldBook = InventoryHelper.getHeldBook(player, true); ItemStack heldBook = InventoryHelper.getHeldBook(player, true);
BookMeta bookMetadata = (BookMeta) heldBook.getItemMeta(); BookMeta bookMetadata = (BookMeta) heldBook.getItemMeta();
String path = getBookFolder() + "Encrypted" + getSlash(); String path = getBookFolder() + "Encrypted" + getSlash();
@@ -198,11 +217,8 @@ public final class EncryptionHelper {
return null; return null;
} }
String fileName = (!bookMetadata.hasTitle()) ? "Untitled," + player.getName() : bookMetadata.getTitle() + String fileName = "[" + key + "]" + BookHelper.getBookFile(bookMetadata, player, true);
BooksWithoutBordersConfig.getTitleAuthorSeparator() + bookMetadata.getAuthor(); fileName = fixName(cleanString(fileName), false);
fileName = "[" + key + "]" + fileName;
fileName = cleanString(fileName);
fileName = fixName(fileName, false);
File file = new File(path + fileName + ".yml"); File file = new File(path + fileName + ".yml");
if (!file.isFile()) { if (!file.isFile()) {
@@ -248,7 +264,9 @@ public final class EncryptionHelper {
* @param groupName <p>The group which should be able to decrypt the book</p> * @param groupName <p>The group which should be able to decrypt the book</p>
* @return <p>The new encrypted metadata for the book, or null if encryption failed</p> * @return <p>The new encrypted metadata for the book, or null if encryption failed</p>
*/ */
private static BookMeta saveEncryptedBookForGroup(Player player, BookMeta bookMetadata, String groupName) { @Nullable
private static BookMeta saveEncryptedBookForGroup(@NotNull Player player, @NotNull BookMeta bookMetadata,
@NotNull String groupName) {
String path = getBookFolder() + "Encrypted" + getSlash() + cleanString(groupName) + getSlash(); String path = getBookFolder() + "Encrypted" + getSlash() + cleanString(groupName) + getSlash();
File dirTest = new File(path); File dirTest = new File(path);
//Creates group dir //Creates group dir
@@ -258,17 +276,13 @@ public final class EncryptionHelper {
BooksWithoutBorders.sendErrorMessage(BooksWithoutBorders.getConsoleSender(), "Unable to create encryption group folder!"); BooksWithoutBorders.sendErrorMessage(BooksWithoutBorders.getConsoleSender(), "Unable to create encryption group folder!");
return null; return null;
} }
} catch (Exception e) { } catch (Exception exception) {
e.printStackTrace(); BooksWithoutBorders.getInstance().getLogger().log(Level.SEVERE, "Unable to save group encrypted book");
return null; return null;
} }
} }
//Creates file //Generate file name
String fileName = (!bookMetadata.hasTitle()) ? "Untitled," + player.getName() : String fileName = BookHelper.getBookFile(bookMetadata, player, true);
bookMetadata.getTitle() + BooksWithoutBordersConfig.getTitleAuthorSeparator() + bookMetadata.getAuthor();
fileName = cleanString(fileName);
fileName = fixName(fileName, false);
List<String> newLore = new ArrayList<>(); List<String> newLore = new ArrayList<>();
newLore.add(ChatColor.GRAY + "[" + groupName + " encrypted]"); newLore.add(ChatColor.GRAY + "[" + groupName + " encrypted]");
@@ -281,12 +295,12 @@ public final class EncryptionHelper {
bookMetadata.setLore(newLore); bookMetadata.setLore(newLore);
//Save file //Save file
File file = (BooksWithoutBordersConfig.getUseYml()) ? new File(path + fileName + ".yml") : new File(path + fileName + ".txt"); File file = (BooksWithoutBordersConfig.getUseYml()) ? new File(path + fileName + ".yml") :
new File(path + fileName + ".txt");
if (!file.isFile()) { if (!file.isFile()) {
try { try {
BookToFromTextHelper.bookToYml(path, fileName, bookMetadata); BookToFromTextHelper.bookToYml(path, fileName, bookMetadata);
} catch (IOException e) { } catch (IOException exception) {
e.printStackTrace();
BooksWithoutBorders.sendErrorMessage(player, "Group encrypted failed!"); BooksWithoutBorders.sendErrorMessage(player, "Group encrypted failed!");
return null; return null;
} }
@@ -303,25 +317,23 @@ public final class EncryptionHelper {
* @param key <p>The key to use for encryption</p> * @param key <p>The key to use for encryption</p>
* @return <p>The new encrypted metadata for the book, or null if encryption failed</p> * @return <p>The new encrypted metadata for the book, or null if encryption failed</p>
*/ */
private static Boolean saveEncryptedBook(Player player, BookMeta bookMetaData, String key) { @NotNull
private static Boolean saveEncryptedBook(@NotNull Player player, @NotNull BookMeta bookMetaData, @NotNull String key) {
String path = getBookFolder() + "Encrypted" + getSlash(); String path = getBookFolder() + "Encrypted" + getSlash();
String fileName = (!bookMetaData.hasTitle()) ? "Untitled," + player.getName() :
bookMetaData.getTitle() + BooksWithoutBordersConfig.getTitleAuthorSeparator() + bookMetaData.getAuthor();
fileName = "[" + key + "]" + fileName; String fileName = "[" + key + "]" + BookHelper.getBookFile(bookMetaData, player, true);
fileName = cleanString(fileName); fileName = fixName(cleanString(fileName), false);
fileName = fixName(fileName, false);
//cancels saving if file is already encrypted //cancels saving if file is already encrypted
File file = (BooksWithoutBordersConfig.getUseYml()) ? new File(path + fileName + ".yml") : new File(path + fileName + ".txt"); File file = (BooksWithoutBordersConfig.getUseYml()) ? new File(path + fileName + ".yml") :
new File(path + fileName + ".txt");
if (file.isFile()) { if (file.isFile()) {
return true; return true;
} }
try { try {
BookToFromTextHelper.bookToYml(path, fileName, bookMetaData); BookToFromTextHelper.bookToYml(path, fileName, bookMetaData);
} catch (IOException e) { } catch (IOException exception) {
e.printStackTrace();
BooksWithoutBorders.sendErrorMessage(player, "Encryption failed!"); BooksWithoutBorders.sendErrorMessage(player, "Encryption failed!");
return false; return false;
} }

View File

@@ -1,7 +1,12 @@
package net.knarcraft.bookswithoutborders.utility; package net.knarcraft.bookswithoutborders.utility;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/** /**
* Helper class for cleaning input and names * Helper class for cleaning input and names
@@ -17,7 +22,8 @@ public final class InputCleaningHelper {
* @param list <p>The list to clean</p> * @param list <p>The list to clean</p>
* @return <p>A clean list containing all relevant values</p> * @return <p>A clean list containing all relevant values</p>
*/ */
public static List<String> cleanList(List<String> list) { @NotNull
public static List<String> cleanList(@NotNull List<String> list) {
List<String> resultList = new ArrayList<>(list); List<String> resultList = new ArrayList<>(list);
resultList.removeIf((item) -> item == null || item.trim().isEmpty()); resultList.removeIf((item) -> item == null || item.trim().isEmpty());
return resultList; return resultList;
@@ -29,7 +35,8 @@ public final class InputCleaningHelper {
* @param fileName <p>The file name to clean</p> * @param fileName <p>The file name to clean</p>
* @return <p>The cleaned file name</p> * @return <p>The cleaned file name</p>
*/ */
public static String cleanString(String fileName) { @NotNull
public static String cleanString(@NotNull String fileName) {
fileName = fileName.replace("/", ""); fileName = fileName.replace("/", "");
fileName = fileName.replace("\\", ""); fileName = fileName.replace("\\", "");
fileName = fileName.replace("*", ""); fileName = fileName.replace("*", "");
@@ -39,6 +46,7 @@ public final class InputCleaningHelper {
fileName = fileName.replace(">", ""); fileName = fileName.replace(">", "");
fileName = fileName.replace("?", ""); fileName = fileName.replace("?", "");
fileName = fileName.replace("\"", ""); fileName = fileName.replace("\"", "");
fileName = fileName.replace("§", "&");
return fileName; return fileName;
} }
@@ -49,7 +57,8 @@ public final class InputCleaningHelper {
* @param isLoading <p>Whether loading from a file as opposed to saving to a file</p> * @param isLoading <p>Whether loading from a file as opposed to saving to a file</p>
* @return <p>The fixed name</p> * @return <p>The fixed name</p>
*/ */
public static String fixName(String fileName, Boolean isLoading) { @NotNull
public static String fixName(@NotNull String fileName, @NotNull Boolean isLoading) {
if (isLoading) { if (isLoading) {
fileName = fileName.replace("_", " "); fileName = fileName.replace("_", " ");
} else { } else {
@@ -58,4 +67,41 @@ public final class InputCleaningHelper {
return fileName; return fileName;
} }
/**
* Parses a page number for a string like "page1"
*
* @param input <p>The input to parse</p>
* @return <p>The page number, or 0 if not valid</p>
*/
public static int parsePageNumber(@NotNull String input) {
try {
Pattern pattern = Pattern.compile("page([0-9]+)");
Matcher matcher = pattern.matcher(input);
if (matcher.matches()) {
return Integer.parseInt(matcher.group(1));
} else {
return 0;
}
} catch (NumberFormatException exception) {
return 0;
}
}
/**
* Parses an author specifier given in a command
*
* @param input <p>The input to parse</p>
* @return <p>The author name, or null if not an author specifier</p>
*/
@Nullable
public static String parseAuthorSpecifier(@NotNull String input) {
Pattern pattern = Pattern.compile("author([0-9a-zA-Z_]+)");
Matcher matcher = pattern.matcher(input);
if (matcher.matches()) {
return matcher.group(1);
} else {
return null;
}
}
} }

View File

@@ -0,0 +1,94 @@
package net.knarcraft.bookswithoutborders.utility;
import org.jetbrains.annotations.NotNull;
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>
*/
@NotNull
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>
*/
@NotNull
private static String repeat(char character, int times) {
return String.valueOf(character).repeat(Math.max(0, times));
}
}

View File

@@ -7,6 +7,8 @@ import org.bukkit.Material;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.BookMeta; import org.bukkit.inventory.meta.BookMeta;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/** /**
* The inventory helper mainly helps with getting and setting books * The inventory helper mainly helps with getting and setting books
@@ -23,7 +25,8 @@ public final class InventoryHelper {
* @param signedBook <p>Whether to check for signed or unsigned books</p> * @param signedBook <p>Whether to check for signed or unsigned books</p>
* @return <p>The book the player is holding</p> * @return <p>The book the player is holding</p>
*/ */
public static ItemStack getHeldBook(Player player, boolean signedBook) { @NotNull
public static ItemStack getHeldBook(@NotNull Player player, boolean signedBook) {
ItemSlot heldSlot = getHeldSlotBook(player, false, false, true, signedBook); ItemSlot heldSlot = getHeldSlotBook(player, false, false, true, signedBook);
if (heldSlot == ItemSlot.MAIN_HAND) { if (heldSlot == ItemSlot.MAIN_HAND) {
return getHeldItem(player, true); return getHeldItem(player, true);
@@ -40,7 +43,7 @@ public final class InventoryHelper {
* @param player <p>The player holding the book</p> * @param player <p>The player holding the book</p>
* @param newBook <p>The new book the player should hold</p> * @param newBook <p>The new book the player should hold</p>
*/ */
public static void setHeldWrittenBook(Player player, ItemStack newBook) { public static void setHeldWrittenBook(@NotNull Player player, @NotNull ItemStack newBook) {
ItemSlot itemSlot = getHeldSlotBook(player, false, false, true, true); ItemSlot itemSlot = getHeldSlotBook(player, false, false, true, true);
if (itemSlot == ItemSlot.MAIN_HAND) { if (itemSlot == ItemSlot.MAIN_HAND) {
replaceHeldItem(player, newBook, true); replaceHeldItem(player, newBook, true);
@@ -51,6 +54,32 @@ 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(@NotNull Player player, @NotNull String noBookMessage,
@NotNull 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 * Performs checks to validate that a player contains exactly one written book
* *
@@ -59,7 +88,8 @@ public final class InventoryHelper {
* @param twoBooksMessage <p>The message to display if the player is holding one book in each hand</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> * @return <p>False if the player is holding exactly one book</p>
*/ */
public static boolean notHoldingOneWrittenBookCheck(Player player, String noBookMessage, String twoBooksMessage) { public static boolean notHoldingOneWrittenBookCheck(@NotNull Player player, @NotNull String noBookMessage,
@NotNull String twoBooksMessage) {
BookHoldingState holdingState = getBookHoldingState(player); BookHoldingState holdingState = getBookHoldingState(player);
if (holdingState == BookHoldingState.NONE || holdingState == BookHoldingState.UNSIGNED_BOTH_HANDS || if (holdingState == BookHoldingState.NONE || holdingState == BookHoldingState.UNSIGNED_BOTH_HANDS ||
@@ -86,7 +116,8 @@ public final class InventoryHelper {
* @param writtenBook <p>Whether to search for written or unwritten books, if it's relevant</p> * @param writtenBook <p>Whether to search for written or unwritten books, if it's relevant</p>
* @return <p>The slot of the player's held book</p> * @return <p>The slot of the player's held book</p>
*/ */
public static ItemSlot getHeldSlotBook(Player player, boolean handMatters, boolean mainHand, @NotNull
public static ItemSlot getHeldSlotBook(@NotNull Player player, boolean handMatters, boolean mainHand,
boolean typeMatters, boolean writtenBook) { boolean typeMatters, boolean writtenBook) {
BookHoldingState state = getBookHoldingState(player); BookHoldingState state = getBookHoldingState(player);
ItemStack mainHandItem = getHeldItem(player, true); ItemStack mainHandItem = getHeldItem(player, true);
@@ -143,7 +174,8 @@ public final class InventoryHelper {
* @param player <p>The player to check</p> * @param player <p>The player to check</p>
* @return <p>The state of the player's book holding</p> * @return <p>The state of the player's book holding</p>
*/ */
private static BookHoldingState getBookHoldingState(Player player) { @NotNull
private static BookHoldingState getBookHoldingState(@NotNull Player player) {
ItemStack mainHandItem = getHeldItem(player, true); ItemStack mainHandItem = getHeldItem(player, true);
ItemStack offHandItem = getHeldItem(player, false); ItemStack offHandItem = getHeldItem(player, false);
@@ -180,7 +212,8 @@ public final class InventoryHelper {
* @param mainHand <p>Whether to get information about a book in the player's main hand or off hand</p> * @param mainHand <p>Whether to get information about a book in the player's main hand or off hand</p>
* @return <p>Information about the held book</p> * @return <p>Information about the held book</p>
*/ */
public static BookMeta getHeldBookMetadata(Player player, boolean mainHand) { @Nullable
public static BookMeta getHeldBookMetadata(@NotNull Player player, boolean mainHand) {
return (BookMeta) getHeldItem(player, mainHand).getItemMeta(); return (BookMeta) getHeldItem(player, mainHand).getItemMeta();
} }
@@ -191,7 +224,8 @@ public final class InventoryHelper {
* @param mainHand <p>Whether to get the item in the player's main hand or off hand</p> * @param mainHand <p>Whether to get the item in the player's main hand or off hand</p>
* @return <p>The item the player is holding in the given hand</p> * @return <p>The item the player is holding in the given hand</p>
*/ */
public static ItemStack getHeldItem(Player player, boolean mainHand) { @NotNull
public static ItemStack getHeldItem(@NotNull Player player, boolean mainHand) {
if (mainHand) { if (mainHand) {
return player.getInventory().getItemInMainHand(); return player.getInventory().getItemInMainHand();
} else { } else {
@@ -206,7 +240,7 @@ public final class InventoryHelper {
* @param newBook <p>The new book the player should hold</p> * @param newBook <p>The new book the player should hold</p>
* @param mainHand <p>Whether to replace the item in the player's main hand or off hand</p> * @param mainHand <p>Whether to replace the item in the player's main hand or off hand</p>
*/ */
public static void replaceHeldItem(Player player, ItemStack newBook, boolean mainHand) { public static void replaceHeldItem(@NotNull Player player, @NotNull ItemStack newBook, boolean mainHand) {
if (mainHand) { if (mainHand) {
player.getInventory().setItemInMainHand(newBook); player.getInventory().setItemInMainHand(newBook);
} else { } else {

View File

@@ -1,48 +1,16 @@
package net.knarcraft.bookswithoutborders.utility; package net.knarcraft.bookswithoutborders.utility;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**
* Helper class for getting string lists required for auto-completion * Helper class for getting string lists required for auto-completion
*/ */
public final class TabCompletionHelper { public final class TabCompletionTypeHelper {
private TabCompletionHelper() { private TabCompletionTypeHelper() {
}
/**
* Finds tab complete values that contain the typed text
*
* @param values <p>The values to filter</p>
* @param typedText <p>The text the player has started typing</p>
* @return <p>The given string values that contain the player's typed text</p>
*/
public static List<String> filterMatchingContains(List<String> values, String typedText) {
List<String> configValues = new ArrayList<>();
for (String value : values) {
if (value.toLowerCase().contains(typedText.toLowerCase())) {
configValues.add(value);
}
}
return configValues;
}
/**
* Finds tab complete values that match the start of the typed text
*
* @param values <p>The values to filter</p>
* @param typedText <p>The text the player has started typing</p>
* @return <p>The given string values that start with the player's typed text</p>
*/
public static List<String> filterMatchingStartsWith(List<String> values, String typedText) {
List<String> configValues = new ArrayList<>();
for (String value : values) {
if (value.toLowerCase().startsWith(typedText.toLowerCase())) {
configValues.add(value);
}
}
return configValues;
} }
/** /**
@@ -50,6 +18,7 @@ public final class TabCompletionHelper {
* *
* @return <p>A list of booleans</p> * @return <p>A list of booleans</p>
*/ */
@NotNull
public static List<String> getBooleans() { public static List<String> getBooleans() {
List<String> booleans = new ArrayList<>(); List<String> booleans = new ArrayList<>();
booleans.add("true"); booleans.add("true");
@@ -64,6 +33,7 @@ public final class TabCompletionHelper {
* @param end <p>The end number</p> * @param end <p>The end number</p>
* @return <p>A list of numbers</p> * @return <p>A list of numbers</p>
*/ */
@NotNull
public static List<String> getNumbers(int start, int end) { public static List<String> getNumbers(int start, int end) {
List<String> numbers = new ArrayList<>(); List<String> numbers = new ArrayList<>();
for (int i = start; i <= end; i++) { for (int i = start; i <= end; i++) {
@@ -79,6 +49,7 @@ public final class TabCompletionHelper {
* @param end <p>The end number</p> * @param end <p>The end number</p>
* @return <p>A list of booleans and numbers</p> * @return <p>A list of booleans and numbers</p>
*/ */
@NotNull
public static List<String> getBooleansAndNumbers(int start, int end) { public static List<String> getBooleansAndNumbers(int start, int end) {
List<String> booleansAndNumbers = new ArrayList<>(); List<String> booleansAndNumbers = new ArrayList<>();
List<String> booleans = getBooleans(); List<String> booleans = getBooleans();

View File

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

View File

@@ -5,7 +5,7 @@ Options:
Max_Number_of_Duplicates: 5 Max_Number_of_Duplicates: 5
# The separator used to separate the book title and the book author # The separator used to separate the book title and the book author
Title-Author_Separator: "," Title-Author_Separator: ","
# The separator used to denote a new line in the book lore # The separator used to denote a new line in the book/item lore
Lore_line_separator: "~" Lore_line_separator: "~"
# A list of books given to new players the first time they join the server # A list of books given to new players the first time they join the server
Books_for_new_players: [ ] Books_for_new_players: [ ]
@@ -21,6 +21,14 @@ Options:
Admin_Auto_Decrypt: false Admin_Auto_Decrypt: false
# Whether to only allow the author of a book to create copies # Whether to only allow the author of a book to create copies
Author_Only_Copy: false Author_Only_Copy: false
# Whether to only allow the author of a book to unsign it
Author_Only_Unsign: false
# Whether to only allow saving a player's own books with /savebook
Author_Only_Save: false
# Whether to automatically format every book when it's signed # Whether to automatically format every book when it's signed
Format_Book_After_Signing: false Format_Book_After_Signing: false
# Whether to display "COPY" or "COPY_OF_COPY" instead of "ORIGINAL" when a book is copied. This also uses the
# vanilla behavior where a copy of a copy cannot be copied further.
Change_Generation_On_Copy: false
# Whether to enable hitting a chiseled bookshelf while sneaking to see the shelf's contents.
Enable_Book_Peeking: false

View File

@@ -1,10 +1,10 @@
name: BooksWithoutBorders name: BooksWithoutBorders
version: '${project.version}' version: '${project.version}'
main: net.knarcraft.bookswithoutborders.BooksWithoutBorders main: net.knarcraft.bookswithoutborders.BooksWithoutBorders
api-version: 1.18 api-version: '1.20'
prefix: Books Without Borders prefix: 'Books Without Borders'
authors: [ EpicKnarvik97, AkiraAkiba ] authors: [ EpicKnarvik97, AkiraAkiba ]
description: A continuation of the original Books Without Borders description: 'A continuation of the original Books Without Borders'
softdepend: [ Vault ] softdepend: [ Vault ]
website: https://www.spigotmc.org/resources/books-without-borders-updated.96069/ website: https://www.spigotmc.org/resources/books-without-borders-updated.96069/
dev-url: https://git.knarcraft.net/EpicKnarvik97/Books-Without-Borders dev-url: https://git.knarcraft.net/EpicKnarvik97/Books-Without-Borders
@@ -45,6 +45,10 @@ commands:
description: Copies the book the player is holding description: Copies the book the player is holding
usage: /<command> <# of copies> usage: /<command> <# of copies>
permission: bookswithoutborders.copy permission: bookswithoutborders.copy
clearbook:
description: Removes all text from the book the player is holding
usage: /<command>
permission: bookswithoutborders.clear
unsignbook: unsignbook:
description: Un-signs the book the player is holding description: Un-signs the book the player is holding
usage: /<command> usage: /<command>
@@ -53,6 +57,10 @@ commands:
description: Encrypts the book the player is holding. "key" is required and can be any phrase or number excluding spaces. "style" is not required. Possible values are "DNA" or "" description: Encrypts the book the player is holding. "key" is required and can be any phrase or number excluding spaces. "style" is not required. Possible values are "DNA" or ""
usage: /<command> <key> [encryption style] usage: /<command> <key> [encryption style]
permission: bookswithoutborders.encrypt permission: bookswithoutborders.encrypt
setbookgeneration:
description: Sets the generation of the held book
usage: /<command> <generation>
permission: bookswithoutborders.setgeneration
setbookprice: setbookprice:
description: Sets the per-book-price to create a book via commands. If "Item", the item in the player's hand in the amount of [quantity] will be the price. If "Eco", a Vault based economy will be used for price. If neither <Item/Eco> or <quantity> are specified, the current price to create books will be removed. description: Sets the per-book-price to create a book via commands. If "Item", the item in the player's hand in the amount of [quantity] will be the price. If "Eco", a Vault based economy will be used for price. If neither <Item/Eco> or <quantity> are specified, the current price to create books will be removed.
usage: /<command> <item/eco> <quantity> usage: /<command> <item/eco> <quantity>
@@ -88,7 +96,11 @@ commands:
reload: reload:
description: Reloads BwB's configuration file description: Reloads BwB's configuration file
usage: /<command> usage: /<command>
permission: bookswithoutborders.admin permission: bookswithoutborders.reload
setBookshelfData:
description: Sets custom data for a chiseled bookshelf used when peeking at the bookshelf
usage: /<command> <delete/name/lore> <text> [more text]
permission: bookswithoutborders.editbookshelf
permissions: permissions:
bookswithoutborders.*: bookswithoutborders.*:
description: Grants all permissions description: Grants all permissions
@@ -111,22 +123,30 @@ permissions:
bookswithoutborders.give: true bookswithoutborders.give: true
bookswithoutborders.givepublic: true bookswithoutborders.givepublic: true
bookswithoutborders.bypassauthoronlycopy: true bookswithoutborders.bypassauthoronlycopy: true
bookswithoutborders.bypassauthoronlyunsign: true
bookswithoutborders.bypassauthoronlysave: true
bookswithoutborders.bypassbookprice: true bookswithoutborders.bypassbookprice: true
bookswithoutborders.setbookprice: true bookswithoutborders.setbookprice: true
bookswithoutborders.reload: true
bookswithoutborders.setgeneration: true
bookswithoutborders.editbookshelf: true
bookswithoutborders.use: bookswithoutborders.use:
description: Allows player to use commands to save/load/delete in their personal directory description: Allows player to use commands to save/load/delete in their personal directory, and peeking at bookshelves if enabled
children: children:
bookswithoutborders.save: true bookswithoutborders.save: true
bookswithoutborders.load: true bookswithoutborders.load: true
bookswithoutborders.delete: true bookswithoutborders.delete: true
bookswithoutborders.peekbookshelf: true
bookswithoutborders.alterbooks: bookswithoutborders.alterbooks:
description: Allows player to change books' data such as lore/title/author/formatting and unsigning books description: Allows player to change books' data such as lore/title/author/generation/formatting and unsigning books
children: children:
bookswithoutborders.clear: true
bookswithoutborders.unsign: true bookswithoutborders.unsign: true
bookswithoutborders.settitle: true bookswithoutborders.settitle: true
bookswithoutborders.setauthor: true bookswithoutborders.setauthor: true
bookswithoutborders.setlore: true bookswithoutborders.setlore: true
bookswithoutborders.format: true bookswithoutborders.format: true
bookswithoutborders.setgeneration: true
bookswithoutborders.format: bookswithoutborders.format:
description: Allows a player to format a book description: Allows a player to format a book
bookswithoutborders.save: bookswithoutborders.save:
@@ -136,9 +156,11 @@ permissions:
bookswithoutborders.delete: bookswithoutborders.delete:
description: Allows player to delete books from their personal directory description: Allows player to delete books from their personal directory
bookswithoutborders.unsign: bookswithoutborders.unsign:
description: Allows player to use unsign command description: Allows player to use the unsign command
bookswithoutborders.copy: 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: bookswithoutborders.loadpublic:
description: Allows player to load from the public directory description: Allows player to load from the public directory
bookswithoutborders.savepublic: bookswithoutborders.savepublic:
@@ -163,7 +185,19 @@ permissions:
description: Allows player to set the lore of the currently held item description: Allows player to set the lore of the currently held item
bookswithoutborders.bypassauthoronlycopy: bookswithoutborders.bypassauthoronlycopy:
description: Allows player to ignore Author_Only_Copy config setting description: Allows player to ignore Author_Only_Copy config setting
bookswithoutborders.bypassauthoronlyunsign:
description: Allows player to ignore Author_Only_Unsign config setting
bookswithoutborders.bypassauthoronlysave:
description: Allows player to ignore Author_Only_Save config setting
bookswithoutborders.bypassbookprice: bookswithoutborders.bypassbookprice:
description: Allows player to ignore Price_to_create_book config setting description: Allows player to ignore Price_to_create_book config setting
bookswithoutborders.setbookprice: bookswithoutborders.setbookprice:
description: Allows player to set the cost of creating a book description: Allows player to set the cost of creating a book
bookswithoutborders.reload:
description: Allows player to reload this plugin
bookswithoutborders.setgeneration:
description: Allows player to change the generation of a book (Original, Copy, Copy of Copy)
bookswithoutborders.peekbookshelf:
description: Allows player to left-click a bookshelf to see the contents of the shelf
bookswithoutborders.editbookshelf:
description: Allows player to set name/lore for bookshelves, used for peeking

View File

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

View File

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