Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ mvn package

## Version history

- 1.11 - spawnEntity, setDirection, setRotation, setPitch
- 1.11 - spawnEntity, setSign, setDirection, setRotation, setPitch
- 1.10.1 - bug fixes
- 1.10 - left, right, both hit clicks added to config.yml & fixed minor hit events bug
- 1.9.1 - minor change to improve connection reset
Expand Down
233 changes: 232 additions & 1 deletion src/main/java/net/zhuoweizhang/raspberryjuice/RemoteSession.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
package net.zhuoweizhang.raspberryjuice;

import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.*;
import java.util.*;

import org.bukkit.*;
import org.bukkit.entity.*;
import org.bukkit.block.*;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.BookMeta;
import org.bukkit.inventory.meta.BookMeta.Generation;

import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

import org.bukkit.event.player.AsyncPlayerChatEvent;
import org.bukkit.util.Vector;

Expand Down Expand Up @@ -485,11 +497,25 @@ protected void handleCommand(String c, String[] args) {
if ( thisBlock.getState() instanceof Sign ) {
Sign sign = (Sign) thisBlock.getState();
for ( int i = 5; i-5 < 4 && i < args.length; i++) {
sign.setLine(i-5, args[i]);
sign.setLine(i-5, unescape(args[i]));
}
sign.update();
}

// world.addBookToChest
} else if (c.equals("world.addBookToChest")) {
Location loc = parseRelativeBlockLocation(args[0], args[1], args[2]);
Block thisBlock = world.getBlockAt(loc);
BlockState thisBlockState = thisBlock.getState();
if ( thisBlockState instanceof InventoryHolder) {
InventoryHolder chest = (InventoryHolder) thisBlockState;
ItemStack book = createBookFromJson(unescape(args[3]));
chest.getInventory().addItem(book);
} else {
plugin.getLogger().info("addBook needs location of chest or other InventoryHolder");
send("Fail");
}

// world.spawnEntity
} else if (c.equals("world.spawnEntity")) {
Location loc = parseRelativeBlockLocation(args[0], args[1], args[2]);
Expand Down Expand Up @@ -522,6 +548,17 @@ protected void handleCommand(String c, String[] args) {

}
}

public String unescape(String s) {
if ( s == null ) return null;
s = s.replace("&#10;", "\n");
s = s.replace("&#40;", "(");
s = s.replace("&#41;", ")");
s = s.replace("&#44;", ",");
s = s.replace("&sect;", "§");
s = s.replace("&amp;", "&");
return s;
}

// create a cuboid of lots of blocks
private void setCuboid(Location pos1, Location pos2, int blockType, byte data) {
Expand Down Expand Up @@ -777,5 +814,199 @@ public static int blockFaceToNotch(BlockFace face) {
return 7; // Good as anything here, but technically invalid
}
}

