Makes a lot of formatting customizable, and fixes some problems caused by splitting Translatable
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good

This commit is contained in:
2025-08-20 13:38:10 +02:00
parent 887cc72f0d
commit b01523f058
23 changed files with 315 additions and 98 deletions

View File

@@ -145,7 +145,7 @@ public class CommandBooksWithoutBorders implements TabExecutor {
PluginCommand pluginCommand = BooksWithoutBorders.getInstance().getCommand(bwBCommand.toString());
if (pluginCommand == null) {
BooksWithoutBorders.log(Level.SEVERE, StringFormatter.replacePlaceholder(
StaticMessage.COMMAND_NOT_REGISTERED.toString(), "{command}", bwBCommand.toString()));
StaticMessage.EXCEPTION_COMMAND_NOT_REGISTERED.toString(), "{command}", bwBCommand.toString()));
return "";
}

View File

@@ -40,7 +40,7 @@ public class CommandSetLore implements TabExecutor {
ItemStack heldItem = InventoryHelper.getHeldItem(player, true);
if (heldItem.getType() == Material.AIR) {
stringFormatter.displayErrorMessage(player, Translatable.ERROR_LORE_NO_ITEM);
stringFormatter.displayErrorMessage(player, Translatable.ERROR_NO_ITEM);
return false;
}

View File

@@ -40,7 +40,7 @@ public class CommandSetTitle implements TabExecutor {
ItemStack heldItem = InventoryHelper.getHeldItem(player, true);
if (heldItem.getType() == Material.AIR) {
BooksWithoutBorders.sendErrorMessage(sender, "You must be holding an item to set title!");
stringFormatter.displayErrorMessage(sender, Translatable.ERROR_NO_ITEM);
return false;
}
@@ -49,7 +49,7 @@ public class CommandSetTitle implements TabExecutor {
ItemMeta itemMetadata = heldItem.getItemMeta();
if (itemMetadata == null) {
BooksWithoutBorders.sendErrorMessage(sender, "Unable to get metadata for your held item!");
stringFormatter.displayErrorMessage(sender, Translatable.ERROR_METADATA_MISSING);
return false;
}
@@ -57,7 +57,7 @@ public class CommandSetTitle implements TabExecutor {
ItemMeta newMetaData;
if (heldItem.getType() == Material.WRITTEN_BOOK) {
if (title.length() > 32) {
BooksWithoutBorders.sendErrorMessage(sender, "Book titles are capped at 32 characters!");
stringFormatter.displayErrorMessage(sender, Translatable.ERROR_TITLE_LENGTH);
return false;
}
BookMeta bookMetadata = (BookMeta) itemMetadata;
@@ -70,7 +70,8 @@ public class CommandSetTitle implements TabExecutor {
//Set the new metadata
heldItem.setItemMeta(newMetaData);
BooksWithoutBorders.sendSuccessMessage(sender, "Title set to " + title + "!");
stringFormatter.displaySuccessMessage(sender,
stringFormatter.replacePlaceholder(Translatable.SUCCESS_TITLE_SET, "{title}", title));
return true;
}

View File

@@ -1,9 +1,12 @@
package net.knarcraft.bookswithoutborders.command;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.Permission;
import net.knarcraft.bookswithoutborders.config.translation.Translatable;
import net.knarcraft.bookswithoutborders.state.ItemSlot;
import net.knarcraft.bookswithoutborders.utility.BookHelper;
import net.knarcraft.bookswithoutborders.utility.InventoryHelper;
import net.knarcraft.knarlib.formatting.StringFormatter;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor;
@@ -24,13 +27,17 @@ public class CommandUnSign implements TabExecutor {
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
@NotNull String[] arguments) {
StringFormatter stringFormatter = BooksWithoutBorders.getStringFormatter();
if (!(sender instanceof Player player)) {
BooksWithoutBorders.sendErrorMessage(sender, "This command can only be used by a player!");
stringFormatter.displayErrorMessage(sender, Translatable.ERROR_PLAYER_ONLY);
return false;
}
if (InventoryHelper.notHoldingOneWrittenBookCheck(player, "You must be holding a signed book to unsign it!",
"You cannot unsign two books at once. Please un-equip one of the books you're holding!")) {
if (InventoryHelper.notHoldingOneWrittenBookCheck(player,
stringFormatter.replacePlaceholder(Translatable.ERROR_NOT_HOLDING_WRITTEN_BOOK, "{action}",
stringFormatter.getUnFormattedColoredMessage(Translatable.ACTION_UNSIGN)),
stringFormatter.replacePlaceholder(Translatable.ERROR_ONLY_ONE_BOOK, "{action}",
stringFormatter.getUnFormattedColoredMessage(Translatable.ACTION_UNSIGN)))) {
return false;
}
@@ -50,14 +57,14 @@ public class CommandUnSign implements TabExecutor {
//Get the old book
BookMeta oldMetadata = InventoryHelper.getHeldBookMetadata(player, mainHand);
if (oldMetadata == null) {
BooksWithoutBorders.sendErrorMessage(player, "Unable to get metadata from the held book!");
BooksWithoutBorders.getStringFormatter().displayErrorMessage(player, Translatable.ERROR_METADATA_MISSING);
return;
}
ItemStack heldBook = InventoryHelper.getHeldBook(player, mainHand);
//Only allow the owner to un-sign the book
if (BooksWithoutBorders.getConfiguration().getAuthorOnlyUnsign() &&
!player.hasPermission("bookswithoutborders.bypassAuthorOnlyUnsign")) {
!player.hasPermission(Permission.AUTHOR_ONLY_UNSIGN.toString())) {
if (BookHelper.isNotAuthor(player, Objects.requireNonNull(oldMetadata))) {
return;
}

View File

@@ -37,6 +37,10 @@ public enum Permission {
*/
BYPASS_AUTHOR_ONLY_SAVE("bypassAuthorOnlySave"),
/**
* The permission for bypassing author only un-signing
*/
AUTHOR_ONLY_UNSIGN("bypassAuthorOnlyUnsign"),
;
private final @NotNull String node;

View File

@@ -9,7 +9,7 @@ public enum StaticMessage {
BOOK_SAVING_FAILED("Saving failed! Aborting..."),
BOOK_FOLDER_CREATE_FAILED("Unable to create necessary folders"),
COMMAND_NOT_REGISTERED("Command {command} has not been registered!"),
EXCEPTION_COMMAND_NOT_REGISTERED("Command {command} has not been registered!"),
EXCEPTION_VAULT_NOT_AVAILABLE("Vault is unavailable, but book price is set to economy. Unsetting book cost!"),
EXCEPTION_VAULT_PRICE_NOT_CHANGED("BooksWithoutBorders failed to hook into Vault! Book price not set!"),
EXCEPTION_ENCRYPTED_FILE_DELETE_FAILED("Book encryption data failed to delete upon decryption!\nFile location: {path}"),
@@ -17,6 +17,14 @@ public enum StaticMessage {
EXCEPTION_SAVE_BOOK_FAILED("Unable to save book"),
EXCEPTION_BOOKSHELF_SAVING_FAILED("Unable to save bookshelves!"),
NOTICE_NO_BOOKSHELVES("BooksWithoutBorders found no bookshelves to load"),
EXCEPTION_BOOKSHELF_NAME_EMPTY("Bookshelves cannot have empty titles!"),
DEBUG_AES_INVALID_KEY("Invalid AES key given!"),
DEBUG_AES_INVALID_PARAMETERS("Invalid AES parameters given!"),
DEBUG_AES_INVALID_BLOCK_SIZE("Invalid AES block size during finalization"),
DEBUG_AES_INVALID_PADDING_FINALIZATION("Invalid AES padding during finalization"),
DEBUG_AES_INVALID_ALGORITHM("Invalid AES algorithm"),
DEBUG_AES_INVALID_PADDING_CIPHER("Invalid AES padding during Cipher generation"),
DEBUG_AES_INVALID_KEY_SPECIFICATION("Invalid AES key specification"),
;
private final @NotNull String messageString;

View File

@@ -41,7 +41,7 @@ public enum BookshelfMessage implements TranslatableMessage {
@Override
public @NotNull TranslatableMessage[] getAllMessages() {
return Translatable.values();
return BookshelfMessage.values();
}
}

View File

@@ -51,7 +51,7 @@ public enum CostMessage implements TranslatableMessage {
@Override
public @NotNull TranslatableMessage[] getAllMessages() {
return Translatable.values();
return CostMessage.values();
}
}

View File

@@ -67,11 +67,91 @@ public enum Formatting implements TranslatableMessage {
* The format used when formatting text on a title page
*/
NEUTRAL_TITLE_PAGE_TEXT_FORMAT,
/**
* The format used when showing the header for public books by a specific author
*/
NEUTRAL_AUTHOR_PUBLIC_BOOKS_HEADER,
/**
* The format used when showing the header for player books by a specific author
*/
NEUTRAL_AUTHOR_PLAYER_BOOKS_HEADER,
/**
* The format used when showing the hover action for selecting a book by its path
*/
NEUTRAL_AUTHOR_BOOKS_PATH,
/**
* The format used when displaying an empty author books page
*/
NEUTRAL_AUTHOR_BOOKS_INVALID_PAGE,
/**
* The format used when showing current ant total pages in the book list
*/
NEUTRAL_BOOK_LIST_TOTAL_PAGES,
/**
* The format used when showing the previous button in the book list
*/
NEUTRAL_BOOK_LIST_PREVIOUS_PAGE,
/**
* The format used when showing the next button in the book list
*/
NEUTRAL_BOOK_LIST_NEXT_PAGE,
/**
* The separator between book and author in the book list
*/
NEUTRAL_BOOK_LIST_AUTHOR_SEPARATOR,
/**
* The format used when showing the page a link goes to in the book list
*/
NEUTRAL_BOOK_LIST_TO_PAGE,
/**
* The format used when showing the hover hint on the book index
*/
NEUTRAL_BOOK_LIST_INDEX_HOVER,
/**
* The format used when showing the header of the public book list
*/
NEUTRAL_BOOK_LIST_PUBLIC_BOOKS_HEADER,
/**
* The format used when showing the header of the player book list
*/
NEUTRAL_BOOK_LIST_PLAYER_BOOKS_HEADER,
/**
* The format used when showing the books by hover text
*/
NEUTRAL_BOOK_LIST_AUTHOR_HOVER,
/**
* The format used when showing the book path hover text
*/
NEUTRAL_BOOK_LIST_PATH_HOVER,
/**
* The format used when showing the book list select by index hover text
*/
NEUTRAL_BOOK_LIST_BOOK_INDEX_HOVER,
/**
* The format used when showing book int index in the book list
*/
NEUTRAL_BOOK_LIST_BOOK_INDEX_NUMBER,
;
@Override
public @NotNull TranslatableMessage[] getAllMessages() {
return Translatable.values();
return Formatting.values();
}
}

View File

@@ -41,7 +41,7 @@ public enum GiveMessage implements TranslatableMessage {
@Override
public @NotNull TranslatableMessage[] getAllMessages() {
return Translatable.values();
return GiveMessage.values();
}
}

View File

@@ -41,7 +41,7 @@ public enum SaveMessage implements TranslatableMessage {
@Override
public @NotNull TranslatableMessage[] getAllMessages() {
return Translatable.values();
return SaveMessage.values();
}
}

View File

@@ -78,6 +78,11 @@ public enum Translatable implements TranslatableMessage {
*/
SUCCESS_LORE_SET,
/**
* The success message displayed when an item's title is successfully set
*/
SUCCESS_TITLE_SET,
/**
* The error to display when the console attempts to run a player-only command
*/
@@ -128,6 +133,11 @@ public enum Translatable implements TranslatableMessage {
*/
ACTION_CHANGE_GENERATION,
/**
* The translation of the unsign action
*/
ACTION_UNSIGN,
/**
* The error displayed when running a relevant command while holding one book in each hand
*/
@@ -291,12 +301,17 @@ public enum Translatable implements TranslatableMessage {
/**
* The error displayed when attempting to change the lore of an item without holding an item
*/
ERROR_LORE_NO_ITEM,
ERROR_NO_ITEM,
/**
* The error displayed when attempting to change an item's title/display name without specifying the new title/display name
*/
ERROR_TITLE_EMPTY,
/**
* The error displayed when attempting to change a book's title with a title that's too long
*/
ERROR_TITLE_LENGTH,
;
@Override

View File

@@ -1,5 +1,6 @@
package net.knarcraft.bookswithoutborders.container;
import net.knarcraft.bookswithoutborders.config.StaticMessage;
import org.bukkit.Location;
import org.jetbrains.annotations.NotNull;
@@ -64,7 +65,7 @@ public class Bookshelf {
*/
public void setTitle(@NotNull String title) {
if (title.isBlank()) {
throw new IllegalArgumentException("Bookshelves cannot have empty titles!");
throw new IllegalArgumentException(StaticMessage.EXCEPTION_BOOKSHELF_NAME_EMPTY.toString());
}
this.title = title;
}

View File

@@ -1,6 +1,7 @@
package net.knarcraft.bookswithoutborders.encryption;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.StaticMessage;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -97,8 +98,11 @@ public class AES implements Encryptor {
//Initialize cipher
try {
aes.init(mode, secretKeySpec, ivParameterSpec);
} catch (InvalidKeyException | InvalidAlgorithmParameterException exception) {
BooksWithoutBorders.log(Level.SEVERE, "Invalid AES input given!");
} catch (InvalidKeyException exception) {
BooksWithoutBorders.log(Level.FINE, StaticMessage.DEBUG_AES_INVALID_KEY.toString());
return null;
} catch (InvalidAlgorithmParameterException exception) {
BooksWithoutBorders.log(Level.FINE, StaticMessage.DEBUG_AES_INVALID_PARAMETERS.toString());
return null;
}
//Perform encryption/decryption and output result
@@ -106,10 +110,10 @@ public class AES implements Encryptor {
byte[] output = aes.doFinal(getInputBytes(input, encrypt));
return createResult(output, encrypt);
} catch (IllegalBlockSizeException exception) {
BooksWithoutBorders.log(Level.SEVERE, "Invalid AES block size during finalization");
BooksWithoutBorders.log(Level.FINE, StaticMessage.DEBUG_AES_INVALID_BLOCK_SIZE.toString());
return null;
} catch (BadPaddingException exception) {
BooksWithoutBorders.log(Level.SEVERE, "Invalid AES padding during finalization");
BooksWithoutBorders.log(Level.FINE, StaticMessage.DEBUG_AES_INVALID_PADDING_FINALIZATION.toString());
return null;
}
}
@@ -156,10 +160,10 @@ public class AES implements Encryptor {
try {
aes = Cipher.getInstance("AES/CBC/PKCS5Padding");
} catch (NoSuchAlgorithmException exception) {
BooksWithoutBorders.log(Level.SEVERE, "Invalid AES algorithm during Cipher generation");
BooksWithoutBorders.log(Level.SEVERE, StaticMessage.DEBUG_AES_INVALID_ALGORITHM.toString());
return null;
} catch (NoSuchPaddingException exception) {
BooksWithoutBorders.log(Level.SEVERE, "Invalid AES padding during Cipher generation");
BooksWithoutBorders.log(Level.SEVERE, StaticMessage.DEBUG_AES_INVALID_PADDING_CIPHER.toString());
return null;
}
return aes;
@@ -178,14 +182,14 @@ public class AES implements Encryptor {
try {
keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
} catch (NoSuchAlgorithmException exception) {
BooksWithoutBorders.log(Level.SEVERE, "Invalid AES algorithm");
BooksWithoutBorders.log(Level.SEVERE, StaticMessage.DEBUG_AES_INVALID_ALGORITHM.toString());
return null;
}
SecretKey tmp;
try {
tmp = keyFactory.generateSecret(spec);
} catch (InvalidKeySpecException exception) {
BooksWithoutBorders.log(Level.SEVERE, "Invalid AES key specification");
BooksWithoutBorders.log(Level.SEVERE, StaticMessage.DEBUG_AES_INVALID_KEY_SPECIFICATION.toString());
return null;
}
return new SecretKeySpec(tmp.getEncoded(), "AES");

View File

@@ -12,6 +12,8 @@ import java.util.Random;
*
* <p>Not sure where this was gotten from, but it does exist at
* <a href="https://crypto.stackexchange.com/questions/11614/how-do-i-test-my-encryption-absolute-amateur">Stack Exchange</a>.</p>
*
* @author AkiraAkiba
*/
public class GenenCrypt implements Encryptor {
@@ -58,11 +60,12 @@ public class GenenCrypt implements Encryptor {
originalCodonList.remove(index);
}
// define the characters that can be encoded, 64 in total
// 26 capital letters
// 10 digits
// space, newline, and tab
// the symbols . , ? " ! @ # $ % ^ & * ( ) - + = / _ \ : ; < >
/* define the characters that can be encoded, 64 in total
26 capital letters
10 digits
space, newline, and tab
the symbols . , ? " ! @ # $ % ^ & * ( ) - + = / _ \ : ; < >
*/
availableCharacters = new String[]{"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q",
"R", "S", "T", "U", "V", "W", "X", "Y", "Z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", " ",
"\t", "\n", ".", ",", "?", "\"", "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "-", "+", "=", "/",

View File

@@ -5,6 +5,8 @@ import org.jetbrains.annotations.Nullable;
/**
* So-called "Magic" encryption which simply makes the contents unreadable
*
* @author AkiraAkiba
*/
public class Magic implements Encryptor {

View File

@@ -10,6 +10,8 @@ import java.util.Base64;
/**
* A one-time pad implementation
*
* @author AkiraAkiba
*/
public class OneTimePad implements Encryptor {

View File

@@ -8,6 +8,8 @@ import java.util.StringTokenizer;
/**
* A simple substitution cipher
*
* @author AkiraAkiba
*/
public class SubstitutionCipher implements Encryptor {

View File

@@ -1,8 +1,10 @@
package net.knarcraft.bookswithoutborders.gui;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.translation.Formatting;
import net.knarcraft.bookswithoutborders.utility.BookFileHelper;
import net.knarcraft.bookswithoutborders.utility.BookFormatter;
import net.knarcraft.knarlib.formatting.TranslatableMessage;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.ClickEvent;
import net.md_5.bungee.api.chat.ComponentBuilder;
@@ -33,9 +35,9 @@ public class AuthorBookIndex extends BookIndex {
int totalPages = (int) Math.ceil((double) availableBooks.size() / booksPerPage);
if (page > totalPages) {
sender.sendMessage(ChatColor.GRAY + "No such page");
BooksWithoutBorders.getStringFormatter().displayErrorMessage(sender, Formatting.NEUTRAL_AUTHOR_BOOKS_INVALID_PAGE);
} else {
showAuthorBooks(sender, command, page, totalPages, availableBooks, authorName);
showAuthorBooks(sender, command, page, totalPages, availableBooks, authorName, listPublic);
}
}
@@ -48,19 +50,16 @@ public class AuthorBookIndex extends BookIndex {
* @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>
* @param listPublic <p>Whether to display public books</p>
*/
private static void showAuthorBooks(@NotNull CommandSender sender, @NotNull String command, int page,
int totalPages, @NotNull List<String> availableBooks, @NotNull String authorName) {
int totalPages, @NotNull List<String> availableBooks,
@NotNull String authorName, boolean listPublic) {
ComponentBuilder componentBuilder = new ComponentBuilder();
String navigationCommand = command + " author" + authorName;
componentBuilder.append("--- ");
if (command.toLowerCase().contains("public")) {
componentBuilder.append("Publicly saved books by: ").color(ChatColor.GREEN).append(authorName).color(ChatColor.AQUA);
} else {
componentBuilder.append("Your saved books by: ").color(ChatColor.GREEN).append(authorName).color(ChatColor.AQUA);
}
componentBuilder.append(" ---", ComponentBuilder.FormatRetention.NONE).append("\n");
TranslatableMessage message = listPublic ? Formatting.NEUTRAL_AUTHOR_PUBLIC_BOOKS_HEADER : Formatting.NEUTRAL_AUTHOR_PLAYER_BOOKS_HEADER;
componentBuilder.append(color(message, "{author}", authorName));
displayBookList(componentBuilder, command, page, availableBooks);
@@ -84,10 +83,15 @@ public class AuthorBookIndex extends BookIndex {
@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(
String title = BookFileHelper.getBookTitleFromPath(availableBooks.get(bookIndex));
String author = BookFileHelper.getBookAuthorFromPath(availableBooks.get(bookIndex));
String niceName = color(title) + color(Formatting.NEUTRAL_BOOK_LIST_AUTHOR_SEPARATOR) + color(author);
componentBuilder.append(niceName).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")));
new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text(BooksWithoutBorders.getStringFormatter().
getUnFormattedColoredMessage(Formatting.NEUTRAL_AUTHOR_BOOKS_PATH))));
componentBuilder.append("\n");
}
}

View File

@@ -1,7 +1,12 @@
package net.knarcraft.bookswithoutborders.gui;
import net.knarcraft.bookswithoutborders.utility.BookFileHelper;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.translation.Formatting;
import net.knarcraft.bookswithoutborders.utility.InputCleaningHelper;
import net.knarcraft.knarlib.formatting.StringFormatter;
import net.knarcraft.knarlib.formatting.TranslatableMessage;
import net.knarcraft.knarlib.property.ColorConversion;
import net.knarcraft.knarlib.util.ColorHelper;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.ClickEvent;
import net.md_5.bungee.api.chat.ComponentBuilder;
@@ -9,7 +14,9 @@ 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 org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Map;
public abstract class BookIndex {
@@ -80,8 +87,11 @@ public abstract class BookIndex {
* @param page <p>The current page</p>
* @param totalPages <p>The total amount of pages</p>
*/
protected static void displayTotalPages(@NotNull ComponentBuilder componentBuilder, @NotNull String command, int page, int totalPages) {
componentBuilder.append("Page " + page + " of " + totalPages,
protected static void displayTotalPages(@NotNull ComponentBuilder componentBuilder, @NotNull String command,
int page, int totalPages) {
String pageDisplay = color(Formatting.NEUTRAL_BOOK_LIST_TOTAL_PAGES, List.of("{current}", "{total}"),
List.of(String.valueOf(page), String.valueOf(totalPages)));
componentBuilder.append(pageDisplay,
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));
@@ -101,10 +111,12 @@ public abstract class BookIndex {
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)));
HoverEvent hoverEvent = new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text(
color(Formatting.NEUTRAL_BOOK_LIST_INDEX_HOVER, "{character}", "" + character)));
ClickEvent clickEvent = new ClickEvent(ClickEvent.Action.RUN_COMMAND,
"/" + command + " page" + pageIndex);
componentBuilder.append(character + "").color(interactColor).event(clickEvent).event(hoverEvent);
} else {
componentBuilder.append(character + "", ComponentBuilder.FormatRetention.NONE).color(inactiveColor);
}
@@ -121,14 +133,16 @@ public abstract class BookIndex {
*/
protected static void displayPreviousButton(@NotNull ComponentBuilder componentBuilder,
@NotNull String command, int page) {
String previousPage = color(Formatting.NEUTRAL_BOOK_LIST_PREVIOUS_PAGE);
if (page > 1) {
String fullCommand = "/" + command + " page" + (page - 1);
HoverEvent prevPagePreview = new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text("To page " + (page - 1)));
HoverEvent prevPagePreview = new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text(
color(Formatting.NEUTRAL_BOOK_LIST_TO_PAGE, "{page}", String.valueOf(page - 1))));
ClickEvent prevPageClick = new ClickEvent(ClickEvent.Action.RUN_COMMAND, fullCommand);
componentBuilder.append("[<] Previous", ComponentBuilder.FormatRetention.NONE).color(interactColor)
.event(prevPagePreview).event(prevPageClick);
componentBuilder.append(previousPage, ComponentBuilder.FormatRetention.NONE).color(interactColor).
event(prevPagePreview).event(prevPageClick);
} else {
componentBuilder.append("[<] Previous", ComponentBuilder.FormatRetention.NONE).color(inactiveColor);
componentBuilder.append(previousPage, ComponentBuilder.FormatRetention.NONE).color(inactiveColor);
}
}
@@ -142,29 +156,73 @@ public abstract class BookIndex {
*/
protected static void displayNextButton(@NotNull ComponentBuilder componentBuilder,
@NotNull String command, int page, int totalPages) {
String nextPage = color(Formatting.NEUTRAL_BOOK_LIST_NEXT_PAGE);
if (page < totalPages) {
String fullCommand = "/" + command + " page" + (page + 1);
HoverEvent nextPagePreview = new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text("To page " + (page + 1)));
HoverEvent nextPagePreview = new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text(
color(Formatting.NEUTRAL_BOOK_LIST_TO_PAGE, "{page}", String.valueOf(page + 1))));
ClickEvent nextPageClick = new ClickEvent(ClickEvent.Action.RUN_COMMAND, fullCommand);
componentBuilder.append("Next [>]", ComponentBuilder.FormatRetention.NONE).color(interactColor)
componentBuilder.append(nextPage, ComponentBuilder.FormatRetention.NONE).color(interactColor)
.event(nextPagePreview).event(nextPageClick);
} else {
componentBuilder.append("Next [>]", ComponentBuilder.FormatRetention.NONE).color(inactiveColor);
componentBuilder.append(nextPage, ComponentBuilder.FormatRetention.NONE).color(inactiveColor);
}
}
/**
* Gets a nice name from a book's path
* Colors a translatable message
*
* @param bookPath <p>The path of a book</p>
* @return <p>The prettified book name</p>
* @param translatableMessage <p>The message to color</p>
* @return <p>The colored message</p>
*/
@NotNull
protected static String getNiceName(@NotNull String bookPath) {
String title = BookFileHelper.getBookTitleFromPath(bookPath);
String author = BookFileHelper.getBookAuthorFromPath(bookPath);
return ChatColor.translateAlternateColorCodes('&',
title + ChatColor.RESET + " by " + author + ChatColor.RESET);
protected static String color(@NotNull TranslatableMessage translatableMessage) {
return color(translatableMessage, (List<String>) null, null);
}
/**
* Colors a translatable message
*
* @param translatableMessage <p>The message to color</p>
* @param placeholder <p>Placeholder to replace</p>
* @param replacement <p>Replacement value</p>
* @return <p>The colored message</p>
*/
@NotNull
protected static String color(@NotNull TranslatableMessage translatableMessage, @NotNull String placeholder,
@NotNull String replacement) {
return color(translatableMessage, List.of(placeholder), List.of(replacement));
}
/**
* Colors a translatable message
*
* @param translatableMessage <p>The message to color</p>
* @param placeholders <p>Placeholders to replace</p>
* @param replacements <p>Replacement values</p>
* @return <p>The colored message</p>
*/
@NotNull
protected static String color(@NotNull TranslatableMessage translatableMessage, @Nullable List<String> placeholders,
@Nullable List<String> replacements) {
StringFormatter stringFormatter = BooksWithoutBorders.getStringFormatter();
if (placeholders == null || replacements == null) {
return stringFormatter.getUnFormattedColoredMessage(translatableMessage);
} else {
return ColorHelper.translateColorCodes(stringFormatter.replacePlaceholders(translatableMessage,
placeholders, replacements), ColorConversion.RGB);
}
}
/**
* Colors a message
*
* @param input <p>The message to color</p>
* @return <p>The colored message</p>
*/
@NotNull
protected static String color(@NotNull String input) {
return ColorHelper.translateColorCodes(input, ColorConversion.RGB);
}
}

View File

@@ -1,7 +1,10 @@
package net.knarcraft.bookswithoutborders.gui;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.translation.Formatting;
import net.knarcraft.bookswithoutborders.utility.BookFileHelper;
import net.knarcraft.bookswithoutborders.utility.BookFormatter;
import net.knarcraft.knarlib.formatting.TranslatableMessage;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.ClickEvent;
import net.md_5.bungee.api.chat.ComponentBuilder;
@@ -42,9 +45,9 @@ public class PagedBookIndex extends BookIndex {
int totalPages = (int) Math.ceil((double) availableBooks.size() / booksPerPage);
if (page > totalPages) {
sender.sendMessage(ChatColor.GRAY + "No such page");
BooksWithoutBorders.getStringFormatter().displayErrorMessage(sender, Formatting.NEUTRAL_AUTHOR_BOOKS_INVALID_PAGE);
} else {
showBookMenu(sender, command, page, totalPages, availableBooks, firstInstances);
showBookMenu(sender, command, page, totalPages, availableBooks, firstInstances, listPublic);
}
}
@@ -57,19 +60,15 @@ public class PagedBookIndex extends BookIndex {
* @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>
* @param listPublic <p>Whether to display public books</p>
*/
private static void showBookMenu(@NotNull CommandSender sender, @NotNull String command, int page,
int totalPages, @NotNull List<String> availableBooks,
@NotNull Map<Character, Integer> firstInstances) {
@NotNull Map<Character, Integer> firstInstances, boolean listPublic) {
ComponentBuilder componentBuilder = new ComponentBuilder();
componentBuilder.append("--- ");
if (command.toLowerCase().contains("public")) {
componentBuilder.append("Publicly saved books").color(ChatColor.GREEN);
} else {
componentBuilder.append("Your saved books").color(ChatColor.GREEN);
}
componentBuilder.append(" ---", ComponentBuilder.FormatRetention.NONE).append("\n");
TranslatableMessage message = listPublic ? Formatting.NEUTRAL_BOOK_LIST_PUBLIC_BOOKS_HEADER : Formatting.NEUTRAL_BOOK_LIST_PLAYER_BOOKS_HEADER;
componentBuilder.append(color(message));
displayBookList(componentBuilder, command, page, availableBooks);
displayPreviousButton(componentBuilder, command, page);
@@ -94,24 +93,23 @@ public class PagedBookIndex extends BookIndex {
@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")));
String title = color(BookFileHelper.getBookTitleFromPath(availableBooks.get(bookIndex)));
String author = color(BookFileHelper.getBookAuthorFromPath(availableBooks.get(bookIndex)));
ClickEvent indexClick = new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/" + command + " " + (bookIndex + 1));
HoverEvent indexHover = new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text(color(Formatting.NEUTRAL_BOOK_LIST_BOOK_INDEX_HOVER)));
ClickEvent pathClick = new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/" + command + " " + availableBooks.get(bookIndex));
HoverEvent pathHover = new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text(color(Formatting.NEUTRAL_BOOK_LIST_PATH_HOVER)));
ClickEvent authorClick = new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/" + command + " author" + BookFormatter.stripColor(author) + " page1");
HoverEvent authorHover = new HoverEvent(HoverEvent.Action.SHOW_TEXT,
new Text(color(Formatting.NEUTRAL_BOOK_LIST_AUTHOR_HOVER, "{author}", BookFormatter.stripColor(author))));
componentBuilder.append(color(Formatting.NEUTRAL_BOOK_LIST_BOOK_INDEX_NUMBER, "{index}",
String.valueOf(bookIndex + 1))).color(interactColor).event(indexClick).event(indexHover);
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(title).color(ChatColor.WHITE).event(pathClick).event(pathHover);
componentBuilder.append(color(Formatting.NEUTRAL_BOOK_LIST_AUTHOR_SEPARATOR), ComponentBuilder.FormatRetention.NONE).color(ChatColor.WHITE);
componentBuilder.append(author).color(ChatColor.WHITE).event(authorClick).event(authorHover);
componentBuilder.append("\n");
}
}

View File

@@ -3,6 +3,7 @@ package net.knarcraft.bookswithoutborders.utility;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.state.BookHoldingState;
import net.knarcraft.bookswithoutborders.state.ItemSlot;
import net.knarcraft.knarlib.formatting.StringFormatter;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
@@ -79,16 +80,17 @@ public final class InventoryHelper {
*/
public static boolean notHoldingOneWritableBookCheck(@NotNull Player player, @NotNull String noBookMessage,
@NotNull String twoBooksMessage) {
StringFormatter stringFormatter = BooksWithoutBorders.getStringFormatter();
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);
stringFormatter.displayErrorMessage(player, noBookMessage);
return true;
}
if (holdingState == BookHoldingState.UNSIGNED_BOTH_HANDS) {
BooksWithoutBorders.sendErrorMessage(player, twoBooksMessage);
stringFormatter.displayErrorMessage(player, twoBooksMessage);
return true;
}

View File

@@ -23,6 +23,7 @@ en:
SUCCESS_BOOKSHELF_LORE_SET: "Lore successfully saved!"
SUCCESS_GENERATION_CHANGED: "Book generation successfully changed!"
SUCCESS_LORE_SET: "Added lore to item!"
SUCCESS_TITLE_SET: "Title set to {title}!"
ACTION_COPY: "copy"
ACTION_CLEAR: "clear"
ACTION_DECRYPT: "decrypt"
@@ -30,6 +31,7 @@ en:
ACTION_ADD_TITLE_AUTHOR_PAGE: "add an author title page to"
ACTION_SET_AUTHOR: "set author"
ACTION_CHANGE_GENERATION: "change generation"
ACTION_UNSIGN: "unsign"
ERROR_PLAYER_ONLY: "This command can only be used by a player!"
ERROR_NOT_HOLDING_WRITTEN_BOOK: "You must be holding a written book to {action} it!"
ERROR_NOT_HOLDING_WRITABLE_BOOK: "You must be holding a writable book to {action} it!"
@@ -86,8 +88,12 @@ en:
ERROR_GENERATION_NOT_SPECIFIED: "You must specify the new generation for your book!"
ERROR_GENERATION_INVALID: "Invalid book generation specified!"
ERROR_LORE_EMPTY: "You must specify the new lore to set!"
ERROR_LORE_NO_ITEM: "Must be holding an item to set lore!"
ERROR_NO_ITEM: "You must be holding an item to use this command!"
ERROR_TITLE_EMPTY: "You must specify the new title/display name to set!"
ERROR_TITLE_LENGTH: "Book titles are capped at 32 characters!"
# ---------------------------------- #
# Custom formatting for some output #
# ---------------------------------- #
NEUTRAL_COMMANDS_HEADER: |
&nBooks without Borders help page&r
&e[] = optional, <> = required (see each description for exceptions)
@@ -100,8 +106,28 @@ en:
NEUTRAL_COMMANDS_COMMAND_NO_PERMISSION_REQUIRED: "None"
NEUTRAL_COMMANDS_COMMAND_PERMISSION: " &7{{permission}}"
NEUTRAL_COMMANDS_ALIASES: " &f(&b{aliases}&f)"
NEUTRAL_UNKNOWN_AUTHOR: "Unknown"
NEUTRAL_UNKNOWN_TITLE: "Untitled"
NEUTRAL_TITLE_PAGE_TITLE_AUTHOR_FORMAT: "{title}{separator}By: {author}"
NEUTRAL_TITLE_PAGE_HEADER_FORMAT: "\n&n&l{header}&r"
NEUTRAL_TITLE_PAGE_TEXT_FORMAT: "\n\n&o{text}&r"
NEUTRAL_TITLE_PAGE_TEXT_FORMAT: "\n\n&o{text}&r"
NEUTRAL_AUTHOR_PUBLIC_BOOKS_HEADER: "--- &aPublicly saved books by: &b{author}&r ---\n"
NEUTRAL_AUTHOR_PLAYER_BOOKS_HEADER: "--- &aYour saved books by: &b{author}&r ---\n"
NEUTRAL_AUTHOR_BOOKS_PATH: "Select book by path"
NEUTRAL_AUTHOR_BOOKS_INVALID_PAGE: "&7No such page"
NEUTRAL_BOOK_LIST_TOTAL_PAGES: "Page {current} of {total}"
NEUTRAL_BOOK_LIST_PREVIOUS_PAGE: "[<] Previous"
NEUTRAL_BOOK_LIST_NEXT_PAGE: "Next [>]"
NEUTRAL_BOOK_LIST_AUTHOR_SEPARATOR: "&r by "
NEUTRAL_BOOK_LIST_TO_PAGE: "To page {page}"
NEUTRAL_BOOK_LIST_INDEX_HOVER: "Books starting with {character}"
NEUTRAL_BOOK_LIST_PUBLIC_BOOKS_HEADER: "--- &aPublicly saved books&r ---\n"
NEUTRAL_BOOK_LIST_PLAYER_BOOKS_HEADER: "--- &aYour saved books&r ---\n"
NEUTRAL_BOOK_LIST_AUTHOR_HOVER: "Books by {author}"
NEUTRAL_BOOK_LIST_PATH_HOVER: "Select book by path"
NEUTRAL_BOOK_LIST_BOOK_INDEX_HOVER: "Select book by index"
NEUTRAL_BOOK_LIST_BOOK_INDEX_NUMBER: "[{index}]"
# -----------------------------------------#
# Translations of unknown/untitled author. #
# Altering this might cause problems. #
# -----------------------------------------#
NEUTRAL_UNKNOWN_AUTHOR: "Unknown"
NEUTRAL_UNKNOWN_TITLE: "Untitled"