Pasting/loading Schematics

Discussion in 'Resources' started by p000ison, Jul 18, 2012.

Thread Status:
Not open for further replies.
  1. Offline

    p000ison

    Heyho Community,
    some time ago I tried pasting schematics and except the lightning issues all is ok.
    You will need this library: http://jnbt.svn.sourceforge.net/viewvc/jnbt/
    Some methods are from WorldEdit. I only want to show how its possible without. Hope this is ok :p

    Schematic:
    Code:
    /*
    *
    *    This class is free software: you can redistribute it and/or modify
    *    it under the terms of the GNU General Public License as published by
    *    the Free Software Foundation, either version 3 of the License, or
    *    (at your option) any later version.
    *
    *    This class is distributed in the hope that it will be useful,
    *    but WITHOUT ANY WARRANTY; without even the implied warranty of
    *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    *    GNU General Public License for more details.
    *
    *    You should have received a copy of the GNU General Public License
    *    along with this class.  If not, see <http://www.gnu.org/licenses/>.
    *
    */
     
    /**
    *
    * @author Max
    */
    public class Schematic
    {
     
        private byte[] blocks;
        private byte[] data;
        private short width;
        private short lenght;
        private short height;
     
        public Schematic(byte[] blocks, byte[] data, short width, short lenght, short height)
        {
            this.blocks = blocks;
            this.data = data;
            this.width = width;
            this.lenght = lenght;
            this.height = height;
        }
     
        /**
        * @return the blocks
        */
        public byte[] getBlocks()
        {
            return blocks;
        }
     
        /**
        * @return the data
        */
        public byte[] getData()
        {
            return data;
        }
     
        /**
        * @return the width
        */
        public short getWidth()
        {
            return width;
        }
     
        /**
        * @return the lenght
        */
        public short getLenght()
        {
            return lenght;
        }
     
        /**
        * @return the height
        */
        public short getHeight()
        {
            return height;
        }
    }
    
    Loading/pasting methods:
    Code:
        public static void pasteSchematic(World world, Location loc, Schematic schematic)
        {
            byte[] blocks = schematic.getBlocks();
            byte[] blockData = schematic.getData();
     
            short length = schematic.getLenght();
            short width = schematic.getWidth();
            short height = schematic.getHeight();
     
            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;
                        Block block = new Location(world, x + loc.getX(), y + loc.getY(), z + loc.getZ()).getBlock();
                        block.setTypeIdAndData(blocks[index], blockData[index], true);
                    }
                }
            }
        }
     
        public static Schematic loadSchematic(File file) throws IOException
        {
            FileInputStream stream = new FileInputStream(file);
            NBTInputStream nbtStream = new NBTInputStream(new GZIPInputStream(stream));
     
            CompoundTag schematicTag = (CompoundTag) nbtStream.readTag();
            if (!schematicTag.getName().equals("Schematic")) {
                throw new IllegalArgumentException("Tag \"Schematic\" does not exist or is not first");
            }
     
            Map<String, Tag> schematic = schematicTag.getValue();
            if (!schematic.containsKey("Blocks")) {
                throw new IllegalArgumentException("Schematic file is missing a \"Blocks\" tag");
            }
     
            short width = getChildTag(schematic, "Width", ShortTag.class).getValue();
            short length = getChildTag(schematic, "Length", ShortTag.class).getValue();
            short height = getChildTag(schematic, "Height", ShortTag.class).getValue();
     
            String materials = getChildTag(schematic, "Materials", StringTag.class).getValue();
            if (!materials.equals("Alpha")) {
                throw new IllegalArgumentException("Schematic file is not an Alpha schematic");
            }
     
            byte[] blocks = getChildTag(schematic, "Blocks", ByteArrayTag.class).getValue();
            byte[] blockData = getChildTag(schematic, "Data", ByteArrayTag.class).getValue();
            return new Schematic(blocks, blockData, width, length, height);
        }
     
        /**
        * Get child tag of a NBT structure.
        *
        * @param items The parent tag map
        * @param key The name of the tag to get
        * @param expected The expected type of the tag
        * @return child tag casted to the expected type
        * @throws DataException if the tag does not exist or the tag is not of the
        * expected type
        */
        private static <T extends Tag> T getChildTag(Map<String, Tag> items, String key, Class<T> expected) throws IllegalArgumentException
        {
            if (!items.containsKey(key)) {
                throw new IllegalArgumentException("Schematic file is missing a \"" + key + "\" tag");
            }
            Tag tag = items.get(key);
            if (!expected.isInstance(tag)) {
                throw new IllegalArgumentException(key + " tag is not of tag type " + expected.getName());
            }
            return expected.cast(tag);
        }
     
  2. Offline

    Derthmonuter

    Thanks! This is very helpful.
     
  3. Offline

    p000ison

    np, maybe the lightning is fixed in 1.3 not sure. I hate the lightning atm...
     
  4. Offline

    desht

    p000ison very nice, and has the advantage over my approach of not needing to hook WorldEdit.

    One potential concern is that pasting large schematics will be rather slow, since block.setTypeIdAndData() is not a fast operation - it causes a lot of lighting/physics recalculation for every block that's updated. A faster approach using direct net.minecraft.server calls is possible, where the chunk data is updated directly (very fast) and then chunk update packets are queued up for any players in range of the change. This should also eliminate any lighting problems.

    For an example of this, see the initLighting() and sendClientChanges() methods in https://github.com/desht/ChessCraft.../java/me/desht/chesscraft/regions/Cuboid.java, as well as the setBlockFast() methods in https://github.com/desht/ChessCraft...va/me/desht/chesscraft/blocks/BlockUtils.java (the NMS calls work with CB-1.3.1)

    If you're OK with it, I might incorporate your code into ChessCraft and integrate the faster block updating mechanism described above...
     
  5. Offline

    p000ison

    Possibill but calling the net.minecraft code will make in incompitable for servermods which dont use the net.minecraft code but bukkit. I can post it on github and you can make a pullrequest if you want :p or post here the code
     
  6. Offline

    desht

    Yeah, true. Maybe some kind of subclassing mechanism where the default method of updating a block can be overridden... I'll have a think about it anyway. I'd definitely like to use this class in a future ChessCraft release (credit will of course be given :) ) to reduce external dependencies (and WorldEdit has the same problem with very large schematics - restoring a 200x60x200 region will lag the server badly enough to disconnect everyone, while using NMS calls can do it in about 2-3 seconds).
     
  7. Offline

    Deleted user

    @p000ison
    desht

    One of you may know..

    How exactly do I use this.. I can't find an arguement that loadSchematic() enjoys..
    Lets say my schematic file is in the dataFolder named test.. can one of you
    give me some code on how to load and then paste it using the above code?
     
  8. Offline

    p000ison

    Yeah sure sine I made it :p

    k you have to do something like this: Schematic schematic = TheClassWhereTheLoadMethodsAre.loadSchematic(new File(plugin,getDataFolder(), schematic.schematic);

    TheClassWhereTheLoadMethodsAre.pasteSchematic(World world, Location loc, schematic);
    done :D
     
  9. Offline

    Deleted user

    Code:
    java.lang.NoClassDefFoundError: org/jnbt/NBTInputStream
            at net.HungerCraftGames.HcMain.HcMain.loadSchematic(HcMain.java:439)
            at net.HungerCraftGames.HcMain.HcMain.onEnable(HcMain.java:310)
            at org.bukkit.plugin.java.JavaPlugin.setEnabled(JavaPlugin.java:217)
            at org.bukkit.plugin.java.JavaPluginLoader.enablePlugin(JavaPluginLoader
    .java:337)
            at org.bukkit.plugin.SimplePluginManager.enablePlugin(SimplePluginManage
    r.java:381)
            at org.bukkit.craftbukkit.CraftServer.loadPlugin(CraftServer.java:257)
            at org.bukkit.craftbukkit.CraftServer.enablePlugins(CraftServer.java:239
    )
            at org.bukkit.craftbukkit.CraftServer.<init>(CraftServer.java:191)
            at net.minecraft.server.ServerConfigurationManager.<init>(ServerConfigur
    ationManager.java:55)
            at net.minecraft.server.MinecraftServer.init(MinecraftServer.java:158)
            at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:424)
            at net.minecraft.server.ThreadServerApplication.run(SourceFile:492)
    Caused by: java.lang.ClassNotFoundException: org.jnbt.NBTInputStream
            at java.net.URLClassLoader$1.run(Unknown Source)
            at java.net.URLClassLoader$1.run(Unknown Source)
            at java.security.AccessController.doPrivileged(Native Method)
            at java.net.URLClassLoader.findClass(Unknown Source)
            at org.bukkit.plugin.java.PluginClassLoader.findClass(PluginClassLoader.
    java:41)
            at org.bukkit.plugin.java.PluginClassLoader.findClass(PluginClassLoader.
    java:29)
            at java.lang.ClassLoader.loadClass(Unknown Source)
            at java.lang.ClassLoader.loadClass(Unknown Source)
            ... 12 more
    >
    
     
  10. Offline

    p000ison

    You need to include the nbt lib in your jar you export. Compile the src of the jnbt and add it to your jar.
     
  11. Offline

    Deleted user

    Sorry for being such a noob, but could you go into detail on how to do this?
     
  12. Offline

    libraryaddict

    Is this guide outdated?

    When I attempt to load a freshly made schematic which was saved using worldedit.
    It spews up a "Not a GZIP file"

    Edit.

    For some reason switching the library to the one worldedit uses fixed the issue
     
  13. Offline

    slater96

    I'm not sure how to import the libary, never done it before. Is there a tutorial or any help you could give me?
    Thanks
     
  14. Offline

    p000ison

    Its very simple: Just add the class and the methods above to your project. Download this: http://jnbt.svn.sourceforge.net/viewvc/jnbt/?view=tar and add everything from the src/ folder to your project, too. Done :p
     
  15. Offline

    slater96

    Ok, thanks for the reply. On getChildTag i get an error which is saying
    Bound mismatch: The generic method getChildTag(Map<String,Tag>, String, Class<T>) of type SchematicPaste is not applicable for the arguments (Map<String,Tag>, String, Class<ShortTag>). The inferred type ShortTag is not a valid substitute for the bounded parameter <T extends Tag>
    And also a yellow underline on
    private static <T extends Tag> T getChildTag(Map<String, Tag> items,
    saying
    The type parameter T should not be bounded by the final type Tag. Final types cannot be further extended
    Am I doing something wrong? I imported all of the jnbt libary.
     
  16. Offline

    evilmidget38

    Tag is a final type, meaning nothing can extend it. Therefore, I don't see any reason why that should be a generic method. Just use Tag in place of where you would use the generic type argument.
     
  17. Offline

    JeroenV

    I'm getting this error:
    I've never added a library before but I just copied the files from http://jnbt.svn.sourceforge.net/viewvc/jnbt/?view=tarover to my java project and then somewhere in my own code used the methods:

    Code:
    Schematic schematic = Schematic.loadSchematic(new File(SchematicFolder,"colorballon.schematic"));
     
    Schematic.pasteSchematic(world,loc,schematic);
     
    
    and then it spewed those errors, I'm not getting any errors in my code so I assume it has all the resources it needs, can anyone help me out?
     
  18. Offline

    p000ison

    hmm seems like something is wrong with the nbt lib or the schematic is corrupted. You could also try to include the lib which worldedit uses
     
  19. Offline

    JeroenV

    @p000ison

    How should I go about that?

    EDIT:
    I'm not sure but during my long journey googling stuff about this I read somewhere that worldedit (something)-zips their files. Perhaps that's the problem, it still looks like a normal schematic file though.
     
  20. Offline

    p000ison

    sry dont have enough time atm. But ill look into it :/
     
  21. Offline

    Latzafs

    Since the last thing I've seen is a month ago, is this still a working method?

    Where would you have to put this schematic so this can grab it?

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: Dec 21, 2015
  22. Offline

    Uniclaw

    Awesome! But how can i get all the Blocks from the SChematic? getBlocks returns a Byte array and not a block array..
     
  23. Offline

    p000ison

    just call for example: block.setTypeId(blocks[1]); for example to get the second block
     
  24. Offline

    Uniclaw

    p000ison Thanks! So if i would iterate trough all blocks, i just must do a thing like for(int i = 0; i < blocks.length; i++){Block b = //what can i do here?

    Because i've this method, and i need a Location Array..
    Code:
    public void generate(Location[] loc){
      for(Location l : loc){
      if(l.getBlock().getType() == Material.SIGN || l.getBlock().getType() == Material.SIGN_POST || l.getBlock().getType() == Material.WALL_SIGN){
      Sign s = (Sign) l.getBlock().getState();
      if(s.getLine(1).equalsIgnoreCase("ore")){
      ore.add(l);
      l.getBlock().setTypeIdAndData(15, (byte) Randomizer(), true);
      }
      }
      }
        }
     
  25. Offline

    p000ison

    what are you trying to do? the block array only contains the block types
     
  26. Offline

    Uniclaw

    It's already solved ;) I've tryed to change all signs from the schematic to woolblocks
     
  27. Offline

    bob7

    Getting the same error!
     
  28. Offline

    p000ison

    Have you tried multiple schematic saves? maybe there are a few differences between them. Not sure, but when I wrote this it worked.
     
  29. Offline

    bob7

    Yeah but it's fine, my plugin is private and i already use worldedit for everything haha.
     
    Cybermaxke likes this.
  30. Offline

    notrodash

    Hello,
    I need to load several schematics and use this code to do just that. However, there is a problem with some of my schematics that do not paste properly. I found a way around this by wrapping the contents of the most inner for loop in a try catch block. After this modification, I was able to paste more of my schematic but some blocks were missing. The stack trace said this:
    Code:
    java.lang.ArrayIndexOutOfBoundsException: -111
    Here is my code:
    Code:
    public static void assembleSchematic(Location loc, Schematic schematic)
        {
            byte[] blocks = schematic.getBlocks();
            byte[] blockData = schematic.getData();
     
            short length = schematic.getLength();
            short width = schematic.getWidth();
            short height = schematic.getHeight();
           
            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;
                        try{
                            Block block = new Location(loc.getWorld(), x + loc.getBlockX(), y + loc.getBlockY(), z + loc.getBlockZ()).getBlock();
                            block.setTypeIdAndData(blocks[index], blockData[index], true);
                        }catch(Exception e){
                            e.printStackTrace();
                        }
                    }
                }
            }
        }

    I'm assuming that my schematic file is corrupt, but further clarification would be appreciated!
     
Thread Status:
Not open for further replies.

Share This Page