/**
* Creates a WRITTEN_BOOK from JSON string including interactivity such as clicks and hovers.
* JSON format same as used by "/give" command without need to escape double quotes
*
* Inspired by https://github.com/upperlevel/spigot-book-api/blob/master/src/main/java/xyz/upperlevel/spigot/book/NmsBookHelper.java
*
* @author Tim Cummings
* @param json - JSON string used to define a book
* @return the book as an ItemStack
*/
public ItemStack createBookFromJson(String json) {
BookMeta meta = (BookMeta) Bukkit.getItemFactory().getItemMeta(Material.WRITTEN_BOOK);
JsonObject pyBook = new JsonParser().parse(json).getAsJsonObject();
JsonElement pyTitle = pyBook.get("title");
JsonElement pyAuthor = pyBook.get("author");
JsonElement pyPages = pyBook.get("pages");
JsonElement pyDisplay = pyBook.get("display");
JsonElement pyGeneration = pyBook.get("generation");
if (pyTitle != null) {
try {
meta.setTitle(pyTitle.getAsString());
} catch (ClassCastException e) {
plugin.getLogger().info("Book title can't be got as string because it is not JsonPrimitive. Its JSON is " + pyTitle.toString());
e.printStackTrace();
} catch (IllegalStateException e) {
plugin.getLogger().info("Book title can't be got as string because it is a multiple element array. Its JSON is " + pyTitle.toString());
e.printStackTrace();
}
}
if (pyAuthor != null) {
try {
meta.setAuthor(pyAuthor.getAsString());
} catch (ClassCastException e) {
plugin.getLogger().info("Book author can't be got as string because it is not JsonPrimitive. Its JSON is " + pyAuthor.toString());
e.printStackTrace();
} catch (IllegalStateException e) {
plugin.getLogger().info("Book author can't be got as string because it is a multiple element array. Its JSON is " + pyAuthor.toString());
e.printStackTrace();
}
}
if (pyDisplay != null) {
JsonElement pyDisplayName = null;
JsonElement pyDisplayLore = null;
if ( pyDisplay.isJsonObject() ) {
pyDisplayName = pyDisplay.getAsJsonObject().get("Name");
pyDisplayLore = pyDisplay.getAsJsonObject().get("Lore");
}
if (pyDisplayName != null) {
try {
meta.setDisplayName(pyDisplayName.getAsString());
} catch (ClassCastException e) {
plugin.getLogger().info("Book display name can't be got as string because it is not JsonPrimitive. Its JSON is " + pyDisplayName.toString());
e.printStackTrace();
} catch (IllegalStateException e) {
plugin.getLogger().info("Book display name can't be got as string because it is a multiple element array. Its JSON is " + pyDisplayName.toString());
e.printStackTrace();
}
}
if (pyDisplayLore != null) {
List<String> listLore = new ArrayList<String>();
if (pyDisplayLore.isJsonArray()) {
for (JsonElement je : pyDisplayLore.getAsJsonArray()) {
try {
listLore.add(je.getAsString());
} catch (ClassCastException e) {
plugin.getLogger().info("Book display lore item can't be got as string because it is not JsonPrimitive. Its JSON is " + je.toString());
e.printStackTrace();
} catch (IllegalStateException e) {
plugin.getLogger().info("Book display lore item can't be got as string because it is a multiple element array. Its JSON is " + je.toString());
e.printStackTrace();
}
}
} else {
try {
listLore.add(pyDisplayLore.getAsString());
} catch (ClassCastException e) {
plugin.getLogger().info("Book display lore can't be got as string because it is not JsonPrimitive. Really it should be JsonArray but if not we try this. Its JSON is " + pyDisplayLore.toString());
e.printStackTrace();
} catch (IllegalStateException e) {
plugin.getLogger().info("Book display lore can't be got as string because it is a multiple element array. This should never happen because we have already checked it is not a JsonArray. Its JSON is " + pyDisplayLore.toString());
e.printStackTrace();
}
}
}
}
if (pyGeneration != null) {
try {
int g = pyGeneration.getAsInt();
Generation[] ga = Generation.values();
if ( g >= 0 && g < ga.length ) {
meta.setGeneration(ga[g]);
}
} catch (ClassCastException e) {
plugin.getLogger().info("Book generation item can't be got as int because it is not JsonPrimitive of int. Its JSON is " + pyGeneration.toString());
e.printStackTrace();
} catch (IllegalStateException e) {
plugin.getLogger().info("Book generation item can't be got as int because it is a multiple element array rather than an int. Its JSON is " + pyGeneration.toString());
e.printStackTrace();
}
}
String version = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
Class<?> craftMetaBookClass = null;
Field craftMetaBookField = null;
String strCraftMetaBook = "org.bukkit.craftbukkit." + version + ".inventory.CraftMetaBook";
try {
craftMetaBookClass = Class.forName(strCraftMetaBook);
} catch (ClassNotFoundException e) {
plugin.getLogger().warning("Can't get class " + strCraftMetaBook + " required to get pages of book that we want to modify");
e.printStackTrace();
}
if (craftMetaBookClass != null ) {
try {
craftMetaBookField = craftMetaBookClass.getDeclaredField("pages");
craftMetaBookField.setAccessible(true);
} catch (NoSuchFieldException e) {
plugin.getLogger().info("Field 'pages' missing from class " + strCraftMetaBook + " required to get pages of book we want to modify");
e.printStackTrace();
} catch (SecurityException se) {
plugin.getLogger().warning("Security exception getting field 'pages' from class " + strCraftMetaBook + " required to get pages of book we want to modify");
se.printStackTrace();
}
}
Class<?> chatSerializer = null;
String strChatSerializer1 = "net.minecraft.server." + version + ".IChatBaseComponent$ChatSerializer";
String strChatSerializer2 = "net.minecraft.server." + version + ".ChatSerializer";
String strChatSerializer = null;
try {
chatSerializer = Class.forName(strChatSerializer1);
strChatSerializer = strChatSerializer1;
} catch(ClassNotFoundException e) {
plugin.getLogger().info("Can't find class " + strChatSerializer1 + ". Will try " + strChatSerializer2);
e.printStackTrace();
}
if ( chatSerializer == null ) {
try {
chatSerializer = Class.forName(strChatSerializer2);
strChatSerializer = strChatSerializer2;
} catch(ClassNotFoundException e) {
plugin.getLogger().warning("Can't find classes " + strChatSerializer1 + " or " + strChatSerializer2 + " needed to convert JSON to formatted interactive text in books");
e.printStackTrace();
}
}
Method chatSerializerA = null;
if ( chatSerializer != null ) {
try {
chatSerializerA = chatSerializer.getDeclaredMethod("a", String.class);
} catch (NoSuchMethodException e) {
plugin.getLogger().warning("Class " + strChatSerializer + " does not have method a() required to convert JSON to formatted interactive text in books");
e.printStackTrace();
} catch (SecurityException se) {
plugin.getLogger().warning("Security exception getting declared method a() from " + strChatSerializer);
se.printStackTrace();
}
}
List<Object> pages = null;
//get the pages if required for json formatting
if (craftMetaBookField != null ) {
try {
@SuppressWarnings("unchecked")
List<Object> lo = (List<Object>) craftMetaBookField.get(meta);
pages = lo;
} catch (ReflectiveOperationException ex) {
plugin.getLogger().warning("Reflection exception getting pages from book using " + strCraftMetaBook + ".pages");
ex.printStackTrace();
}
}
if (pyPages.isJsonArray()) {
for (JsonElement jePage : pyPages.getAsJsonArray()) {
String page = jePage.toString();
//plugin.getLogger().info(page);
if (chatSerializerA != null && pages != null) {
try {
pages.add(chatSerializerA.invoke(null, page));
} catch (IllegalAccessException e) {
plugin.getLogger().warning("IllegalAccessException invoking method " + strChatSerializer + ".a() using reflection");
e.printStackTrace();
} catch (IllegalArgumentException e) {
plugin.getLogger().warning("IllegalArgumentException invoking method " + strChatSerializer + ".a() using reflection");
e.printStackTrace();
} catch (InvocationTargetException e) {
plugin.getLogger().warning("InvocationTargetException invoking method " + strChatSerializer + ".a() using reflection");
e.printStackTrace();
}
} else {
//something wrong with reflection methods so just add raw text to book
meta.addPage(page);
}
}
}
ItemStack book = new ItemStack(Material.WRITTEN_BOOK);
book.setItemMeta(meta);
return book;
}

}
110 changes: 110 additions & 0 deletions src/main/resources/mcpi/api/python/modded/mcpi/book.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import json

