This plugin generates Minecraft books from text files on your server.
Your can use it to create rule books for your server that your players can obtain, or whatever else warps your starship. You create one or more text files in your Minecraft folder similar to:
book(bookname).txt
Where bookname can be any name, but cannot contain spaces (not even on systems that allow filenames to contain spaces) because the player has to be able to type the book name as a single word to the /BOOK command. If you name a book file book(my story).txt the player will type /BOOK MY STORY and the plugin will try to load book(my).txt, so don't put spaces in filenames!
Your players can type /BOOK BOOKNAME to load any book into their inventory. The book command is not case sensitive, so they can type /book bookname or /BOOK BOOKNAME or /BoOk BoOkNaMe to get the book.
If a player types just /BOOK it will list all available books; that is, all files that follow the standard: book(*).txt. And if no books exist, the plugin will create an example book for you to edit.
The book file is just a text file that you can edit with Notepad. Do not use a word processor like MS Word or Wordpad! The book file must be a pure text file.
A book file looks like this:
^T Book Title
^A Author Name
^C Chapter Title
^P Paragraph
^P Another paragraph
^P Yet another paragraph
^C Another Chapter Title
^P Paragraph
^P Another paragraph
^P Yet another paragraph
^B A line break
The book title is the title of the book in Minecraft. The plugin will create a "title page" for your book containing the title, author, and date, just like a real book. Each chapter title forces a new page. If a chapter exceeds the page, it will start a new page automagically. Each paragraph forces a blank line and starts on a new line.
You can insert funky characters using the percent % symbol like
this: %25 or %5E. This is how you could type a % or a ^ into the text of your book.
If you want to add features and fix bugs, go ahead. I included the Java source for just that reason.
Code (Text):
package FredashaySpigotBook;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.sql.Date;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import org.bukkit.Material;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory;
import org.bukkit.inventory.meta.BookMeta;
import org.bukkit.plugin.PluginDescriptionFile;
import org.bukkit.plugin.java.JavaPlugin;
public class MyPlugin extends JavaPlugin implements Listener, CommandExecutor {
private static Logger logger = null;
private static PluginDescriptionFile pdfFile = null;
final static char CRLF = (char) 0x0a;
final static int pageWidth = 114;
final static int PageLines = 13;
final static int pageChars = 240;
@Override
public void onEnable() {
logger = Logger.getLogger("Minecraft");
pdfFile = getDescription();
getServer().getPluginManager().registerEvents(this,this);
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
String fileName = null;
String bookText = null;
String title = null;
String author = null;
long fileDate = 0;
String bookPages[] = null;
File dir = null;
File files[] = null;
File file = null;
int index = 0;
int books = 0;
if (label.equalsIgnoreCase("book")) {
if (!(sender instanceof Player)) {
sender.sendMessage("[" + pdfFile.getName() + "] This command can only be issued by a player in the game. ");
return (false);
}
Player player = (Player) sender;
if (args.length > 0) {
fileName = getFileName(args[0].toLowerCase().trim());
if (fileName == null) {
player.sendMessage("<BOOK> Oops! Book, '" + args[0] + "' does not exist. ");
}
else {
bookText = readBook(fileName, player);
bookText = scanBook(bookText);
fileDate = getFileDate(fileName);
title = getTitle(bookText);
author = getAuthor(bookText);
bookPages = formatBook(bookText, fileDate);
ItemStack myBook = createBook(title, author, bookPages);
giveItemToPlayer(player, myBook, title);
}
}
else {
dir = new File(".");
files = dir.listFiles();
index = 0;
books = 0;
while (index < files.length) {
file = files[index];
if (file.isFile()) {
fileName = file.getName();
if ((fileName.startsWith("book(")) && (fileName.endsWith(").txt"))) {
if (fileName.indexOf('(') + 1 < fileName.indexOf(')')) {
if (!fileName.contains(" ")) {
if (books == 0) {
player.sendMessage("<BOOK> The following books are available: ");
}
books = books + 1;
title = fileName.substring(fileName.indexOf('(') + 1, fileName.indexOf(')'));
player.sendMessage(" " + title);
}
}
}
}
index = index + 1;
}
if (books == 0) {
writeExampleBook();
player.sendMessage("<BOOK> No books are available. I created an example book 'Example' for you to use to start making your own books. Look in your server folder for it. You can edit it with Notepad or vi. ");
}
}
return (true);
}
return (false);
}
private String getFileName(String bookName) {
String fileName = null;
String searchName = null;
File dir = null;
File files[] = null;
File file = null;
int index = 0;
fileName = null;
dir = new File(".");
files = dir.listFiles();
index = 0;
searchName = "";
while (index < files.length) {
file = files[index];
if (file.isFile()) {
searchName = file.getName();
if (searchName.equalsIgnoreCase("BOOK(" + bookName + ").TXT")) {
fileName = searchName;
}
}
index = index + 1;
}
return(fileName);
}
private String readBook(String fileName, Player plr) {
File bookFile = null;
BufferedReader reader = null;
String recordBuffer = null;
String fullText = null;
bookFile = new File(fileName);
if (bookFile.exists()) {
try {
fullText = "";
reader = new BufferedReader(new FileReader(fileName));
recordBuffer = reader.readLine();
while (recordBuffer != null) {
if (recordBuffer.length() > 0) {
fullText = fullText.trim() + " " + recordBuffer.trim();
}
recordBuffer = reader.readLine();
}
}
catch (IOException oops) {
plr.sendMessage("<BOOK> Oops! There's a problem with file, '" + fileName + "'. ");
fullText = null;
}
finally {
try {
reader.close();
}
catch (IOException oops) {
plr.sendMessage("<BOOK> Oops! There's a problem with file, '" + fileName + "'. ");
fullText = null;
}
}
}
else {
plr.sendMessage("<BOOK> Oops! There's a problem with file, '" + fileName + "'. ");
fullText = null;
}
return (fullText);
}
private String scanBook (String bookIn) {
String bookOut = "";
char bookChar = ' ';
int index = 0;
while (index < bookIn.length()) {
bookChar = bookIn.charAt(index);
if (bookChar == '"') {
bookOut = bookOut + "\\" + bookChar;
}
else if (bookChar == '\\') {
bookOut = bookOut + "\\" + bookChar;
}
else {
bookOut = bookOut + bookChar;
}
index = index + 1;
}
return (bookOut);
}
private long getFileDate(String fileName) {
File bookFile = null;
long fileDate = 0;
bookFile = new File(fileName);
fileDate = 0;
if (bookFile.exists()) {
fileDate = bookFile.lastModified();
}
return(fileDate);
}
private String getTitle(String fullText) {
String wordArray[] = null;
String word = null;
int wordIndex = 0;
String code = null;
char mode = ' ';
String title = null;
wordArray = fullText.split("\\s\\s*");
wordIndex = 0;
title = "";
while (wordIndex < wordArray.length) {
word = wordArray[wordIndex].trim();
if (word.startsWith("^")) {
if (word.length() > 1) {
code = word.substring(1, 2);
if (code.equalsIgnoreCase("T")) {
title = "";
mode = 'T';
wordArray[wordIndex] = "";
}
else {
mode = ' ';
}
}
}
else if (mode == 'T') {
if (title.length() == 0) {
title = word;
}
else {
title = title + " " + word;
}
wordArray[wordIndex] = "";
}
wordIndex = wordIndex + 1;
}
return (title);
}
private String getAuthor(String fullText) {
String wordArray[] = null;
String word = null;
int wordIndex = 0;
String code = null;
char mode = ' ';
String author = null;
wordArray = fullText.split("\\s\\s*");
wordIndex = 0;
author = "";
while (wordIndex < wordArray.length) {
word = wordArray[wordIndex].trim();
if (word.startsWith("^")) {
if (word.length() > 1) {
code = word.substring(1, 2);
if (code.equalsIgnoreCase("A")) {
author = "";
mode = 'A';
wordArray[wordIndex] = "";
}
else {
mode = ' ';
}
}
}
else if (mode == 'A') {
if (author.length() == 0) {
author = word;
}
else {
author = author + " " + word;
}
wordArray[wordIndex] = "";
}
wordIndex = wordIndex + 1;
}
return (author);
}
private String[] formatBook(String fullText, long bookDate) {
String wordArray[] = null;
String word = null;
int wordIndex = 0;
String code = null;
char mode = ' ';
String title = null;
String author = null;
String pages[] = null;
String titleWords[] = null;
int titleIndex = 0;
String authorWords[] = null;
int authorIndex = 0;
int fillerNeeded = 0;
String by = "by";
boolean newPage = false;
int currentPage = 0;
int lineCount = 0;
int lineWidth = 0;
int nextWidth = 0;
SimpleDateFormat sdf = null;
String prettyDate = null;
Date myDate = null;
int arrayLength = 0;
int controlIndex = 0;
wordArray = fullText.split("\\s\\s*");
wordIndex = 0;
title = "";
author = "";
while (wordIndex < wordArray.length) {
word = wordArray[wordIndex].trim();
if (word.startsWith("^")) {
if (word.length() > 1) {
code = word.substring(1, 2);
if (code.equalsIgnoreCase("T")) {
title = "";
mode = 'T';
wordArray[wordIndex] = "";
}
else if (code.equalsIgnoreCase("A")) {
author = "";
mode = 'A';
wordArray[wordIndex] = "";
}
else {
mode = ' ';
}
}
}
else if (mode == 'T') {
if (title.length() == 0) {
title = word;
}
else {
title = title + " " + word;
}
wordArray[wordIndex] = "";
}
else if (mode == 'A') {
if (author.length() == 0) {
author = word;
}
else {
author = author + " " + word;
}
wordArray[wordIndex] = "";
}
wordIndex = wordIndex + 1;
}
wordIndex = 0;
pages = new String[1];
pages[0] = " " + CRLF + CRLF;
if (textWidth(title) <= pageWidth) {
fillerNeeded = pageWidth - textWidth(title);
pages[0] = pages[0] + spaces(fillerNeeded / 2) + title + CRLF;
}
else {
titleWords = title.split(" ");
titleIndex = 0;
while (titleIndex < titleWords.length) {
fillerNeeded = pageWidth - textWidth(titleWords[titleIndex]);
pages[0] = pages[0] + spaces(fillerNeeded / 2) + titleWords[titleIndex] + CRLF;
titleIndex = titleIndex + 1;
}
}
if (author.length() > 0) {
pages[0] = pages[0] + CRLF;
fillerNeeded = pageWidth - textWidth(by);
pages[0] = pages[0] + spaces(fillerNeeded / 2) + by + CRLF;
if (author.length() <= pageWidth) {
fillerNeeded = pageWidth - textWidth(author);
pages[0] = pages[0] + spaces(fillerNeeded / 2) + author + CRLF;
}
else {
authorWords = author.split(" ");
authorIndex = 0;
while (authorIndex < authorWords.length) {
fillerNeeded = pageWidth - textWidth(authorWords[authorIndex]);
pages[0] = pages[0] + spaces(fillerNeeded / 2) + authorWords[authorIndex] + CRLF;
authorIndex = authorIndex + 1;
}
}
}
if (bookDate > 0) {
pages[0] = pages[0] + CRLF;
myDate = new java.sql.Date(bookDate);
sdf = new SimpleDateFormat("dd-MMM-yyyy");
prettyDate = sdf.format(myDate);
fillerNeeded = pageWidth - textWidth(prettyDate);
pages[0] = pages[0] + spaces(fillerNeeded / 2) + prettyDate;
}
pages = createNewPage(pages);
newPage = true;
currentPage = pages.length - 1;
lineCount = 1;
wordIndex = 0;
while (wordIndex < wordArray.length) {
controlIndex = wordIndex;
if (textWidth(wordArray[wordIndex]) > pageWidth) {
arrayLength = wordArray[wordIndex].length();
while (textWidth(subString(wordArray[wordIndex], 0, arrayLength)) > pageWidth) {
arrayLength = arrayLength - 1;
}
arrayLength = arrayLength - 1;
word = subString(wordArray[wordIndex], 0, arrayLength) + "-";
wordArray[wordIndex] = subString(wordArray[wordIndex], arrayLength, wordArray[wordIndex].length() - arrayLength);
}
else {
word = wordArray[wordIndex].trim();
wordIndex = wordIndex + 1;
}
if (word.length() > 0) {
if (word.startsWith("^")) {
if (word.length() > 1) {
code = word.substring(1, 2);
if (code.equalsIgnoreCase("C")) {
mode = 'C';
if (!newPage) {
pages = createNewPage(pages);
newPage = true;
lineWidth = 0;
lineCount = 1;
currentPage = pages.length - 1;
}
}
else if (code.equalsIgnoreCase("P")) {
pages[currentPage] = pages[currentPage] + CRLF + CRLF;
lineWidth = 0;
lineCount = lineCount + 2;
}
else if (code.equalsIgnoreCase("B")) {
pages[currentPage] = pages[currentPage] + CRLF;
lineWidth = 0;
lineCount = lineCount + 1;
}
else {
mode = ' ';
}
}
else {
mode = ' ';
}
wordArray[controlIndex] = "";
}
else {
if (word.contains("%")) {
word = hexDecode(word);
}
nextWidth = textWidth(word) + textWidth(" ");
if (lineWidth + nextWidth > pageWidth) {
while ((pages[currentPage].endsWith(" ")) && (pages[currentPage].length() > 1)) {
pages[currentPage] = subString(pages[currentPage], 0, pages[currentPage].length() - 1);
}
pages[currentPage] = pages[currentPage] + CRLF;
lineWidth = 0;
lineCount = lineCount + 1;
}
if (lineCount > PageLines) {
pages = createNewPage(pages);
newPage = true;
currentPage = pages.length - 1;
lineWidth = 0;
lineCount = 1;
}
else if (pages[currentPage].length() >= (pageChars - 4)) {
pages = createNewPage(pages);
newPage = true;
currentPage = pages.length - 1;
lineWidth = 0;
lineCount = 1;
}
pages[currentPage] = pages[currentPage] + word + " ";
lineWidth = lineWidth + nextWidth;
newPage = false;
}
}
}
return (pages);
}
private String spaces(int filler) {
String spaces = "";
while (filler > 0) {
spaces = spaces + " ";
filler = filler - 4;
}
return (spaces);
}
private int textWidth(String word) {
int count = 0;
int index = 0;
char myChar = ' ';
count = 0;
index = 0;
while (index < word.length()) {
myChar = word.charAt(index);
if (myChar == 'I') {
count = count + 4;
}
else if (myChar == 'i') {
count = count + 2;
}
else if (myChar == 'l') {
count = count + 3;
}
else if (myChar == '+') {
count = count + 3;
}
else if (myChar == 'f') {
count = count + 5;
}
else if (myChar == 'k') {
count = count + 2;
}
else if (myChar == '@') {
count = count + 7;
}
else if (myChar == '~') {
count = count + 7;
}
else if (myChar == ' ') {
count = count + 4;
}
else if (myChar == ',') {
count = count + 2;
}
else if (myChar == '.') {
count = count + 2;
}
else if (myChar == '|') {
count = count + 2;
}
else if (myChar == ':') {
count = count + 2;
}
else if (myChar == ';') {
count = count + 2;
}
else if (myChar == '!') {
count = count + 2;
}
else if (myChar == '`') {
count = count + 3;
}
else if (myChar == '\'') {
count = count + 3;
}
else if (myChar == '"') {
count = count + 5;
}
else if (myChar == '*') {
count = count + 5;
}
else if (myChar == '(') {
count = count + 5;
}
else if (myChar == ')') {
count = count + 5;
}
else if (myChar == '{') {
count = count + 5;
}
else if (myChar == '}') {
count = count + 5;
}
else if (myChar == '[') {
count = count + 4;
}
else if (myChar == ']') {
count = count + 4;
}
else {
count = count + 6;
}
index = index + 1;
}
return(count);
}
private String hexDecode(String word) {
int index = 0;
String hexString = null;
char funkyChar = ' ';
index = 0;
while (index < word.length()) {
if ((word.charAt(index) == '%') && (word.length() >= (index + 3))) {
hexString = subString(word, index + 1, 2);
funkyChar = (char)Integer.parseInt(hexString, 16);
word = subString(word, 0, index) + funkyChar + subString(word, (index + 3), (word.length() - (index + 3)));
}
index = index + 1;
}
return(word);
}
private String subString(String stringIn, int start, int length) {
int stringLength = 0;
stringLength = start + length;
if (stringLength > stringIn.length()) {
stringLength = stringIn.length();
}
String stringOut = stringIn.substring(start, stringLength);
return(stringOut);
}
private String[] createNewPage(String[] oldPages) {
int newPage = oldPages.length + 1;
String newPages[] = new String[newPage];
int pageIndex = 0;
pageIndex = 0;
while (pageIndex < oldPages.length) {
newPages[pageIndex] = oldPages[pageIndex];
pageIndex = pageIndex + 1;
}
newPages[pageIndex] = "";
return (newPages);
}
private ItemStack createBook(String title, String author, String page[]) {
ItemStack myBook = new ItemStack(Material.WRITTEN_BOOK);
BookMeta bookData = (BookMeta) myBook.getItemMeta();
bookData.setTitle(title);
bookData.setAuthor(author);
int pageIx = 0;
List<String> bookPages = new ArrayList<String>();
if (page.length > 0) {
pageIx = 0;
while (pageIx < page.length) {
bookPages.add(pageIx, page[pageIx]);
pageIx = pageIx + 1;
}
}
bookData.setPages(bookPages);
myBook.setItemMeta(bookData);
return (myBook);
}
private void giveItemToPlayer(Player player, ItemStack item, String title) {
PlayerInventory inventoryList = player.getInventory();
if (inventoryList.getItemInMainHand().getAmount() == 0) {
inventoryList.setItemInMainHand(item);
player.updateInventory();
player.sendMessage("<BOOK> You now have a copy of '" + title + "'. Happy reading! ");
}
else if (inventoryList.getItemInOffHand().getAmount() == 0) {
inventoryList.setItemInOffHand(item);
player.updateInventory();
player.sendMessage("<BOOK> You now have a copy of '" + title + "'. Happy reading! ");
}
else {
boolean searching = true;
int invIx = 0;
while (searching) {
if (invIx >= inventoryList.getSize()) {
player.sendMessage("<BOOK> You have no empty inventory slots. ");
searching = false;
}
else {
if (inventoryList.getItem(invIx) == null) {
inventoryList.setItem(invIx, item);
player.updateInventory();
player.sendMessage("<BOOK> You now have a copy of '" + title + "' in your inventory. Happy reading! ");
searching = false;
}
}
invIx = invIx + 1;
}
}
}
private void writeExampleBook() {
String bookFilename = null;
FileOutputStream fileStream = null;
File bookFile = null;
bookFilename = "book(Example).txt";
bookFile = new File(bookFilename);
try {
bookFile.delete();
fileStream = new FileOutputStream(bookFile);
comfortablyNumb (fileStream, "^T Example Book Title ");
comfortablyNumb (fileStream, "^A William Shakesword ");
comfortablyNumb (fileStream, "^C BOOK FILENAME");
comfortablyNumb (fileStream, "^P This book is saved on your Minecraft folder as 'book(Example).txt'.");
comfortablyNumb (fileStream, "^P All book files must follow this convention: 'book(bookname).txt'.");
comfortablyNumb (fileStream, "^P The name of the book within the parentheses must be a single word, no spaces. ");
comfortablyNumb (fileStream, "^P The name of the book within the parentheses can be mixed case. ");
comfortablyNumb (fileStream, "^C BOOK STRUCTURE");
comfortablyNumb (fileStream, "^P You can edit your books on the disk with Notepad. ");
comfortablyNumb (fileStream, "^P Do not use a word processor like MS Word or Wordpad. The book text must be plain text. You can use Notepad or vi. ");
comfortablyNumb (fileStream, "^P Control marks are a caret followed by a letter and a space. ");
comfortablyNumb (fileStream, "^P The text title is what the title of the book will be in Minecraft, not the filename title. ");
comfortablyNumb (fileStream, "^P Each chapter heading forces a new page. Each paragraph forces a blank line. ");
comfortablyNumb (fileStream, "^P To see what I mean, compare this book in Minecraft to its text file. ");
comfortablyNumb (fileStream, "^P Then change it around with Notepad to see what effect it has. ");
comfortablyNumb (fileStream, "^P There's no limit to how many pages a book can have. ");
comfortablyNumb (fileStream, "^C \"T\" must be the first tag on the first line and is the title of the book.");
comfortablyNumb (fileStream, "^C \"A\" must be the second tag on the second and is the author of the book.");
comfortablyNumb (fileStream, "^C \"C\" creates a new page.");
comfortablyNumb (fileStream, "^P \"P\" creates a paragraph.");
comfortablyNumb (fileStream, "^B \"B\" creates a new line without a blank line.");
}
catch (IOException oops) {
logger.info("[" + pdfFile.getName() + "] We could not write the example book to a file, oh my! ");
oops.printStackTrace(System.err);
}
finally {
try {
fileStream.close();
}
catch (IOException oops) {
oops.printStackTrace(System.err);
}
}
}
private void comfortablyNumb(FileOutputStream fileStream, String output) throws IOException {
String EOL = System.getProperty("line.separator");
byte[] data = null;
output = output + EOL;
data = output.getBytes();
fileStream.write(data, 0, data.length);
return;
}
// public void doNothing() { }
}