diff --git a/plugin/src/main/java/com/denizenscript/denizen/nms/interfaces/BlockHelper.java b/plugin/src/main/java/com/denizenscript/denizen/nms/interfaces/BlockHelper.java index bd9101dd7d..6c841526c6 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/nms/interfaces/BlockHelper.java +++ b/plugin/src/main/java/com/denizenscript/denizen/nms/interfaces/BlockHelper.java @@ -68,4 +68,22 @@ default float getBlockStength(Material mat) { default void setBlockStrength(Material mat, float strength) { throw new UnsupportedOperationException(); } + + default ModernBlockData parseBlockData(String text) { + int openBracket = text.indexOf('['); + String material = text; + String otherData = null; + if (openBracket > 0) { + material = text.substring(0, openBracket); + otherData = text.substring(openBracket); + } + if (material.startsWith("minecraft:")) { + material = material.substring("minecraft:".length()); + } + return parseBlockData(Material.getMaterial(material.toUpperCase()), otherData); + } + + default ModernBlockData parseBlockData(Material material, String otherData) { + throw new UnsupportedOperationException(); + } } diff --git a/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/world/SchematicCommand.java b/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/world/SchematicCommand.java index c34cfebf0d..caa0d7167b 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/world/SchematicCommand.java +++ b/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/world/SchematicCommand.java @@ -4,6 +4,7 @@ import com.denizenscript.denizen.utilities.Utilities; import com.denizenscript.denizen.utilities.blocks.CuboidBlockSet; import com.denizenscript.denizen.utilities.blocks.MCEditSchematicHelper; +import com.denizenscript.denizen.utilities.blocks.SpongeSchematicHelper; import com.denizenscript.denizen.utilities.debugging.Debug; import com.denizenscript.denizen.nms.interfaces.BlockData; import com.denizenscript.denizen.objects.CuboidTag; @@ -55,6 +56,8 @@ public class SchematicCommand extends AbstractCommand implements Holdable, Liste // For 'save', 'load', and 'rotate', this processes async to prevent server lockup. // For 'paste' and 'create', this delays how many blocks can be processed at once, spread over many ticks. // + // The "load" option by default will load '.schem' files. If no '.schem' file is available, will attempt to load a legacy '.schematic' file instead. + // // @Tags // ].height> // ].length> @@ -236,19 +239,29 @@ public void execute(final ScriptEntry scriptEntry) { return; } String directory = URLDecoder.decode(System.getProperty("user.dir")); - File f = new File(directory + "/plugins/Denizen/schematics/" + fname + ".schematic"); + File f = new File(directory + "/plugins/Denizen/schematics/" + fname + ".schem"); if (!Utilities.canReadFile(f)) { Debug.echoError("Server config denies reading files in that location."); return; } if (!f.exists()) { - Debug.echoError("Schematic file " + fname + " does not exist. Are you sure it's in " + directory + "/plugins/Denizen/schematics/?"); - return; + f = new File(directory + "/plugins/Denizen/schematics/" + fname + ".schematic"); + if (!f.exists()) { + Debug.echoError("Schematic file " + fname + " does not exist. Are you sure it's in " + directory + "/plugins/Denizen/schematics/?"); + return; + } } + File schemFile = f; Runnable loadRunnable = () -> { try { - InputStream fs = new FileInputStream(f); - CuboidBlockSet newSet = MCEditSchematicHelper.fromMCEditStream(fs); + InputStream fs = new FileInputStream(schemFile); + CuboidBlockSet newSet; + if (schemFile.getName().endsWith(".schem")) { + newSet = SpongeSchematicHelper.fromSpongeStream(fs); + } + else { + newSet = MCEditSchematicHelper.fromMCEditStream(fs); + } fs.close(); Bukkit.getScheduler().runTask(DenizenAPI.getCurrentInstance(), () -> { schematics.put(name.asString().toUpperCase(), newSet); @@ -536,7 +549,7 @@ public void schematicTags(ReplaceableTagEvent event) { // Returns the number of blocks in the schematic. // --> if (attribute.startsWith("blocks")) { - event.setReplaced(new ElementTag(set.blocks.size()) + event.setReplaced(new ElementTag(set.blocks.length) .getAttribute(attribute.fulfill(1))); return; } diff --git a/plugin/src/main/java/com/denizenscript/denizen/utilities/blocks/BlockSet.java b/plugin/src/main/java/com/denizenscript/denizen/utilities/blocks/BlockSet.java index a20938488b..b1e7200af6 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/utilities/blocks/BlockSet.java +++ b/plugin/src/main/java/com/denizenscript/denizen/utilities/blocks/BlockSet.java @@ -3,11 +3,9 @@ import com.denizenscript.denizen.nms.interfaces.BlockData; import org.bukkit.Location; -import java.util.List; - public interface BlockSet { - List getBlocks(); + BlockData[] getBlocks(); void setBlocksDelayed(Location loc, Runnable runme, boolean noAir); diff --git a/plugin/src/main/java/com/denizenscript/denizen/utilities/blocks/CuboidBlockSet.java b/plugin/src/main/java/com/denizenscript/denizen/utilities/blocks/CuboidBlockSet.java index 985bcad3b7..2945896df0 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/utilities/blocks/CuboidBlockSet.java +++ b/plugin/src/main/java/com/denizenscript/denizen/utilities/blocks/CuboidBlockSet.java @@ -9,9 +9,6 @@ import org.bukkit.Material; import org.bukkit.scheduler.BukkitRunnable; -import java.util.ArrayList; -import java.util.List; - public class CuboidBlockSet implements BlockSet { public CuboidBlockSet() { @@ -20,16 +17,18 @@ public CuboidBlockSet() { public CuboidBlockSet(CuboidTag cuboid, Location center) { Location low = cuboid.pairs.get(0).low; Location high = cuboid.pairs.get(0).high; - x_width = (high.getX() - low.getX()) + 1; - y_length = (high.getY() - low.getY()) + 1; - z_height = (high.getZ() - low.getZ()) + 1; + x_width = (int) ((high.getX() - low.getX()) + 1); + y_length = (int) ((high.getY() - low.getY()) + 1); + z_height = (int) ((high.getZ() - low.getZ()) + 1); center_x = center.getX() - low.getX(); center_y = center.getY() - low.getY(); center_z = center.getZ() - low.getZ(); + blocks = new BlockData[(int) (x_width * y_length * z_height)]; + int index = 0; for (int x = 0; x < x_width; x++) { for (int y = 0; y < y_length; y++) { for (int z = 0; z < z_height; z++) { - blocks.add(NMSHandler.getBlockHelper().getBlockData(low.clone().add(x, y, z).getBlock())); + blocks[index++] = NMSHandler.getBlockHelper().getBlockData(low.clone().add(x, y, z).getBlock()); } } } @@ -38,15 +37,15 @@ public CuboidBlockSet(CuboidTag cuboid, Location center) { public void buildDelayed(CuboidTag cuboid, Location center, Runnable runme) { Location low = cuboid.pairs.get(0).low; Location high = cuboid.pairs.get(0).high; - x_width = (high.getX() - low.getX()) + 1; - y_length = (high.getY() - low.getY()) + 1; - z_height = (high.getZ() - low.getZ()) + 1; + x_width = (int) ((high.getX() - low.getX()) + 1); + y_length = (int) ((high.getY() - low.getY()) + 1); + z_height = (int) ((high.getZ() - low.getZ()) + 1); center_x = center.getX() - low.getX(); center_y = center.getY() - low.getY(); center_z = center.getZ() - low.getZ(); final long goal = (long) (x_width * y_length * z_height); new BukkitRunnable() { - int index; + int index = 0; @Override public void run() { long start = System.currentTimeMillis(); @@ -54,7 +53,7 @@ public void run() { long z = index % ((long) (z_height)); long y = ((index - z) % ((long) (y_length * z_height))) / ((long) z_height); long x = (index - y - z) / ((long) (y_length * z_height)); - blocks.add(NMSHandler.getBlockHelper().getBlockData(low.clone().add(x, y, z).getBlock())); + blocks[index] = NMSHandler.getBlockHelper().getBlockData(low.clone().add(x, y, z).getBlock()); index++; if (System.currentTimeMillis() - start > 50) { SchematicCommand.noPhys = false; @@ -70,13 +69,13 @@ public void run() { }.runTaskTimer(DenizenAPI.getCurrentInstance(), 1, 1); } - public List blocks = new ArrayList<>(); + public BlockData[] blocks = null; - public double x_width; + public int x_width; - public double y_length; + public int y_length; - public double z_height; + public int z_height; public double center_x; @@ -85,7 +84,7 @@ public void run() { public double center_z; @Override - public List getBlocks() { + public BlockData[] getBlocks() { return blocks; } @@ -108,8 +107,8 @@ public void run() { long z = index % ((long) (z_height)); long y = ((index - z) % ((long) (y_length * z_height))) / ((long) z_height); long x = (index - y - z) / ((long) (y_length * z_height)); - if (!noAir || blocks.get(index).getMaterial() != Material.AIR) { - blocks.get(index).setBlock(loc.clone().add(x - center_x, y - center_y, z - center_z).getBlock(), false); + if (!noAir || blocks[index].getMaterial() != Material.AIR) { + blocks[index].setBlock(loc.clone().add(x - center_x, y - center_y, z - center_z).getBlock(), false); } index++; if (System.currentTimeMillis() - start > 50) { @@ -134,8 +133,8 @@ public void setBlocks(Location loc, boolean noAir) { for (int x = 0; x < x_width; x++) { for (int y = 0; y < y_length; y++) { for (int z = 0; z < z_height; z++) { - if (!noAir || blocks.get(index).getMaterial() != Material.AIR) { - blocks.get(index).setBlock(loc.clone().add(x - center_x, y - center_y, z - center_z).getBlock(), false); + if (!noAir || blocks[index].getMaterial() != Material.AIR) { + blocks[index].setBlock(loc.clone().add(x - center_x, y - center_y, z - center_z).getBlock(), false); } index++; } @@ -145,30 +144,32 @@ public void setBlocks(Location loc, boolean noAir) { } public void rotateOne() { - List bd = new ArrayList<>(); + BlockData[] bd = new BlockData[blocks.length]; + int index = 0; double cx = center_x; center_x = center_z; center_z = cx; for (int x = 0; x < z_height; x++) { for (int y = 0; y < y_length; y++) { for (int z = (int) x_width - 1; z >= 0; z--) { - bd.add(blockAt(z, y, x)); + bd[index++] = blockAt(z, y, x); } } } - double xw = x_width; + int xw = x_width; x_width = z_height; z_height = xw; blocks = bd; } public void flipX() { - List bd = new ArrayList<>(); + BlockData[] bd = new BlockData[blocks.length]; + int index = 0; center_x = x_width - center_x; for (int x = (int) x_width - 1; x >= 0; x--) { for (int y = 0; y < y_length; y++) { for (int z = 0; z < z_height; z++) { - bd.add(blockAt(x, y, z)); + bd[index++] = blockAt(x, y, z); } } } @@ -176,12 +177,13 @@ public void flipX() { } public void flipY() { - List bd = new ArrayList<>(); + BlockData[] bd = new BlockData[blocks.length]; + int index = 0; center_x = x_width - center_x; for (int x = 0; x < x_width; x++) { for (int y = (int) y_length - 1; y >= 0; y--) { for (int z = 0; z < z_height; z++) { - bd.add(blockAt(x, y, z)); + bd[index++] = blockAt(x, y, z); } } } @@ -189,12 +191,13 @@ public void flipY() { } public void flipZ() { - List bd = new ArrayList<>(); + BlockData[] bd = new BlockData[blocks.length]; + int index = 0; center_x = x_width - center_x; for (int x = 0; x < x_width; x++) { for (int y = 0; y < y_length; y++) { for (int z = (int) z_height - 1; z >= 0; z--) { - bd.add(blockAt(x, y, z)); + bd[index++] = blockAt(x, y, z); } } } @@ -202,7 +205,7 @@ public void flipZ() { } public BlockData blockAt(double X, double Y, double Z) { - return blocks.get((int) (Z + Y * z_height + X * z_height * y_length)); + return blocks[(int) (Z + Y * z_height + X * z_height * y_length)]; // This calculation should produce the same result as the below nonsense: /* int index = 0; diff --git a/plugin/src/main/java/com/denizenscript/denizen/utilities/blocks/MCEditSchematicHelper.java b/plugin/src/main/java/com/denizenscript/denizen/utilities/blocks/MCEditSchematicHelper.java index 119ebe7aac..a80d82acf1 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/utilities/blocks/MCEditSchematicHelper.java +++ b/plugin/src/main/java/com/denizenscript/denizen/utilities/blocks/MCEditSchematicHelper.java @@ -6,7 +6,6 @@ import com.denizenscript.denizen.objects.MaterialTag; import com.denizenscript.denizen.utilities.debugging.Debug; import org.bukkit.util.BlockVector; -import org.bukkit.util.Vector; import java.io.InputStream; import java.io.OutputStream; @@ -107,20 +106,19 @@ else if (entry.getKey().equals("z")) { BlockVector vec = new BlockVector(x, y, z); tileEntitiesMap.put(vec, values); } - org.bukkit.util.Vector vec = new Vector(width, height, length); + int finalIndex = 0; for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { for (int z = 0; z < length; z++) { int index = y * width * length + z * width + x; BlockVector pt = new BlockVector(x, y, z); - // TODO: 1.13 - move away from legacy IDs somehow? MaterialTag dMat = OldMaterialsHelper.getMaterialFrom(OldMaterialsHelper.getLegacyMaterial(blocks[index]), blockData[index]); BlockData block = dMat.getNmsBlockData(); if (tileEntitiesMap.containsKey(pt)) { CompoundTag otag = NMSHandler.getInstance().createCompoundTag(tileEntitiesMap.get(pt)); block.setCompoundTag(otag); } - cbs.blocks.add(block); + cbs.blocks[finalIndex++] = block; } } } @@ -131,8 +129,7 @@ else if (entry.getKey().equals("z")) { return cbs; } - private static T getChildTag(Map items, String key, - Class expected) throws Exception { + private static T getChildTag(Map items, String key, Class expected) throws Exception { if (!items.containsKey(key)) { throw new Exception("Schematic file is missing a '" + key + "' tag"); } @@ -169,7 +166,7 @@ public static void saveMCEditFormatToStream(CuboidBlockSet blockSet, OutputStrea for (int y = 0; y < blockSet.y_length; y++) { for (int z = 0; z < blockSet.z_height; z++) { int index = (int) (y * (blockSet.x_width) * (blockSet.z_height) + z * (blockSet.x_width) + x); - BlockData bd = blockSet.blocks.get(indexer);//blockAt(x, y, z); + BlockData bd = blockSet.blocks[indexer];//blockAt(x, y, z); indexer++; int matId = NMSHandler.getBlockHelper().idFor(bd.getMaterial()); if (matId > 255) { @@ -189,7 +186,6 @@ public static void saveMCEditFormatToStream(CuboidBlockSet blockSet, OutputStrea for (Map.Entry entry : rawTag.getValue().entrySet()) { values.put(entry.getKey(), entry.getValue()); } - // TODO: ??? -> values.put("id", new StringTag(null)); // block.getNbtId() values.put("x", new IntTag(x)); values.put("y", new IntTag(y)); values.put("z", new IntTag(z)); diff --git a/plugin/src/main/java/com/denizenscript/denizen/utilities/blocks/SpongeSchematicHelper.java b/plugin/src/main/java/com/denizenscript/denizen/utilities/blocks/SpongeSchematicHelper.java new file mode 100644 index 0000000000..9be2ad755f --- /dev/null +++ b/plugin/src/main/java/com/denizenscript/denizen/utilities/blocks/SpongeSchematicHelper.java @@ -0,0 +1,127 @@ +package com.denizenscript.denizen.utilities.blocks; + +import com.denizenscript.denizen.nms.NMSHandler; +import com.denizenscript.denizen.nms.interfaces.BlockData; +import com.denizenscript.denizen.nms.util.jnbt.*; +import com.denizenscript.denizen.utilities.debugging.Debug; +import org.bukkit.util.BlockVector; + +import java.io.InputStream; +import java.io.OutputStream; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.zip.GZIPInputStream; + +public class SpongeSchematicHelper { + + // Referenced from WorldEdit source and Sponge schematic format v2 documentation + public static CuboidBlockSet fromSpongeStream(InputStream is) { + CuboidBlockSet cbs = new CuboidBlockSet(); + try { + NBTInputStream nbtStream = new NBTInputStream(new GZIPInputStream(is)); + NamedTag rootTag = nbtStream.readNamedTag(); + nbtStream.close(); + if (!rootTag.getName().equals("Schematic")) { + throw new Exception("Tag 'Schematic' does not exist or is not first!"); + } + CompoundTag schematicTag = (CompoundTag) rootTag.getTag(); + Map schematic = schematicTag.getValue(); + short width = getChildTag(schematic, "Width", ShortTag.class).getValue(); + short length = getChildTag(schematic, "Length", ShortTag.class).getValue(); + short height = getChildTag(schematic, "Height", ShortTag.class).getValue(); + int originX = 0; + int originY = 0; + int originZ = 0; + try { + // Note: "Offset" contains complete nonsense from WE, so just don't touch it. + int[] offsetArr = getChildTag(schematic, "DenizenOffset", IntArrayTag.class).getValue(); + originX = offsetArr[0]; + originY = offsetArr[1]; + originZ = offsetArr[2]; + } + catch (Exception e) { + // Default origin, why not + } + cbs.x_width = width; + cbs.z_height = length; + cbs.y_length = height; + cbs.center_x = originX; + cbs.center_y = originY; + cbs.center_z = originZ; + cbs.blocks = new BlockData[width * length * height]; + Map paletteMap = getChildTag(schematic, "Palette", CompoundTag.class).getValue(); + HashMap palette = new HashMap<>(256); + for (String key : paletteMap.keySet()) { + int id = getChildTag(paletteMap, key, IntTag.class).getValue(); + ModernBlockData data = NMSHandler.getBlockHelper().parseBlockData(key); + palette.put(id, data); + } + Map> tileEntitiesMap = new HashMap<>(); + if (schematic.containsKey("TileEntities")) { + List tileEntities = getChildTag(schematic, "TileEntities", ListTag.class).getValue(); + for (Tag tag : tileEntities) { + if (!(tag instanceof CompoundTag)) { + continue; + } + CompoundTag t = (CompoundTag) tag; + int[] pos = getChildTag(t.getValue(), "Pos", IntArrayTag.class).getValue(); + int x = pos[0]; + int y = pos[1]; + int z = pos[2]; + BlockVector vec = new BlockVector(x, y, z); + tileEntitiesMap.put(vec, t.getValue()); + } + } + byte[] blocks = getChildTag(schematic, "BlockData", ByteArrayTag.class).getValue(); + int i = 0; + int index = 0; + while (i < blocks.length) { + int value = 0; + int varintLength = 0; + while (true) { + value |= (blocks[i] & 127) << (varintLength++ * 7); + if (varintLength > 5) { + throw new Exception("Schem file blocks tag data corrupted"); + } + if ((blocks[i] & 128) != 128) { + i++; + break; + } + i++; + } + BlockData block = NMSHandler.getBlockHelper().getBlockData(palette.get(value)); + int y = index / (width * length); + int z = (index % (width * length)) / width; + int x = (index % (width * length)) % width; + int cbsIndex = z + y * cbs.z_height + x * cbs.z_height * cbs.y_length; + BlockVector pt = new BlockVector(x, y, z); + if (tileEntitiesMap.containsKey(pt)) { + CompoundTag otag = NMSHandler.getInstance().createCompoundTag(tileEntitiesMap.get(pt)); + block.setCompoundTag(otag); + } + cbs.blocks[cbsIndex] = block; + index++; + } + } + catch (Exception e) { + Debug.echoError(e); + } + return cbs; + } + + private static T getChildTag(Map items, String key, Class expected) throws Exception { + if (!items.containsKey(key)) { + throw new Exception("Schem file is missing a '" + key + "' tag"); + } + Tag tag = items.get(key); + if (!expected.isInstance(tag)) { + throw new Exception(key + " tag is not of tag type " + expected.getName()); + } + return expected.cast(tag); + } + + public static void saveToSpongeStream(CuboidBlockSet blockSet, OutputStream os) { + // TODO + } +} diff --git a/v1_13/src/main/java/com/denizenscript/denizen/nms/v1_13/helpers/BlockHelperImpl.java b/v1_13/src/main/java/com/denizenscript/denizen/nms/v1_13/helpers/BlockHelperImpl.java index c1a93a4838..f48383d43c 100644 --- a/v1_13/src/main/java/com/denizenscript/denizen/nms/v1_13/helpers/BlockHelperImpl.java +++ b/v1_13/src/main/java/com/denizenscript/denizen/nms/v1_13/helpers/BlockHelperImpl.java @@ -20,6 +20,7 @@ import org.bukkit.craftbukkit.v1_13_R2.block.CraftBlockEntityState; import org.bukkit.craftbukkit.v1_13_R2.block.CraftBlockState; import org.bukkit.craftbukkit.v1_13_R2.block.CraftSkull; +import org.bukkit.craftbukkit.v1_13_R2.block.data.CraftBlockData; import org.bukkit.craftbukkit.v1_13_R2.inventory.CraftItemStack; import org.bukkit.craftbukkit.v1_13_R2.util.CraftLegacy; import org.bukkit.event.world.PortalCreateEvent; @@ -41,6 +42,12 @@ public List getBlocksList(PortalCreateEvent event) { return blocks; } + @Override + public ModernBlockData parseBlockData(Material material, String otherData) { + CraftBlockData data = CraftBlockData.newData(material, otherData); + return new ModernBlockData(data); + } + public T getTE(CraftBlockEntityState cbs) { try { Field f = CraftBlockEntityState.class.getDeclaredField("tileEntity"); diff --git a/v1_14/src/main/java/com/denizenscript/denizen/nms/v1_14/helpers/BlockHelperImpl.java b/v1_14/src/main/java/com/denizenscript/denizen/nms/v1_14/helpers/BlockHelperImpl.java index b33532e1e2..c431a2e246 100644 --- a/v1_14/src/main/java/com/denizenscript/denizen/nms/v1_14/helpers/BlockHelperImpl.java +++ b/v1_14/src/main/java/com/denizenscript/denizen/nms/v1_14/helpers/BlockHelperImpl.java @@ -49,6 +49,12 @@ public class BlockHelperImpl implements BlockHelper { craftBlockEntityState_tileEntity = f; } + @Override + public ModernBlockData parseBlockData(Material material, String otherData) { + CraftBlockData data = CraftBlockData.newData(material, otherData); + return new ModernBlockData(data); + } + @Override public List getBlocksList(PortalCreateEvent event) { List blocks = new ArrayList<>();