class Book:
'''Minecraft PI Book description. Can be sent to Minecraft.addBookToChest(x,y,z,book)

Book maintains a dictionary which describes the book and then converts it to a
json string when sent to the minecraft server.
Create a book from a dictionary

b = mcpi.book.Book({"title":"Python", "author":"RaspberryJuice"})

or from a JSON string

b = mcpi.book.Book.fromJson('{"title":"Python", "author":"RaspberryJuice"}')

Pages can be added at creation time or added later one page at a time from a string

b.addPageOfText("Some text")

or from an array of dictionaries

b.addPage('[{"text":"Some text"}]')

Display parameters can be set at creation time or later from a dictionary

b.setDisplay({"Name":"My display name"})

@author: Tim Cummings https://www.triptera.com.au/wordpress/'''

def __init__(self, book):
"""creates a book from dictionary. All keys in dictionary are optional but values should be correct type

Example:
b = mcpi.book.Book({"title":"Python", "author":"RaspberryJuice", "pages":[]})"""
self.book = book

def __eq__(self, other):
return ((self.book) == (other.book))

def __ne__(self, other):
return ((self.book) != (other.book))

def __lt__(self, other):
return ((self.book) < (other.book))

def __le__(self, other):
return ((self.book) <= (other.book))

def __gt__(self, other):
return ((self.book) > (other.book))

def __ge__(self, other):
return ((self.book) >= (other.book))

def __iter__(self):
'''Allows a Book to be used in flatten()'''
return iter((json.dumps(self.book),))

def __repr__(self):
return "Book.fromJson('{}')".format(json.dumps(self.book))

def __str__(self):
return json.dumps(self.book)

@staticmethod
def fromJson(jsonstr):
"""creates book from JSON string. All attributes are optional but should be correct type

Example:
b = mcpi.book.Book.fromJson('{"title":"Json book","author":"RaspberryJuice","generation":0,"pages":[]}')"""
return Book(json.loads(jsonstr))

def addPageOfText(self,text):
"""adds a new page of plain unformatted text to book

New page is added after all existing pages
Example:
book.addPageOfText("My text on the page.\n\nNewlines can be added. Can't have formatting (color, bold, italic. etc) or interactivity (clicks or hovers)")"""
self.addPage([{"text":text}])

def addPage(self, page):
"""adds a new page using an array of dictionaries describing all the text on the new page

New page is added after all existing pages
Example:
addPage([
{"text":'New "(page)"\nThis text is black ', "color":"reset"},
{"text":"and this text is red, and bold.\n\n", "color":"red", "bold":True},
{"text":"Hover or click the following\n"},
{"text":"\nRunning a command\n", "underlined":True, "color":"blue",
"hoverEvent":{"action":"show_text","value":"runs command to set daytime"},
"clickEvent":{"action":"run_command", "value":"/time set day"}},
{"text":"\nOpening a URL\n", "underlined":True, "color":"blue",
"hoverEvent":{"action":"show_text","value":"opens url to RaspberryJuice"},
"clickEvent":{"action":"open_url","value":"https://github.com/zhuowei/RaspberryJuice"}},
{"text":"\nGoing to a page", "underlined":True, "color":"blue",
"hoverEvent":{"action":"show_text","value":"goes to page 1"},
"clickEvent":{"action":"change_page","value":1}}
])"""
if 'pages' not in self.book:
self.book['pages'] = []
self.book['pages'].append(page)

def setDisplay(self, display):
"""sets display name and lore and any other display parameters from a dictionary

Replaces any previously set display
Example:
b.setDisplay({"Name":"My display string", "Lore":["An array of strings","describing lore"]})"""
self.book['display'] = display
Loading