package com.intellectualcrafters.plot.util; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLConnection; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.UUID; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; import com.intellectualcrafters.jnbt.ByteArrayTag; import com.intellectualcrafters.jnbt.CompoundTag; import com.intellectualcrafters.jnbt.IntTag; import com.intellectualcrafters.jnbt.ListTag; import com.intellectualcrafters.jnbt.NBTInputStream; import com.intellectualcrafters.jnbt.NBTOutputStream; import com.intellectualcrafters.jnbt.ShortTag; import com.intellectualcrafters.jnbt.StringTag; import com.intellectualcrafters.jnbt.Tag; import com.intellectualcrafters.plot.PS; import com.intellectualcrafters.plot.config.Settings; import com.intellectualcrafters.plot.object.Location; import com.intellectualcrafters.plot.object.Plot; import com.intellectualcrafters.plot.object.PlotBlock; import com.intellectualcrafters.plot.object.PlotId; import com.intellectualcrafters.plot.object.RunnableVal; import com.intellectualcrafters.plot.object.schematic.PlotItem; import com.intellectualcrafters.plot.object.schematic.StateWrapper; import com.intellectualcrafters.plot.util.bukkit.BukkitUtil; import com.intellectualcrafters.plot.util.bukkit.UUIDHandler; public abstract class SchematicHandler { public static SchematicHandler manager = new BukkitSchematicHandler(); private boolean exportAll = false; public boolean exportAll(final Collection collection, final File outputDir, final String namingScheme, final Runnable ifSuccess) { if (exportAll) { return false; } if (collection.size() == 0) { return false; } exportAll = true; final ArrayList plots = new ArrayList(collection); TaskManager.runTask(new Runnable() { @Override public void run() { if (plots.size() == 0) { exportAll = false; TaskManager.runTask(ifSuccess); return; } Iterator i = plots.iterator(); final Plot plot = i.next(); i.remove(); String o = UUIDHandler.getName(plot.owner); if (o == null) { o = "unknown"; } final String name; if (namingScheme == null) { name = plot.id.x + ";" + plot.id.y + "," + plot.world + "," + o; } else { name = namingScheme.replaceAll("%owner%", o).replaceAll("%id%", plot.id.toString()).replaceAll("%idx%", plot.id.x + "").replaceAll("%idy%", plot.id.y + "").replaceAll("%world%", plot.world); } final String directory; if (outputDir == null) { directory = Settings.SCHEMATIC_SAVE_PATH; } else { directory = outputDir.getPath(); } Location top = plot.getTop(); Location bot = plot.getBottom(); int area = (1 + top.getX() - bot.getX()) * (1 + top.getZ() - bot.getZ()); if (area > 4096) { PS.log("The plot is > 64 x 64 - Fast lossy schematic saving will be used"); } if (area <= 4096 && PS.get().worldEdit != null) { new WorldEditSchematic().saveSchematic(directory + File.separator + name + ".schematic", plot.world, plot.id); } else { final Runnable THIS = this; SchematicHandler.manager.getCompoundTag(plot.world, plot.id, new RunnableVal() { @Override public void run() { if (value == null) { MainUtil.sendMessage(null, "&7 - Skipped plot &c" + plot.id); } else { TaskManager.runTaskAsync(new Runnable() { @Override public void run() { MainUtil.sendMessage(null, "&6ID: " + plot.id); final boolean result = SchematicHandler.manager.save(value, directory + File.separator + name + ".schematic"); if (!result) { MainUtil.sendMessage(null, "&7 - Failed to save &c" + plot.id); } else { MainUtil.sendMessage(null, "&7 - &a success: " + plot.id); } TaskManager.runTask(new Runnable() { @Override public void run() { THIS.run(); } }); } }); } } }); } } }); return true; } /** * Paste a schematic * * @param schematic the schematic object to paste * @param plot plot to paste in * @param x_offset offset x to paste it from plot origin * @param z_offset offset z to paste it from plot origin * * @return boolean true if succeeded */ public void paste(final Schematic schematic, final Plot plot, final int x_offset, final int z_offset, final RunnableVal whenDone) { System.out.print(1); TaskManager.runTaskAsync(new Runnable() { @Override public void run() { System.out.print(2); if (whenDone != null) whenDone.value = false; if (schematic == null) { PS.log("Schematic == null :|"); TaskManager.runTask(whenDone); return; } try { System.out.print(3); final Dimension demensions = schematic.getSchematicDimension(); final int WIDTH = demensions.getX(); final int LENGTH = demensions.getZ(); final int HEIGHT = demensions.getY(); byte[] ids = schematic.ids; byte[] datas = schematic.datas; int y_offset; if (HEIGHT >= 256) { y_offset = -1; } else { y_offset = BukkitUtil.getMaxHeight(plot.world) - 1; } Location bottom = MainUtil.getPlotBottomLoc(plot.world, plot.id).add(1 + x_offset, y_offset, 1 + z_offset); int X = bottom.getX(); int Y = bottom.getY(); int Z = bottom.getZ(); int id; System.out.print(4); System.out.print("HEIGHT: " + HEIGHT); for (int y = 0; y < Math.max(256, HEIGHT); y++) { int i1 = y * WIDTH * LENGTH; for (int z = 0; z < LENGTH; z++) { int i2 = z * WIDTH + i1; for (int x = 0; x < WIDTH; x++) { int i = i2 + x; id = ids[i]; switch(id) { case 0: case 2: case 4: case 13: case 14: case 15: case 20: case 21: case 22: case 24: case 30: case 32: case 37: case 39: case 40: case 41: case 42: case 45: case 46: case 47: case 48: case 49: case 50: case 51: case 55: case 56: case 57: case 58: case 60: case 7: case 8: case 9: case 10: case 11: case 73: case 74: case 75: case 76: case 78: case 79: case 80: case 81: case 82: case 83: case 85: case 87: case 88: case 101: case 102: case 103: case 110: case 112: case 113: case 121: case 122: case 129: case 133: case 165: case 166: case 169: case 170: case 172: case 173: case 174: case 181: case 182: case 188: case 189: case 190: case 191: case 192: { SetBlockQueue.setBlock(plot.world, X + x, Y + y, Z + z, id); break; } default: { SetBlockQueue.setBlock(plot.world, X + x, Y + y, Z + z, new PlotBlock((short) id, (byte) datas[i])); break; } } // set block } } } System.out.print(5); SetBlockQueue.addNotify(new Runnable() { @Override public void run() { System.out.print(6); pasteStates(schematic, plot, x_offset, z_offset); System.out.print(7); if (whenDone != null) { whenDone.value = true; whenDone.run(); } } }); return; } catch (final Exception e) { e.printStackTrace(); TaskManager.runTask(whenDone); return; } } }); } public boolean pasteStates(final Schematic schematic, final Plot plot, final int x_offset, final int z_offset) { if (schematic == null) { PS.log("Schematic == null :|"); return false; } HashSet items = schematic.getItems(); if (items == null) { return false; } Location l1 = MainUtil.getPlotBottomLoc(plot.world, plot.getId()); final int sy = BukkitUtil.getHeighestBlock(plot.world, l1.getX() + 1, l1.getZ() + 1); final Dimension demensions = schematic.getSchematicDimension(); final int HEIGHT = demensions.getY(); if (!(HEIGHT == BukkitUtil.getMaxHeight(plot.world))) { l1 = l1.add(1, sy - 1, 1); } else { l1 = l1.add(1, 0, 1); } int X = l1.getX() + x_offset; int Y = l1.getY(); int Z = l1.getZ() + z_offset; for (PlotItem item : items) { item.x += X; item.y += Y; item.z += Z; BlockManager.manager.addItems(plot.world, item); } return true; } public Schematic getSchematic(final CompoundTag tag, final File file) { final Map tagMap = tag.getValue(); // Slow // byte[] addId = new byte[0]; // if (tagMap.containsKey("AddBlocks")) { // addId = ByteArrayTag.class.cast(tagMap.get("AddBlocks")).getValue(); // } // end slow final short width = ShortTag.class.cast(tagMap.get("Width")).getValue(); final short length = ShortTag.class.cast(tagMap.get("Length")).getValue(); final short height = ShortTag.class.cast(tagMap.get("Height")).getValue(); final byte[] block = ByteArrayTag.class.cast(tagMap.get("Blocks")).getValue(); final byte[] data = ByteArrayTag.class.cast(tagMap.get("Data")).getValue(); // Slow + has code for exceptions (addId) inside the loop rather than outside // for (int index = 0; index < b.length; index++) { // if ((index >> 1) >= addId.length) { // blocks[index] = (short) (b[index] & 0xFF); // } else { // if ((index & 1) == 0) { // blocks[index] = (short) (((addId[index >> 1] & 0x0F) << 8) + (b[index] & 0xFF)); // } else { // blocks[index] = (short) (((addId[index >> 1] & 0xF0) << 4) + (b[index] & 0xFF)); // } // } // } // Slow as wrapper for each block // final DataCollection[] collection = new DataCollection[b.length]; // for (int x = 0; x < b.length; x++) { // collection[x] = new DataCollection(blocks[x], d[x]); // } // Schematic schem = new Schematic(collection, dimension, file); Dimension dimensions = new Dimension(width, height, length); Schematic schem = new Schematic(block, data, dimensions , file); // Slow try { List blockStates = ListTag.class.cast(tagMap.get("TileEntities")).getValue(); for (Tag stateTag : blockStates) { CompoundTag ct = ((CompoundTag) stateTag); Map state = ct.getValue(); short x = IntTag.class.cast(state.get("x")).getValue().shortValue(); short y = IntTag.class.cast(state.get("y")).getValue().shortValue(); short z = IntTag.class.cast(state.get("z")).getValue().shortValue(); new StateWrapper(ct).restoreTag(x, y, z, schem); } } catch (Exception e) { e.printStackTrace(); } return schem; } /** * Get a schematic * * @param name to check * * @return schematic if found, else null */ public Schematic getSchematic(final String name) { { final File parent = new File(PS.get().IMP.getDirectory() + File.separator + "schematics"); if (!parent.exists()) { if (!parent.mkdir()) { throw new RuntimeException("Could not create schematic parent directory"); } } } final File file = new File(PS.get().IMP.getDirectory() + File.separator + "schematics" + File.separator + name + ".schematic"); return getSchematic(file); } /** * Get a schematic * * @param name to check * * @return schematic if found, else null */ public Schematic getSchematic(File file) { if (!file.exists()) { PS.log(file.toString() + " doesn't exist"); return null; } try { final InputStream iStream = new FileInputStream(file); final NBTInputStream stream = new NBTInputStream(new GZIPInputStream(iStream)); final CompoundTag tag = (CompoundTag) stream.readTag(); stream.close(); return getSchematic(tag, file); } catch (final Exception e) { PS.log(file.toString() + " is not in GZIP format"); return null; } } public URL upload(final CompoundTag tag) { if (tag == null) { PS.log("&cCannot save empty tag"); return null; } try { UUID uuid = UUID.randomUUID(); String website = Settings.WEB_URL + "upload.php?" + uuid; String charset = "UTF-8"; String param = "value"; String boundary = Long.toHexString(System.currentTimeMillis()); String CRLF = "\r\n"; URLConnection con = new URL(website).openConnection(); con.setDoOutput(true); con.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary); try ( OutputStream output = con.getOutputStream(); PrintWriter writer = new PrintWriter(new OutputStreamWriter(output, charset), true); ) { writer.append("--" + boundary).append(CRLF); writer.append("Content-Disposition: form-data; name=\"param\"").append(CRLF); writer.append("Content-Type: text/plain; charset=" + charset).append(CRLF); writer.append(CRLF).append(param).append(CRLF).flush(); writer.append("--" + boundary).append(CRLF); writer.append("Content-Disposition: form-data; name=\"schematicFile\"; filename=\"" + "plot.schematic" + "\"").append(CRLF); writer.append("Content-Type: " + URLConnection.guessContentTypeFromName("plot.schematic")).append(CRLF); writer.append("Content-Transfer-Encoding: binary").append(CRLF); writer.append(CRLF).flush(); GZIPOutputStream gzip = new GZIPOutputStream(output); NBTOutputStream nos = new NBTOutputStream(gzip); nos.writeTag(tag); gzip.finish(); nos.flush(); output.flush(); writer.append(CRLF).flush(); writer.append("--" + boundary + "--").append(CRLF).flush(); nos.close(); output.close(); } // try (Reader response = new InputStreamReader(con.getInputStream(), StandardCharsets.UTF_8)) { // final char[] buffer = new char[256]; // StringBuilder result = new StringBuilder(); // while (true) { // int r = response.read(buffer); // if (r < 0) { // break; // } // result.append(buffer, 0, r); // } // if (!result.equals("The file plot.schematic has been uploaded.")) { // return null; // } // } // catch (Exception e) { // // } int responseCode = ((HttpURLConnection) con).getResponseCode(); if (responseCode != 200) { return null; } return new URL(Settings.WEB_URL + "?key=" + uuid + "&ip=" + Settings.WEB_IP); } catch (final Exception e) { e.printStackTrace(); } return null; } /** * Saves a schematic to a file path * * @param tag to save * @param path to save in * * @return true if succeeded */ public boolean save(final CompoundTag tag, final String path) { if (tag == null) { PS.log("&cCannot save empty tag"); return false; } try { final File tmp = new File(path); tmp.getParentFile().mkdirs(); final OutputStream stream = new FileOutputStream(path); final NBTOutputStream output = new NBTOutputStream(new GZIPOutputStream(stream)); output.writeTag(tag); output.close(); stream.close(); } catch (final IOException e) { e.printStackTrace(); return false; } return true; } /** * Create a compound tag from blocks * - Untested * @param blocks * @param blockdata * @param d * @return */ public CompoundTag createTag(byte[] blocks, byte[] blockdata, Dimension d) { final HashMap schematic = new HashMap<>(); schematic.put("Width", new ShortTag("Width", (short) d.getX())); schematic.put("Length", new ShortTag("Length", (short) d.getZ())); schematic.put("Height", new ShortTag("Height", (short) d.getY())); schematic.put("Materials", new StringTag("Materials", "Alpha")); schematic.put("WEOriginX", new IntTag("WEOriginX", 0)); schematic.put("WEOriginY", new IntTag("WEOriginY", 0)); schematic.put("WEOriginZ", new IntTag("WEOriginZ", 0)); schematic.put("WEOffsetX", new IntTag("WEOffsetX", 0)); schematic.put("WEOffsetY", new IntTag("WEOffsetY", 0)); schematic.put("WEOffsetZ", new IntTag("WEOffsetZ", 0)); schematic.put("Blocks", new ByteArrayTag("Blocks", blocks)); schematic.put("Data", new ByteArrayTag("Data", blockdata)); schematic.put("Entities", new ListTag("Entities", CompoundTag.class, new ArrayList())); schematic.put("TileEntities", new ListTag("TileEntities", CompoundTag.class, new ArrayList())); return new CompoundTag("Schematic", schematic); } /** * Gets the schematic of a plot * * @param world to check * @param id plot * * @return tag */ public void getCompoundTag(final String world, final PlotId id, RunnableVal whenDone) { if (!PS.get().getPlots(world).containsKey(id)) { whenDone.run(); return; } final Location pos1 = MainUtil.getPlotBottomLoc(world, id).add(1, -1, 1); final Location pos2 = MainUtil.getPlotTopLoc(world, id); getCompoundTag(world, pos1, pos2, whenDone); } public abstract void getCompoundTag(final String world, final Location pos1, final Location pos2, RunnableVal whenDone); public boolean pastePart(final String world, final DataCollection[] blocks, final Location l1, final int x_offset, final int z_offset, final int i1, final int i2, final int WIDTH, final int LENGTH) { int length = 0; for (int i = i1; i <= i2; i++) { if (blocks[i].block == 0) { length++; } } length = i2 - i1 - length + 1; int X = l1.getX(); int Y = l1.getY(); int Z = l1.getZ(); final int[] xl = new int[length]; final int[] yl = new int[length]; final int[] zl = new int[length]; final int[] ids = new int[length]; final byte[] data = new byte[length]; int count = 0; for (int i = i1; i <= i2; i++) { final short id = blocks[i].block; if (id == 0) { continue; // } final int area = WIDTH * LENGTH; final int r = i % (area); final int x = r % WIDTH; final int y = i / area; final int z = r / WIDTH; xl[count] = x + X; yl[count] = y + Y; zl[count] = z + Z; ids[count] = id; data[count] = blocks[i].data; count++; if (y > 256) { break; } } BlockManager.setBlocks(world, xl, yl, zl, ids, data); return true; } /** * Schematic Class * * @author Citymonstret */ public class Schematic { // Lossy but fast private final byte[] ids; private final byte[] datas; @Deprecated private DataCollection[] collection; private final Dimension schematicDimension; private final File file; private HashSet items; /** * This is deprecated as having a wrapper for each block is really slow.
* - There's also a performance hit by having the cast the DataCollection short / byte * - * @param blockCollection * @param schematicDimension * @param file */ // @Deprecated // public Schematic(final DataCollection[] blockCollection, final Dimension schematicDimension, final File file) { // ids = new byte[blockCollection.length]; // datas = new byte[blockCollection.length]; // for (int i = 0; i < blockCollection.length; i++) { // DataCollection block = blockCollection[i]; // ids[i] = (byte) block.block; // datas[i] = block.data; // } // this.collection = blockCollection; // this.schematicDimension = schematicDimension; // this.file = file; // } public Schematic(final byte[] i, final byte[] b, final Dimension d, final File f) { ids = i; datas = b; schematicDimension = d; file = f; } public void addItem(PlotItem item) { if (this.items == null) { this.items = new HashSet<>(); } items.add(item); } public HashSet getItems() { return this.items; } public File getFile() { return this.file; } public Dimension getSchematicDimension() { return this.schematicDimension; } public byte[] getIds() { return ids; } public byte[] getDatas() { return datas; } // @Deprecated // public DataCollection[] getBlockCollection() { // if (this.collection == null) { // collection = new DataCollection[ids.length]; // for (int i = 0; i < ids.length; i++) { // collection[i] = new DataCollection(ids[i], datas[i]); // } // } // return this.collection; // } } /** * Schematic Dimensions * * @author Citymonstret */ public static class Dimension { private final int x; private final int y; private final int z; public Dimension(final int x, final int y, final int z) { this.x = x; this.y = y; this.z = z; } public int getX() { return this.x; } public int getY() { return this.y; } public int getZ() { return this.z; } } /** * Schematic Data Collection * * @author Citymonstret */ public class DataCollection { private final short block; private final byte data; // public CompoundTag tag; public DataCollection(final short block, final byte data) { this.block = block; this.data = data; } public short getBlock() { return this.block; } public byte getData() { return this.data; } } }