package com.intellectualcrafters.plot.util; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.Reader; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLConnection; import java.nio.charset.StandardCharsets; 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 org.bukkit.Bukkit; 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.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 boolean paste(final Schematic schematic, final Plot plot, final int x_offset, final int z_offset) { if (schematic == null) { PS.log("Schematic == null :|"); return false; } try { final Dimension demensions = schematic.getSchematicDimension(); final int WIDTH = demensions.getX(); final int LENGTH = demensions.getZ(); final int HEIGHT = demensions.getY(); final DataCollection[] blocks = schematic.getBlockCollection(); Location l1 = MainUtil.getPlotBottomLoc(plot.world, plot.getId()); final int sy = BukkitUtil.getHeighestBlock(plot.world, l1.getX() + 1, l1.getZ() + 1); if (!(HEIGHT == BukkitUtil.getMaxHeight(plot.world))) { l1 = l1.add(1, sy - 1, 1); } else { l1 = l1.add(1, 0, 1); } int X = l1.getX(); int Y = l1.getY(); int Z = l1.getZ(); final int[] xl = new int[blocks.length]; final int[] yl = new int[blocks.length]; final int[] zl = new int[blocks.length]; final int[] ids = new int[blocks.length]; final byte[] data = new byte[blocks.length]; for (int x = 0; x < WIDTH; x++) { for (int z = 0; z < LENGTH; z++) { for (int y = 0; y < HEIGHT; y++) { final int index = (y * WIDTH * LENGTH) + (z * WIDTH) + x; final DataCollection block = blocks[index]; xl[index] = x + X; yl[index] = y + Y; zl[index] = z + Z; ids[index] = block.block; data[index] = block.data; } } } BlockManager.setBlocks(plot.world, xl, yl, zl, ids, data); pasteStates(schematic, plot, x_offset, z_offset); } catch (final Exception e) { return false; } return true; } 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(); byte[] addId = new byte[0]; if (tagMap.containsKey("AddBlocks")) { addId = ByteArrayTag.class.cast(tagMap.get("AddBlocks")).getValue(); } 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[] b = ByteArrayTag.class.cast(tagMap.get("Blocks")).getValue(); final byte[] d = ByteArrayTag.class.cast(tagMap.get("Data")).getValue(); final short[] blocks = new short[b.length]; final Dimension dimension = new Dimension(width, height, length); 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)); } } } 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); 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 { private final DataCollection[] blockCollection; private final Dimension schematicDimension; private final File file; private HashSet items; public Schematic(final DataCollection[] blockCollection, final Dimension schematicDimension, final File file) { this.blockCollection = blockCollection; this.schematicDimension = schematicDimension; this.file = file; } 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 DataCollection[] getBlockCollection() { return this.blockCollection; } } /** * 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; } } }