From 94d23551bf9856a899049f0aa6bb2b58a5c81338 Mon Sep 17 00:00:00 2001 From: D Tim Cummings Date: Thu, 31 Aug 2017 14:18:16 +1000 Subject: [PATCH 1/5] Allow json to be used for pages in book --- .../raspberryjuice/RemoteSession.java | 113 +++++++++++++++++- .../mcpi/api/python/modded/mcpi/minecraft.py | 17 ++- .../mcpi/api/python/modded/mcpi/util.py | 12 ++ 3 files changed, 137 insertions(+), 5 deletions(-) diff --git a/src/main/java/net/zhuoweizhang/raspberryjuice/RemoteSession.java b/src/main/java/net/zhuoweizhang/raspberryjuice/RemoteSession.java index c5762166..fd2e44e7 100644 --- a/src/main/java/net/zhuoweizhang/raspberryjuice/RemoteSession.java +++ b/src/main/java/net/zhuoweizhang/raspberryjuice/RemoteSession.java @@ -1,6 +1,9 @@ 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.*; @@ -8,7 +11,10 @@ import org.bukkit.entity.*; import org.bukkit.block.*; import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.event.entity.EntityTargetLivingEntityEvent; +import org.bukkit.inventory.InventoryHolder; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.BookMeta; + import org.bukkit.event.player.AsyncPlayerChatEvent; public class RemoteSession { @@ -442,7 +448,7 @@ protected void handleCommand(String c, String[] args) { send("Fail"); } - // world.setSign Author: Tim Cummings https://www.triptera.com.au/wordpress/ + // world.setSign Author: Tim Cummings https://www.triptera.com.au/wordpress/ } else if (c.equals("world.setSign")) { Location loc = parseRelativeBlockLocation(args[0], args[1], args[2]); Block thisBlock = world.getBlockAt(loc); @@ -458,11 +464,101 @@ 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 Author: Tim Cummings https://www.triptera.com.au/wordpress/ + } 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; + BookMeta meta = (BookMeta) Bukkit.getItemFactory().getItemMeta(Material.WRITTEN_BOOK); + meta.setTitle(args[3]); + meta.setAuthor(args[4]); + //Borrowed code from https://github.com/upperlevel/spigot-book-api/blob/master/src/main/java/xyz/upperlevel/spigot/book/NmsBookHelper.java + //Required if json sent from python + String version = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3]; + Class craftMetaBookClass = null; + Field craftMetaBookField = null; + try { + craftMetaBookClass = Class.forName("org.bukkit.craftbukkit." + version + ".inventory.CraftMetaBook"); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + if (craftMetaBookClass != null ) { + try { + craftMetaBookField = craftMetaBookClass.getDeclaredField("pages"); + craftMetaBookField.setAccessible(true); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } catch (SecurityException se) { + se.printStackTrace(); + } + } + Class chatSerializer = null; + try { + chatSerializer = Class.forName("net.minecraft.server." + version + ".IChatBaseComponent$ChatSerializer"); + } catch(ClassNotFoundException e) { + e.printStackTrace(); + } + if ( chatSerializer == null ) { + try { + chatSerializer = Class.forName("net.minecraft.server." + version + ".ChatSerializer"); + } catch(ClassNotFoundException e) { + e.printStackTrace(); + } + } + Method chatSerializerA = null; + if ( chatSerializer != null ) { + try { + chatSerializerA = chatSerializer.getDeclaredMethod("a", String.class); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } catch (SecurityException se) { + se.printStackTrace(); + } + } + List pages = null; + //get the pages if required for json formatting + if (craftMetaBookField != null ) { + try { + @SuppressWarnings("unchecked") + List lo = (List) craftMetaBookField.get(meta); + pages = lo; + } catch (ReflectiveOperationException ex) { + ex.printStackTrace(); + } + } + //end of borrowed code from https://github.com/upperlevel/spigot-book-api + + for (int i = 5; i < args.length; i++) { + String page = unescape(args[i]); + if (chatSerializerA != null && pages != null && (page.startsWith("[") || page.startsWith("{"))) { + try { + pages.add(chatSerializerA.invoke(null, page)); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } + } else { + meta.addPage(page); + } + } + ItemStack book = new ItemStack(Material.WRITTEN_BOOK); + book.setItemMeta(meta); + chest.getInventory().addItem(book); + } else { + plugin.getLogger().info("addBook needs location of chest or other InventoryHolder"); + send("Fail"); + } + // world.spawnEntity Author: pxai (edited by Tim Cummings) } else if (c.equals("world.spawnEntity")) { Location loc = parseRelativeBlockLocation(args[0], args[1], args[2]); @@ -490,6 +586,17 @@ protected void handleCommand(String c, String[] args) { } } + + public String unescape(String s) { + if ( s == null ) return null; + s = s.replace(" ", "\n"); + s = s.replace("(", "("); + s = s.replace(")", ")"); + s = s.replace(",", ","); + s = s.replace("§", "§"); + s = s.replace("&", "&"); + return s; + } // create a cuboid of lots of blocks private void setCuboid(Location pos1, Location pos2, int blockType, byte data) { diff --git a/src/main/resources/mcpi/api/python/modded/mcpi/minecraft.py b/src/main/resources/mcpi/api/python/modded/mcpi/minecraft.py index c3880290..43bf5a02 100644 --- a/src/main/resources/mcpi/api/python/modded/mcpi/minecraft.py +++ b/src/main/resources/mcpi/api/python/modded/mcpi/minecraft.py @@ -3,7 +3,7 @@ from .event import BlockEvent, ChatEvent from .block import Block import math -from .util import flatten +from .util import flatten, escape """ Minecraft PI low level api v0.1_1 @@ -189,8 +189,21 @@ def setSign(self, *args): for arg in flatten(args): flatargs.append(arg) for flatarg in flatargs[5:]: - lines.append(flatarg.replace(",",";").replace(")","]").replace("(","[")) + lines.append(escape(flatarg)) self.conn.send(b"world.setSign",intFloor(flatargs[0:5]) + lines) + + def addBookToChest(self, *args): + """Add a book to a chest (x,y,z,title,author,pages[]) + + The location x,y,z must contain a chest or other Inventory Holder + @author: Tim Cummings https://www.triptera.com.au/wordpress/""" + bookmeta = [] + flatargs = [] + for arg in flatten(args): + flatargs.append(arg) + for flatarg in flatargs[3:]: + bookmeta.append(escape(flatarg)) + self.conn.send(b"world.addBookToChest",intFloor(flatargs[0:3]) + bookmeta) def spawnEntity(self, *args): """Spawn entity (x,y,z,id,[data])""" diff --git a/src/main/resources/mcpi/api/python/modded/mcpi/util.py b/src/main/resources/mcpi/api/python/modded/mcpi/util.py index 9791072e..e7d016f9 100644 --- a/src/main/resources/mcpi/api/python/modded/mcpi/util.py +++ b/src/main/resources/mcpi/api/python/modded/mcpi/util.py @@ -16,3 +16,15 @@ def _misc_to_bytes(m): See `Connection.send` for more details. """ return str(m).encode("cp437") + +def escape(s): + """Escape content of strings which will break the api using html entity type escaping""" + s = s.replace("&","&") + s = s.replace("\r\n"," ") + s = s.replace("\n"," ") + s = s.replace("\r"," ") + s = s.replace("(","(") + s = s.replace(")",")") + s = s.replace(",",",") + s = s.replace("§","§") + return s From b0118acb40889f38acbd366df692cfae80fe76b0 Mon Sep 17 00:00:00 2001 From: D Tim Cummings Date: Fri, 1 Sep 2017 12:53:54 +1000 Subject: [PATCH 2/5] Use JSON to create formatted interactive books Escape parentheses, commas, section breaks, ampersands and new lines using & type html entities. This now works with signs and books. Java plugin unescapes these characters so now they are preserved rather than translated. Add test for book creation Make entity test more compact so quicker --- .../raspberryjuice/RemoteSession.java | 248 ++++++++++++------ .../mcpi/api/python/modded/mcpi/book.py | 110 ++++++++ .../mcpi/api/python/modded/mcpi/minecraft.py | 3 +- src/main/resources/test/Test.py | 79 ++++-- src/main/resources/test/modded/mcpi/book.py | 110 ++++++++ .../resources/test/modded/mcpi/minecraft.py | 18 +- src/main/resources/test/modded/mcpi/util.py | 12 + 7 files changed, 483 insertions(+), 97 deletions(-) create mode 100644 src/main/resources/mcpi/api/python/modded/mcpi/book.py create mode 100644 src/main/resources/test/modded/mcpi/book.py diff --git a/src/main/java/net/zhuoweizhang/raspberryjuice/RemoteSession.java b/src/main/java/net/zhuoweizhang/raspberryjuice/RemoteSession.java index fd2e44e7..687bd93b 100644 --- a/src/main/java/net/zhuoweizhang/raspberryjuice/RemoteSession.java +++ b/src/main/java/net/zhuoweizhang/raspberryjuice/RemoteSession.java @@ -14,6 +14,11 @@ 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; @@ -476,83 +481,7 @@ protected void handleCommand(String c, String[] args) { BlockState thisBlockState = thisBlock.getState(); if ( thisBlockState instanceof InventoryHolder) { InventoryHolder chest = (InventoryHolder) thisBlockState; - BookMeta meta = (BookMeta) Bukkit.getItemFactory().getItemMeta(Material.WRITTEN_BOOK); - meta.setTitle(args[3]); - meta.setAuthor(args[4]); - //Borrowed code from https://github.com/upperlevel/spigot-book-api/blob/master/src/main/java/xyz/upperlevel/spigot/book/NmsBookHelper.java - //Required if json sent from python - String version = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3]; - Class craftMetaBookClass = null; - Field craftMetaBookField = null; - try { - craftMetaBookClass = Class.forName("org.bukkit.craftbukkit." + version + ".inventory.CraftMetaBook"); - } catch (ClassNotFoundException e) { - e.printStackTrace(); - } - if (craftMetaBookClass != null ) { - try { - craftMetaBookField = craftMetaBookClass.getDeclaredField("pages"); - craftMetaBookField.setAccessible(true); - } catch (NoSuchFieldException e) { - e.printStackTrace(); - } catch (SecurityException se) { - se.printStackTrace(); - } - } - Class chatSerializer = null; - try { - chatSerializer = Class.forName("net.minecraft.server." + version + ".IChatBaseComponent$ChatSerializer"); - } catch(ClassNotFoundException e) { - e.printStackTrace(); - } - if ( chatSerializer == null ) { - try { - chatSerializer = Class.forName("net.minecraft.server." + version + ".ChatSerializer"); - } catch(ClassNotFoundException e) { - e.printStackTrace(); - } - } - Method chatSerializerA = null; - if ( chatSerializer != null ) { - try { - chatSerializerA = chatSerializer.getDeclaredMethod("a", String.class); - } catch (NoSuchMethodException e) { - e.printStackTrace(); - } catch (SecurityException se) { - se.printStackTrace(); - } - } - List pages = null; - //get the pages if required for json formatting - if (craftMetaBookField != null ) { - try { - @SuppressWarnings("unchecked") - List lo = (List) craftMetaBookField.get(meta); - pages = lo; - } catch (ReflectiveOperationException ex) { - ex.printStackTrace(); - } - } - //end of borrowed code from https://github.com/upperlevel/spigot-book-api - - for (int i = 5; i < args.length; i++) { - String page = unescape(args[i]); - if (chatSerializerA != null && pages != null && (page.startsWith("[") || page.startsWith("{"))) { - try { - pages.add(chatSerializerA.invoke(null, page)); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } catch (IllegalArgumentException e) { - e.printStackTrace(); - } catch (InvocationTargetException e) { - e.printStackTrace(); - } - } else { - meta.addPage(page); - } - } - ItemStack book = new ItemStack(Material.WRITTEN_BOOK); - book.setItemMeta(meta); + ItemStack book = createBookFromJson(unescape(args[3])); chest.getInventory().addItem(book); } else { plugin.getLogger().info("addBook needs location of chest or other InventoryHolder"); @@ -857,5 +786,170 @@ 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) { + e.printStackTrace(); + } catch (IllegalStateException e) { + e.printStackTrace(); + } + } + if (pyAuthor != null) { + try { + meta.setAuthor(pyAuthor.getAsString()); + } catch (ClassCastException e) { + e.printStackTrace(); + } catch (IllegalStateException e) { + 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) { + e.printStackTrace(); + } catch (IllegalStateException e) { + e.printStackTrace(); + } + } + if (pyDisplayLore != null) { + List listLore = new ArrayList(); + if (pyDisplayLore.isJsonArray()) { + for (JsonElement je : pyDisplayLore.getAsJsonArray()) { + try { + listLore.add(je.getAsString()); + } catch (ClassCastException e) { + e.printStackTrace(); + } catch (IllegalStateException e) { + e.printStackTrace(); + } + } + } else { + try { + listLore.add(pyDisplayLore.getAsString()); + } catch (ClassCastException e) { + e.printStackTrace(); + } catch (IllegalStateException e) { + 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) { + e.printStackTrace(); + } catch (IllegalStateException e) { + e.printStackTrace(); + } + } + String version = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3]; + Class craftMetaBookClass = null; + Field craftMetaBookField = null; + try { + craftMetaBookClass = Class.forName("org.bukkit.craftbukkit." + version + ".inventory.CraftMetaBook"); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + if (craftMetaBookClass != null ) { + try { + craftMetaBookField = craftMetaBookClass.getDeclaredField("pages"); + craftMetaBookField.setAccessible(true); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } catch (SecurityException se) { + se.printStackTrace(); + } + } + Class chatSerializer = null; + try { + chatSerializer = Class.forName("net.minecraft.server." + version + ".IChatBaseComponent$ChatSerializer"); + } catch(ClassNotFoundException e) { + e.printStackTrace(); + } + if ( chatSerializer == null ) { + try { + chatSerializer = Class.forName("net.minecraft.server." + version + ".ChatSerializer"); + } catch(ClassNotFoundException e) { + e.printStackTrace(); + } + } + Method chatSerializerA = null; + if ( chatSerializer != null ) { + try { + chatSerializerA = chatSerializer.getDeclaredMethod("a", String.class); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } catch (SecurityException se) { + se.printStackTrace(); + } + } + List pages = null; + //get the pages if required for json formatting + if (craftMetaBookField != null ) { + try { + @SuppressWarnings("unchecked") + List lo = (List) craftMetaBookField.get(meta); + pages = lo; + } catch (ReflectiveOperationException ex) { + 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) { + e.printStackTrace(); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + 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; + } } diff --git a/src/main/resources/mcpi/api/python/modded/mcpi/book.py b/src/main/resources/mcpi/api/python/modded/mcpi/book.py new file mode 100644 index 00000000..2b50e1f0 --- /dev/null +++ b/src/main/resources/mcpi/api/python/modded/mcpi/book.py @@ -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 diff --git a/src/main/resources/mcpi/api/python/modded/mcpi/minecraft.py b/src/main/resources/mcpi/api/python/modded/mcpi/minecraft.py index 43bf5a02..6be03d7f 100644 --- a/src/main/resources/mcpi/api/python/modded/mcpi/minecraft.py +++ b/src/main/resources/mcpi/api/python/modded/mcpi/minecraft.py @@ -193,9 +193,10 @@ def setSign(self, *args): self.conn.send(b"world.setSign",intFloor(flatargs[0:5]) + lines) def addBookToChest(self, *args): - """Add a book to a chest (x,y,z,title,author,pages[]) + """Add a book to a chest (x,y,z,book) The location x,y,z must contain a chest or other Inventory Holder + book is a JSON string or mcpi.book.Book object describing the book @author: Tim Cummings https://www.triptera.com.au/wordpress/""" bookmeta = [] flatargs = [] diff --git a/src/main/resources/test/Test.py b/src/main/resources/test/Test.py index c524304c..59b5eb42 100644 --- a/src/main/resources/test/Test.py +++ b/src/main/resources/test/Test.py @@ -7,6 +7,7 @@ import original.mcpi.block as block import modded.mcpi.block as blockmodded import modded.mcpi.entity as entitymodded +import modded.mcpi.book as bookmodded import time import math @@ -459,7 +460,7 @@ def runEntityTests(mc): giants=["ELDER_GUARDIAN","GUARDIAN","GIANT","ENDER_DRAGON","GHAST"] cavers=["MAGMA_CUBE","BAT","PARROT","CHICKEN","STRAY","SKELETON","SPIDER","ZOMBIE","SLIME","CAVE_SPIDER","PIG_ZOMBIE","ENDERMAN","SNOWMAN","SILVERFISH","ILLUSIONER"] bosses=["WITHER"] - # location for platform showing all block types + # location for platform showing all entities xtest = 50 ytest = 100 ztest = 50 @@ -475,8 +476,8 @@ def runEntityTests(mc): rail=blockmodded.RAIL wallsignid=blockmodded.SIGN_WALL.id mc.postToChat("runEntityTests(): Creating test entities at x=" + str(xtest) + " y=" + str(ytest) + " z=" + str(ztest)) - mc.setBlocks(xtest,ytest-1,ztest,xtest+100,ytest+50,ztest+100,air) - mc.setBlocks(xtest,ytest-1,ztest,xtest+100,ytest-1,ztest+50,floor) + mc.setBlocks(xtest,ytest,ztest,xtest+70,ytest+50,ztest+50,air) + mc.setBlocks(xtest,ytest-1,ztest,xtest+70,ytest-1,ztest+50,floor) mc.postToChat("Dancing villager") r = 10 @@ -607,10 +608,10 @@ def runEntityTests(mc): time.sleep(0.1) for key in giants: e = getattr(entitymodded,key) - mc.setBlocks(x,y,z,x+20,y+20,z+20,wall) - mc.setBlocks(x,y+21,z,x+20,y+21,z+20,roof) + mc.setBlocks(x,y,z,x+20,y+10,z+20,wall) + mc.setBlocks(x,y+11,z,x+20,y+11,z+20,roof) mc.setBlocks(x-5,y-1,z-1,x+20,y-1,z+21,floor) - mc.setBlocks(x+1,y,z+1,x+19,y+20,z+19,air) + mc.setBlocks(x+1,y,z+1,x+19,y+10,z+19,air) mc.setSign(x-1,y,z+2,sign,key,"id=" + str(e.id)) mc.spawnEntity(x+10,y+5,z+10,e) untested.discard(e.id) @@ -621,15 +622,15 @@ def runEntityTests(mc): time.sleep(0.1) for key in bosses: e = getattr(entitymodded,key) - mc.setBlocks(x,y,z,x+20,y+20,z+20,blockmodded.BEDROCK) - mc.setBlocks(x,y+21,z,x+20,y+21,z+20,blockmodded.BEDROCK) + mc.setBlocks(x,y,z,x+20,y+10,z+20,blockmodded.BEDROCK) + mc.setBlocks(x,y+11,z,x+20,y+11,z+20,blockmodded.BEDROCK) mc.setBlocks(x-5,y-1,z-1,x+20,y-1,z+21,blockmodded.BEDROCK) - mc.setBlocks(x+1,y,z+1,x+19,y+20,z+19,air) + mc.setBlocks(x+1,y,z+1,x+19,y+10,z+19,air) mc.setBlocks(x+1,y,z+8,x+19,y,z+12,torch) - mc.setBlocks(x+1,y+10,z+2,x+1,y+15,z+18,torch.id,1) - mc.setBlocks(x+19,y+10,z+2,x+19,y+15,z+18,torch.id,2) - mc.setBlocks(x+1,y+10,z+19,x+19,y+15,z+19,torch.id,4) - mc.setBlocks(x+1,y+10,z+1,x+19,y+15,z+1,torch.id,3) + mc.setBlocks(x+1,y+7,z+2,x+1,y+10,z+18,torch.id,1) + mc.setBlocks(x+19,y+7,z+2,x+19,y+10,z+18,torch.id,2) + mc.setBlocks(x+1,y+7,z+19,x+19,y+10,z+19,torch.id,4) + mc.setBlocks(x+1,y+7,z+1,x+19,y+10,z+1,torch.id,3) mc.setBlocks(x,y,z+9,x+3,y+3,z+11,blockmodded.BEDROCK) mc.setBlocks(x+4,y,z+9,x+19,y+3,z+11,wall) mc.setBlocks(x,y,z+10,x+19,y+2,z+10,air) @@ -643,12 +644,12 @@ def runEntityTests(mc): time.sleep(0.1) for key in sinks: e = getattr(entitymodded,key) - mc.setBlocks(x,y,z,x+20,y+20,z+20,wall) - mc.setBlocks(x,y+21,z,x+20,y+21,z+20,roof) + mc.setBlocks(x,y,z,x+20,y+10,z+20,wall) + mc.setBlocks(x,y+11,z,x+20,y+11,z+20,roof) mc.setBlocks(x-5,y-1,z-1,x+20,y-1,z+21,floor) - mc.setBlocks(x+1,y,z+1,x+19,y+20,z+19,blockmodded.WATER_STATIONARY) + mc.setBlocks(x+1,y,z+1,x+19,y+10,z+19,blockmodded.WATER_STATIONARY) mc.setSign(x-1,y,z+2,sign,key,"id=" + str(e.id)) - mc.spawnEntity(x+10,y,z+10,e) + mc.spawnEntity(x+10,y+5,z+10,e) untested.discard(e.id) z += 20 if z > 80: @@ -665,6 +666,49 @@ def runEntityTests(mc): mc.postToChat("/kill @e[type=!player]") mc.postToChat("to remove test entities") +def runBookTests(mc): + """runBookTests - tests creation of book in a chest + + Author: Tim Cummings https://www.triptera.com.au/wordpress/ + """ + # location for chest containing book + xtest = 48 + ytest = 100 + ztest = 48 + mc.postToChat("runBookTests(): Creating chest with book at x=" + str(xtest) + " y=" + str(ytest) + " z=" + str(ztest)) + b = bookmodded.Book({"title":"RaspberryJuice Test", "author":"Tim Cummings"}) + b.addPageOfText('This page should be unformatted text. ' + 'Please turn to page 2 to see formatted interactive text.\n\n' + 'Test escapes\n' + 'Comma ,\n' + 'Parentheses ()\n' + 'Double quotes " "\n' + "Single quotes ' '\n" + 'Section §\n' + 'Ampersand &') + page = [ + {"text":'This text is black ', "color":"reset"}, + {"text":"and this text is red and bold. ", "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\n", "underlined":True, "color":"blue", + "hoverEvent":{"action":"show_text","value":"goes to page 1"}, + "clickEvent":{"action":"change_page","value":1}}, + {"text":"\nCleanup\n", "underlined":True, "color":"blue", + "hoverEvent":{"action":"show_text","value":"kills all non player entities after entity test"}, + "clickEvent":{"action":"run_command", "value":"/kill @e[type=!player]"}} + ] + b.addPage(page) + b.setDisplay({"Name":"Test book", "Lore":["First line of lore","Second line of lore"]}) + mc.setBlock(xtest,ytest,ztest,blockmodded.CHEST) + mc.addBookToChest(xtest,ytest,ztest,b) + + def runTests(mc, library="Standard library", extended=False): @@ -763,6 +807,7 @@ def runTests(mc, library="Standard library", extended=False): if extended: runBlockTests(mc) runEntityTests(mc) + runBookTests(mc) mc.postToChat("Tests complete for " + library) diff --git a/src/main/resources/test/modded/mcpi/book.py b/src/main/resources/test/modded/mcpi/book.py new file mode 100644 index 00000000..2b50e1f0 --- /dev/null +++ b/src/main/resources/test/modded/mcpi/book.py @@ -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 diff --git a/src/main/resources/test/modded/mcpi/minecraft.py b/src/main/resources/test/modded/mcpi/minecraft.py index c3880290..6be03d7f 100644 --- a/src/main/resources/test/modded/mcpi/minecraft.py +++ b/src/main/resources/test/modded/mcpi/minecraft.py @@ -3,7 +3,7 @@ from .event import BlockEvent, ChatEvent from .block import Block import math -from .util import flatten +from .util import flatten, escape """ Minecraft PI low level api v0.1_1 @@ -189,8 +189,22 @@ def setSign(self, *args): for arg in flatten(args): flatargs.append(arg) for flatarg in flatargs[5:]: - lines.append(flatarg.replace(",",";").replace(")","]").replace("(","[")) + lines.append(escape(flatarg)) self.conn.send(b"world.setSign",intFloor(flatargs[0:5]) + lines) + + def addBookToChest(self, *args): + """Add a book to a chest (x,y,z,book) + + The location x,y,z must contain a chest or other Inventory Holder + book is a JSON string or mcpi.book.Book object describing the book + @author: Tim Cummings https://www.triptera.com.au/wordpress/""" + bookmeta = [] + flatargs = [] + for arg in flatten(args): + flatargs.append(arg) + for flatarg in flatargs[3:]: + bookmeta.append(escape(flatarg)) + self.conn.send(b"world.addBookToChest",intFloor(flatargs[0:3]) + bookmeta) def spawnEntity(self, *args): """Spawn entity (x,y,z,id,[data])""" diff --git a/src/main/resources/test/modded/mcpi/util.py b/src/main/resources/test/modded/mcpi/util.py index 9791072e..e7d016f9 100644 --- a/src/main/resources/test/modded/mcpi/util.py +++ b/src/main/resources/test/modded/mcpi/util.py @@ -16,3 +16,15 @@ def _misc_to_bytes(m): See `Connection.send` for more details. """ return str(m).encode("cp437") + +def escape(s): + """Escape content of strings which will break the api using html entity type escaping""" + s = s.replace("&","&") + s = s.replace("\r\n"," ") + s = s.replace("\n"," ") + s = s.replace("\r"," ") + s = s.replace("(","(") + s = s.replace(")",")") + s = s.replace(",",",") + s = s.replace("§","§") + return s From cb6a952c459240a48955be7eb0d60851db27b602 Mon Sep 17 00:00:00 2001 From: D Tim Cummings Date: Sat, 2 Sep 2017 15:58:39 +1000 Subject: [PATCH 3/5] Improve logging during exception handling of book creation --- .../raspberryjuice/RemoteSession.java | 35 +++++++++++++++++-- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/zhuoweizhang/raspberryjuice/RemoteSession.java b/src/main/java/net/zhuoweizhang/raspberryjuice/RemoteSession.java index 687bd93b..b0a58699 100644 --- a/src/main/java/net/zhuoweizhang/raspberryjuice/RemoteSession.java +++ b/src/main/java/net/zhuoweizhang/raspberryjuice/RemoteSession.java @@ -809,8 +809,10 @@ public ItemStack createBookFromJson(String json) { 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(); } } @@ -818,8 +820,10 @@ public ItemStack createBookFromJson(String json) { 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(); } } @@ -834,8 +838,10 @@ public ItemStack createBookFromJson(String json) { 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(); } } @@ -846,8 +852,10 @@ public ItemStack createBookFromJson(String json) { 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(); } } @@ -855,8 +863,10 @@ public ItemStack createBookFromJson(String json) { 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(); } } @@ -870,17 +880,21 @@ public ItemStack createBookFromJson(String json) { 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("org.bukkit.craftbukkit." + version + ".inventory.CraftMetaBook"); + 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 ) { @@ -888,21 +902,30 @@ public ItemStack createBookFromJson(String json) { 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("net.minecraft.server." + version + ".IChatBaseComponent$ChatSerializer"); + 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("net.minecraft.server." + version + ".ChatSerializer"); + 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(); } } @@ -911,8 +934,10 @@ public ItemStack createBookFromJson(String json) { 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(); } } @@ -924,6 +949,7 @@ public ItemStack createBookFromJson(String json) { List lo = (List) craftMetaBookField.get(meta); pages = lo; } catch (ReflectiveOperationException ex) { + plugin.getLogger().warning("Reflection exception getting pages from book using " + strCraftMetaBook + ".pages"); ex.printStackTrace(); } } @@ -935,10 +961,13 @@ public ItemStack createBookFromJson(String json) { 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 { From fa040a768306ba0505e70aed88567485441248e9 Mon Sep 17 00:00:00 2001 From: Martin O'Hanlon Date: Wed, 1 Nov 2017 20:07:18 +0000 Subject: [PATCH 4/5] updated readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4737db3f..09ac7536 100644 --- a/README.md +++ b/README.md @@ -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 From c45a410e3853ba7e497d5b9940bc3984e7e413ad Mon Sep 17 00:00:00 2001 From: D Tim Cummings Date: Wed, 8 Nov 2017 22:35:51 +1000 Subject: [PATCH 5/5] Fix position and time issues with tests Position at y=100 to avoid damaging user's constructions at y=50 Make test position parameters of function Make possible to choose which tests to run Make clearArea clear complete area Show progress when clearing area in case already clear Show progress when running block test in case already created Keep times slow to avoid server crash Split stairs from inverted stairs to make clearer Speed up dancing villager which shouldn't crash server Allow entity test to run consecutively without crashing placing hangers Reduce size of squid pool to reduce load on server Reduce size of entity test platform Update print(listOfBlocks) for python 3 --- src/main/resources/test/Test.py | 231 +++++++++++++++++++------------- 1 file changed, 140 insertions(+), 91 deletions(-) diff --git a/src/main/resources/test/Test.py b/src/main/resources/test/Test.py index 84fa4611..fd97b7cf 100644 --- a/src/main/resources/test/Test.py +++ b/src/main/resources/test/Test.py @@ -11,13 +11,21 @@ import time import math -def runBlockTests(mc): +def clearTestArea(mc, xtest=0, ytest=100, ztest=0): + "clear the area in segments, otherwise it breaks the server, glowstone shows progress if area already clear" + mc.setBlocks(xtest,ytest-1,ztest,xtest+150,ytest-1,ztest,blockmodded.GLOWSTONE_BLOCK) + for x_inc in range(151): + mc.setBlocks(xtest+x_inc,ytest-2,ztest,xtest+x_inc,ytest+25,ztest+100,blockmodded.AIR) + time.sleep(1) + +def runBlockTests(mc, xtest=0, ytest=100, ztest=0): """runBlockTests - tests creation of all blocks for all data values known to RaspberryJuice A sign is placed next to the created block so user can view in Minecraft whether block created correctly or not + xtest, ytest, ztest are coordinates where tests should start. Known issues: - id for NETHER_REACTOR_CORE and GLOWING_OBSIDIAN wrong - - some LEAVES missing but because they decay by the time user sees them + - some LEAVES missing because they decay by the time user sees them - this test doesn't try activation of TNT Author: Tim Cummings https://www.triptera.com.au/wordpress/ @@ -54,9 +62,6 @@ def runBlockTests(mc): mushrooms=["MUSHROOM_BROWN","MUSHROOM_RED"] # location for platform showing all block types - xtest = 0 - ytest = 50 - ztest = 0 mc.postToChat("runBlockTests(): Creating test blocks at x=" + str(xtest) + " y=" + str(ytest) + " z=" + str(ztest)) # create set of all block ids to ensure they all get tested # note some blocks have different names but same ids so they only have to be tested once per id @@ -82,22 +87,26 @@ def runBlockTests(mc): sign=blockmodded.SIGN_STANDING.withData(12) signid=sign.id - x=xtest - y=ytest-1 - z=ztest - mc.setBlocks(x,y,z,x+100,y,z+100,blockmodded.STONE) - time.sleep(1) - #clear the area in segments, otherwise it breaks the server - #clearing area - #mc.setBlocks(x,y+1,z,x+100,y+50,z+100,blockmodded.AIR) - for y_inc in range(1, 10): - mc.setBlocks(x,y+y_inc,z,x+100,y+y_inc,z+100,blockmodded.AIR) - time.sleep(2) mc.player.setTilePos(xtest, ytest, ztest) + mc.player.setRotation(-45) + + #setup a stone area in segments as a platform for the block and book tests with moving glowstone to measure progress" + for x_inc in range(5): + mc.setBlocks(xtest+x_inc*10,ytest-1,ztest,xtest+(x_inc+1)*10,ytest-1,ztest+100,blockmodded.STONE) + mc.setBlock(xtest+x_inc*10,ytest,ztest,blockmodded.GLOWSTONE_BLOCK) + time.sleep(1) + mc.setBlock(xtest+x_inc*10,ytest,ztest,blockmodded.AIR) + for x_inc in range(5): + mc.setBlocks(xtest+50+x_inc*20,ytest-1,ztest,xtest+50+(x_inc+1)*20,ytest-1,ztest+50,blockmodded.STONE) + mc.setBlock(xtest+50+x_inc*20,ytest,ztest,blockmodded.GLOWSTONE_BLOCK) + time.sleep(1) + mc.setBlock(xtest+50+x_inc*20,ytest,ztest,blockmodded.AIR) + time.sleep(1) x=xtest+10 y=ytest z=ztest+10 + mc.setBlock(xtest+10,ytest,ztest+5,blockmodded.GLOWSTONE_BLOCK) for key in solids + gases + flats + fences: b = getattr(blockmodded,key) z += 1 @@ -109,6 +118,8 @@ def runBlockTests(mc): time.sleep(1) x=xtest+20 z=ztest+10 + mc.setBlock(xtest+10,ytest,ztest+5,blockmodded.AIR) + mc.setBlock(xtest+20,ytest,ztest+5,blockmodded.GLOWSTONE_BLOCK) for key in trees: for data in range(16): b = getattr(blockmodded,key).withData(data) @@ -182,6 +193,8 @@ def runBlockTests(mc): time.sleep(1) x=xtest+30 z=ztest+10 + mc.setBlock(xtest+20,ytest,ztest+5,blockmodded.AIR) + mc.setBlock(xtest+30,ytest,ztest+5,blockmodded.GLOWSTONE_BLOCK) for key in coloureds: for data in range(16): b = getattr(blockmodded,key).withData(data) @@ -233,6 +246,8 @@ def runBlockTests(mc): time.sleep(1) x=xtest+40 z=ztest+10 + mc.setBlock(xtest+30,ytest,ztest+5,blockmodded.AIR) + mc.setBlock(xtest+40,ytest,ztest+5,blockmodded.GLOWSTONE_BLOCK) for key in liquids: z += 1 mc.setBlocks(x-1,y,z,x+1,y,z+10,blockmodded.STONE) @@ -260,6 +275,8 @@ def runBlockTests(mc): time.sleep(1) x=xtest+50 z=ztest+10 + mc.setBlock(xtest+40,ytest,ztest+5,blockmodded.AIR) + mc.setBlock(xtest+50,ytest,ztest+5,blockmodded.GLOWSTONE_BLOCK) for key in slabs: for data in range(8): b = getattr(blockmodded,key).withData(data) @@ -273,6 +290,8 @@ def runBlockTests(mc): x=xtest+60 y=ytest z=ztest+10 + mc.setBlock(xtest+50,ytest,ztest+5,blockmodded.AIR) + mc.setBlock(xtest+60,ytest,ztest+5,blockmodded.GLOWSTONE_BLOCK) wallsignid=blockmodded.SIGN_WALL.id for key in wallmounts: b = getattr(blockmodded,key) @@ -323,6 +342,8 @@ def runBlockTests(mc): x=xtest+70 y=ytest z=ztest+10 + mc.setBlock(xtest+60,ytest,ztest+5,blockmodded.AIR) + mc.setBlock(xtest+70,ytest,ztest+5,blockmodded.GLOWSTONE_BLOCK) for key in doors: b = getattr(blockmodded,key) mc.setBlocks(x,y,z,x+3,y+2,z+3,signmount) @@ -388,12 +409,15 @@ def runBlockTests(mc): untested.discard(b.id) time.sleep(1) - x=xtest+70 + x=xtest+80 y=ytest - z=ztest+20 + z=ztest+10 + mc.setBlock(xtest+70,ytest,ztest+5,blockmodded.AIR) + mc.setBlock(xtest+80,ytest,ztest+5,blockmodded.GLOWSTONE_BLOCK) for key in stairs: b = getattr(blockmodded,key) - mc.setBlocks(x+1,y,z-1,x+3,y+11,z-3,signmount) + mc.setBlocks(x ,y,z ,x+4,y+15,z-4,blockmodded.AIR) + mc.setBlocks(x+1,y,z-1,x+3,y+15,z-3,signmount) mc.setBlock(x+1,y ,z ,b.id,0) mc.setBlock(x+2,y+1,z ,b.id,0) mc.setBlock(x+3,y+2,z ,b.id,0) @@ -410,29 +434,39 @@ def runBlockTests(mc): mc.setBlock(x ,y+10,z-2,b.id,2) mc.setBlock(x ,y+11,z-1,b.id,2) mc.setBlock(x ,y+11,z ,signmount) - mc.setBlock(x+1,y-1,z ,b.id,5) - mc.setBlock(x+2,y ,z ,b.id,5) - mc.setBlock(x+3,y+1,z ,b.id,5) - mc.setBlock(x+4,y+2,z-1,b.id,6) - mc.setBlock(x+4,y+3,z-2,b.id,6) - mc.setBlock(x+4,y+4,z-3,b.id,6) - mc.setBlock(x+3,y+5,z-4,b.id,4) - mc.setBlock(x+2,y+6,z-4,b.id,4) - mc.setBlock(x+1,y+7,z-4,b.id,4) - mc.setBlock(x ,y+8,z-3,b.id,7) - mc.setBlock(x ,y+9,z-2,b.id,7) - mc.setBlock(x ,y+10,z-1,b.id,7) + mc.setBlock(x+1,y+4,z ,b.id,5) + mc.setBlock(x+2,y+5,z ,b.id,5) + mc.setBlock(x+3,y+6,z ,b.id,5) + mc.setBlock(x+4,y+7,z ,signmount) + mc.setBlock(x+4,y+7,z-1,b.id,6) + mc.setBlock(x+4,y+8,z-2,b.id,6) + mc.setBlock(x+4,y+9,z-3,b.id,6) + mc.setBlock(x+4,y+9,z-4,signmount) + mc.setBlock(x+3,y+10,z-4,b.id,4) + mc.setBlock(x+2,y+11,z-4,b.id,4) + mc.setBlock(x+1,y+12,z-4,b.id,4) + mc.setBlock(x ,y+12,z-4,signmount) + mc.setBlock(x ,y+13,z-3,b.id,7) + mc.setBlock(x ,y+14,z-2,b.id,7) + mc.setBlock(x ,y+15,z-1,b.id,7) + mc.setBlock(x ,y+15,z ,signmount) mc.setSign (x+2,y+2 ,z ,wallsignid,3,key,"id=" + str(b.id),"data=0") - mc.setSign (x+3,y ,z ,wallsignid,3,key,"id=" + str(b.id),"data=5") + mc.setSign (x+3,y+5 ,z ,wallsignid,3,key,"id=" + str(b.id),"data=5") mc.setSign (x+4,y+5 ,z-2,wallsignid,5,key,"id=" + str(b.id),"data=3") - mc.setSign (x+4,y+3 ,z-3,wallsignid,5,key,"id=" + str(b.id),"data=6") + mc.setSign (x+4,y+8 ,z-3,wallsignid,5,key,"id=" + str(b.id),"data=6") mc.setSign (x+2,y+8 ,z-4,wallsignid,2,key,"id=" + str(b.id),"data=1") - mc.setSign (x+1,y+6 ,z-4,wallsignid,2,key,"id=" + str(b.id),"data=4") + mc.setSign (x+1,y+11,z-4,wallsignid,2,key,"id=" + str(b.id),"data=4") mc.setSign (x ,y+11,z-2,wallsignid,4,key,"id=" + str(b.id),"data=2") - mc.setSign (x ,y+9 ,z-1,wallsignid,4,key,"id=" + str(b.id),"data=7") - y+=12 + mc.setSign (x ,y+14,z-1,wallsignid,4,key,"id=" + str(b.id),"data=7") + time.sleep(0.1) + z+=8 + if z > ztest+40: + z = ztest+10 + x += 10 untested.discard(b.id) + mc.setBlock(xtest+80,ytest,ztest+5,blockmodded.AIR) + #Display list of all blocks which did not get tested for id in untested: untest="Untested block " + str(id) @@ -441,7 +475,7 @@ def runBlockTests(mc): mc.postToChat(untest) mc.postToChat("runBlockTests() complete") -def runEntityTests(mc): +def runEntityTests(mc, xtest=50, ytest=100, ztest=50): """runEntityTests - tests creation of all entities known to RaspberryJuice A sign is placed next to the created entity so user can view in Minecraft whether block created correctly or not @@ -466,9 +500,6 @@ def runEntityTests(mc): cavers=["MAGMA_CUBE","BAT","PARROT","CHICKEN","STRAY","SKELETON","SPIDER","ZOMBIE","SLIME","CAVE_SPIDER","PIG_ZOMBIE","ENDERMAN","SNOWMAN","SILVERFISH","ILLUSIONER"] bosses=["WITHER"] # location for platform showing all entities - xtest = 50 - ytest = 50 - ztest = 50 air=blockmodded.AIR wall=blockmodded.GLASS roof=blockmodded.STONE @@ -482,13 +513,11 @@ def runEntityTests(mc): wallsignid=blockmodded.SIGN_WALL.id mc.postToChat("runEntityTests(): Creating test entities at x=" + str(xtest) + " y=" + str(ytest) + " z=" + str(ztest)) - #clear the area in segments, otherwise it breaks the server - #clearing area - for y_inc in range(0, 10): - mc.setBlocks(xtest,ytest+y_inc,ztest,xtest+100,ytest+y_inc,ztest+100,air) - time.sleep(2) - - mc.setBlocks(xtest,ytest-1,ztest-1,xtest+100,ytest-1,ztest+100,floor) + mc.setBlocks(xtest,ytest-1,ztest-1,xtest+100,ytest-1,ztest+50,floor) + # Only thing that really has to be cleared is mounting wall for hangers. + # Test will crash if previous hangers still exist when trying to mount new ones + # Clear wall now before dancing villager so minecraft client has time to clear before making new one. + mc.setBlocks(xtest+2,ytest,ztest-1,xtest+2,ytest+2,ztest+1+len(hangers)*3,air) mc.player.setTilePos(xtest, ytest, ztest) mc.postToChat("Dancing villager") @@ -499,7 +528,7 @@ def runEntityTests(mc): id=mc.spawnEntity(x,y,z,entitymodded.VILLAGER) theta = 0 while theta <= 2 * math.pi: - time.sleep(1) + time.sleep(0.1) theta += 0.1 x = xtest + math.sin(theta) * r z = ztest + math.cos(theta) * r @@ -531,16 +560,6 @@ def runEntityTests(mc): x=xtest y=ytest z=ztest - for key in items: - z += 2 - if z > 98: - z = ztest - x += 10 - e = getattr(entitymodded,key) - mc.setBlock(x+2,y,z,signmount) - mc.setSign(x+2,y+1,z,sign,key,"id=" + str(e.id)) - mc.spawnEntity(x,y,z,e) - untested.discard(e.id) for key in hangers: z += 3 if z > 97: @@ -551,6 +570,16 @@ def runEntityTests(mc): mc.setSign(x+1,y,z,wallsignid,4,key,"id=" + str(e.id)) mc.spawnEntity(x+1,y+2,z,e) untested.discard(e.id) + for key in items: + z += 2 + if z > 98: + z = ztest + x += 10 + e = getattr(entitymodded,key) + mc.setBlock(x+2,y,z,signmount) + mc.setSign(x+2,y+1,z,sign,key,"id=" + str(e.id)) + mc.spawnEntity(x,y,z,e) + untested.discard(e.id) z = ztest - 4 x += 10 time.sleep(1) @@ -613,17 +642,35 @@ def runEntityTests(mc): mc.setSign(x-1,y,z+2,sign,key,"id=" + str(e.id)) mc.spawnEntity(x+2,y,z+2,e) untested.discard(e.id) + + x+=10 + z=ztest + wallheight=10 + time.sleep(1) + for key in sinks: + e = getattr(entitymodded,key) + mc.setBlocks(x,y,z,x+10,y+wallheight,z+10,wall) + mc.setBlocks(x+1,y,z+1,x+9,y+wallheight,z+9,blockmodded.WATER_STATIONARY) + mc.setBlock(x-1,y,z+1,torch) + mc.setSign(x-1,y,z+2,sign,key,"id=" + str(e.id)) + mc.spawnEntity(x+5,y+5,z+5,e) + untested.discard(e.id) + z += 10 + if z > 90: + z = ztest + x += 15 x=xtest y=ytest+10 z=ztest + wallheight=13 time.sleep(1) for key in giants: e = getattr(entitymodded,key) - mc.setBlocks(x,y,z,x+20,y+10,z+20,wall) - mc.setBlocks(x,y+11,z,x+20,y+11,z+20,roof) + mc.setBlocks(x,y,z,x+20,y+wallheight,z+20,wall) + mc.setBlocks(x,y+wallheight+1,z,x+20,y+wallheight+1,z+20,roof) mc.setBlocks(x-5,y-1,z-1,x+20,y-1,z+21,floor) - mc.setBlocks(x+1,y,z+1,x+19,y+10,z+19,air) + mc.setBlocks(x+1,y,z+1,x+19,y+wallheight,z+19,air) mc.setSign(x-1,y,z+2,sign,key,"id=" + str(e.id)) mc.spawnEntity(x+10,y+5,z+10,e) untested.discard(e.id) @@ -634,10 +681,10 @@ def runEntityTests(mc): time.sleep(1) for key in bosses: e = getattr(entitymodded,key) - mc.setBlocks(x,y,z,x+20,y+10,z+20,blockmodded.BEDROCK) - mc.setBlocks(x,y+11,z,x+20,y+11,z+20,blockmodded.BEDROCK) + mc.setBlocks(x,y,z,x+20,y+wallheight,z+20,blockmodded.BEDROCK) + mc.setBlocks(x,y+wallheight+1,z,x+20,y+wallheight+1,z+20,blockmodded.BEDROCK) mc.setBlocks(x-5,y-1,z-1,x+20,y-1,z+21,blockmodded.BEDROCK) - mc.setBlocks(x+1,y,z+1,x+19,y+10,z+19,air) + mc.setBlocks(x+1,y,z+1,x+19,y+wallheight,z+19,air) mc.setBlocks(x+1,y,z+8,x+19,y,z+12,torch) mc.setBlocks(x+1,y+7,z+2,x+1,y+10,z+18,torch.id,1) mc.setBlocks(x+19,y+7,z+2,x+19,y+10,z+18,torch.id,2) @@ -653,20 +700,6 @@ def runEntityTests(mc): if z > 80: z = ztest x += 25 - time.sleep(1) - for key in sinks: - e = getattr(entitymodded,key) - mc.setBlocks(x,y,z,x+20,y+10,z+20,wall) - mc.setBlocks(x,y+11,z,x+20,y+11,z+20,roof) - mc.setBlocks(x-5,y-1,z-1,x+20,y-1,z+21,floor) - mc.setBlocks(x+1,y,z+1,x+19,y+10,z+19,blockmodded.WATER_STATIONARY) - mc.setSign(x-1,y,z+2,sign,key,"id=" + str(e.id)) - mc.spawnEntity(x+10,y+5,z+10,e) - untested.discard(e.id) - z += 20 - if z > 80: - z = ztest - x += 25 #Display list of all entities which did not get tested for id in untested: @@ -678,15 +711,12 @@ def runEntityTests(mc): mc.postToChat("/kill @e[type=!player]") mc.postToChat("to remove test entities") -def runBookTests(mc): +def runBookTests(mc, xtest=48, ytest=100, ztest=48): """runBookTests - tests creation of book in a chest Author: Tim Cummings https://www.triptera.com.au/wordpress/ """ - # location for chest containing book - xtest = 48 - ytest = 100 - ztest = 48 + # location for chest containing book xtest, ytest, ztest mc.postToChat("runBookTests(): Creating chest with book at x=" + str(xtest) + " y=" + str(ytest) + " z=" + str(ztest)) b = bookmodded.Book({"title":"RaspberryJuice Test", "author":"Tim Cummings"}) b.addPageOfText('This page should be unformatted text. ' @@ -771,8 +801,8 @@ def runTests(mc, library="Standard library", extended=False): #getBlocks if extended: - listOfBlocks = mc.getBlocks(pos.x,pos.y + 10,pos.z, - pos.x + 5, pos.y + 15, pos.z + 5) + listOfBlocks = list(mc.getBlocks(pos.x,pos.y + 10,pos.z, + pos.x + 5, pos.y + 15, pos.z + 5)) print(listOfBlocks) #getPlayerEntityIds @@ -818,22 +848,41 @@ def runTests(mc, library="Standard library", extended=False): mc.spawnEntity(tilePos.x + 2, tilePos.y + 2, tilePos.x + 2, entitymodded.CREEPER) mc.postToChat("Creeper spawned") - mc.postToChat("Post To Chat - Run full block and entity test Y/N?") + mc.postToChat("Post To Chat - a single letter to choose from following options") + mc.postToChat(" B=run full block and book tests") + mc.postToChat(" E=run full entity tests") + mc.postToChat(" A=run all block, book and entity tests") + mc.postToChat(" C=clear full test area") + mc.postToChat(" F=both clear area and run full tests") + mc.postToChat(" S=skip these tests") chatPosted = False - fullTests = False + blockTests = False + entityTests = False + clearArea = False while not chatPosted: time.sleep(1) chatPosts = mc.events.pollChatPosts() for chatPost in chatPosts: mc.postToChat("Echo " + chatPost.message) chatPosted = True - if chatPost.message == "Y": - fullTests = True + chatLetter = chatPost.message.strip().upper() + if len(chatLetter) > 0: chatLetter = chatLetter[0] + if chatLetter in ('F','B','A'): + blockTests = True + if chatLetter in ('F','E','A'): + entityTests = True + if chatLetter in ('C','F'): + clearArea = True - if fullTests: + if clearArea: + clearTestArea(mc) + + if blockTests: runBlockTests(mc) - runEntityTests(mc) runBookTests(mc) + + if entityTests: + runEntityTests(mc) mc.postToChat("Tests complete for " + library)