Solved Best way to create structures

Discussion in 'Spigot Plugin Development' started by Jaume, Apr 25, 2017.

  1. How can I generate block structures in my plugin? I don't care about using external libs. I've looked at WorldEdit, but I don't quite understand how to load and paste schematics. I've also been reading posts here and there, but they aren't solved. Btw I'm not asking for code but for ways to implement it.
     
  2. One of the variants could be a HashMap saving Location as Key and Block as Value, async'ly (Bukkit scheduler?) setting Blocks in the world
     
  3. if you're working from worldedit schematics this is pretty easy to do,
    make your structure, then save it as a schematic using //schem save <name>

    once you have a schematic you can load it back into the world through your plugin by doing the following:

    1) this code is pulled from one of my plugins, thus you won't just be able to copy/paste it and expect it to work. I did comment it so that you can see what each line is doing however.
    2) feel free to modify this to fit your plugin, the only real issue here is converting where you want to paste it x/y/z coords to Block vector coords. I have also included the imports you'll need as you nee BlockVector VECTOR not org.bukkit.vector


    Code (Text):
         
    //IMPORTS
    import com.sk89q.worldedit.CuboidClipboard;
    import com.sk89q.worldedit.EditSession;
    import com.sk89q.worldedit.MaxChangedBlocksException;
    import com.sk89q.worldedit.Vector;
    import com.sk89q.worldedit.bukkit.BukkitWorld;

    //loading code
            //get the world in which you wish to assign it to:
            World world = //whatever world you want this schematic to be loaded into.
            String schemName = //name of your schematic.
            Vector minx = region.getMinimumPoint();     //my code loads schematics into worldguard regions, to use a blockvector you'll also have to import worldguard.   You can also get a blockvector from regular xyz coords, see wg documentation.


           
                //load the file from the schematic folder.
                File file = new File("plugins/WorldEdit/schematics/" +schemName + ".schematic");

                BukkitWorld BW = new BukkitWorld(world);   //convert the world to a BukkitWorld variable.

                @SuppressWarnings("deprecation")
                EditSession es = new EditSession(BW, 2000000);   //All worldedit actions have to use a EditSession.

                CuboidClipboard c =null;  //this is for loading the schematic into a fake clipboard.
                try {
                    c = CuboidClipboard.loadSchematic(file);
                } catch (Exception e) {
                 
                    System.out.println("we failed to load the proper schematic!  " + schemName);
                    e.printStackTrace();
                }

                try {
                    c.place(es, minx, false);   //this will try placing the schematic at the xPoint we laid out earlier.
                    //the false variable here should be used if you do NOT want to paste air blocks.  Depending on what you are doing you may need to change it to true.

                } catch (MaxChangedBlocksException e) {
                    System.out.println("Schematic was larger than the maximum allowed size!");
                    e.printStackTrace();
                }

                System.out.println("NOTICE:  Schematic: " + schemName + " has been loaded");



     
     
    • Winner Winner x 1
    • Useful Useful x 1
  4. Would this read a schematic file? (I don't know how are they structured)
    Or were you thinking about manually updating the location and generating the next block of the structure? Because I've already tried that and it's what I'm trying to avoid.
     
  5. Whoaaa thanks! I've got several questions regarding your answer:
    1.- According to your code, I need WorldGuard. Is there a quick and easy workaround or should I just use it?
    2.- How can it paste a schematic file which is being held by a null worldedit clipboard?
    3.- (Not so important) Does Bukkit broadcast messages via System.out.println()? Because I always use Bukkit.broadcastMessage()...
     
  6. 3 They are different
    System.out.println is seen only in console
     
  7. 1) this is based upon using worldedit / worldguard. If you're not using regions you may be able to omit the worldguard dependency but you DO need blockvectors to get the start point passed into wordedit paste.

    2) it creates just a clipboard instance owned by no one it will work.

    3) as mentioned above system.out just puts it in the console/system log file.



    Sent from my iPhone using Tapatalk
     
    • Agree Agree x 1
  8. Thanks, I'm now going to try it out!

    Edit: Will deprecated methods ever get removed?
     
    #8 Jaume, Apr 26, 2017
    Last edited: Apr 26, 2017
  9. Choco

    Moderator

    If you want to make a structure on your own without external libraries (such as WorldEdit, which every server has loaded anyways... doesn't matter if you use it or not), you also have the option of creating an object of your own. I was making a plugin a while back that required multi-block structure detection, and I used a Structure object (or something similar. I cannot remember the name) which contained a 3 dimensional array of MaterialData objects. (MaterialData[][][]). The three dimensions obviously contained the x, y and z coordinates relative to the place they were located.

    You could either use WorldEdit, or try and write your own, more basic, implementation of a Structure class to make things a little easier for yourself. That would also allow you to expand upon the structures if you really needed to, but it's up to you. All depends on what you prefer ^-^
     
    • Informative Informative x 1
  10. The WorldEdit option is now working. I had already thought about a three dimensional array, but how would you save it? I guess I'd need to create a file for each structure (which would be the equivalent to a schematic).
     
    • Agree Agree x 1
  11. Choco

    Moderator

    Hey, as long as you have everything working, that's a win ^-^ Most servers have WorldEdit installed either way, so it's not a large dependency to lug around on your plugin. If you were to take the other approach however, yes, you would have to serialize it to file somehow in whatever format you'd like. That is exactly how schematics in WorldEdit work. They have their own ".schem" file extension and they just write their own format to store data about the blocks / entities in the saved region
     
    • Informative Informative x 1
  12. They could be removed in the future but they typically won't be removed until there is a viable replacement.


    Sent from my iPhone using Tapatalk
     
  13. So why are they marking methods as deprecated while there's a viable replacement? xD
     
  14. Depends on the developer. If you mess with minecraft code at all you're going to run into that ALOT. There are tons of deprecated methods with no viable replacements.

    And there are several instances where the new method is a lot slower than the deprecated method.


    Sent from my iPhone using Tapatalk
     
  15. I'm getting there! I've managed to register the three dimensional array of Material into a file, but it just repetidely writes this line: "[[[Lorg.bukkit.Material;@258edf73" if using .toString(); and this one: "[[[Org.bukkit.Material;@77e7fdae" if directly getting the value of the array at each 3 dimensional location. (using three for loops inside each other)

    EDIT: Not using MaterialData as you said because I don't really know how to use it (never heard of it before, reading on the docs looks like it's specific data for signs and similar blocks).
     
  16. How are you writing those to the config? Just writing the block won't be helpful, you can get its material, data,location and store those then read them back in when you spawn the objects.

    I suggest going with the working worldedit version because you already have it on the server and you don't have to recreate schematic saving, loading and pasting operations.

    No real need to reinvent the wheel especially if you are going to have worldedit installed anyway!


    Sent from my iPhone using Tapatalk
     
    • Agree Agree x 1
  17. Choco

    Moderator

    Well calling #toString() on an Array returns its memory address, if I'm not mistaken. Same thing may go for the MaterialData. To save it to a file, you would have to iterate over all the arrays (keep in mind that a 3 dimensional array is just an array of an array of arrays - 3 sets of arrays), and save the MaterialData in a custom way. I also recommend using MaterialData over Materials because what if you want to place Andesite, for example. It has a Material, STONE, as well as a byte data value, 1, to determine its state (because Mojang likes byte data).
    Code (Java):
    for (MaterialData[][] dataArray2 : materials) {
        for (MaterialData[] dataArray : dataArray2) {
            for (MaterialData material : dataArray) {
                // Obviously rather than printing, you would save this to a file
                // It would also be in the format that you would like, but I just printed data as an example
                System.out.println(material.getItemType() + ":" + material.getData());
            }
        }
    }

    But yes, if you have WorldEdit already figured out, I also highly recommend this way as well ;) I just wanted to chime in my method of doing things in case anyone chose to do it my way in the future when seeing this thread
     
  18. I like doing it this way because it won't need dependencies and because it improves my (at the moment) poor programming skills ;D
    It was a dumb error, I was writing materials and not materials[x][y][z]. It now prints the name of the materials, as I'm using materials[x][y][z].name(); to make it easier to read.

    The problem with MaterialData is that I don't know how to get it form a block. While getBlock().getType() returns a Material constant, I can't find a method which returns MaterialData from a location or block.

    EDIT: Just to update, I now have this code (simplified):
    Code (Text):
    public void writeStructure() throws IOException {
           
            File dataFolder = plugin.getDataFolder();
            if (!dataFolder.exists()) {
                dataFolder.mkdir();
            }
           
            File saveTo = new File(dataFolder, "structure.txt");
            if(!saveTo.exists())
            {
                try {
                    saveTo.createNewFile();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
           
            FileWriter fw = new FileWriter(saveTo, true);
            PrintWriter pw = new PrintWriter(fw);

            Material[][][] materials = new Material[10][10][10];
           
            for (int x = 0; x <= 9; x++) {
                for (int y = 0; y <= 9; y++) {
                    for (int z = 0; z <= 9; z++) {
                        materials[x][y][z] = new Location(Bukkit.getWorld("world"), x, y, z).getBlock().getType();
                        pw.println(materials[x][y][z].name() + " x: " + x + " y: " + y + " z: " + z);
                    }
                }
            }
            pw.close();
        }
     
    And it outputs this file: (only including a few lines there are 1000 (10x10x10))
    Code (Text):
    AIR x: 0 y: 0 z: 0
    BEDROCK x: 0 y: 0 z: 1
    BEDROCK x: 0 y: 0 z: 2
    BEDROCK x: 0 y: 0 z: 3
    BEDROCK x: 0 y: 0 z: 4
    BEDROCK x: 0 y: 0 z: 5
    BEDROCK x: 0 y: 0 z: 6
    BEDROCK x: 0 y: 0 z: 7
    BEDROCK x: 0 y: 0 z: 8
    BEDROCK x: 0 y: 0 z: 9
    AIR x: 0 y: 1 z: 0
    BEDROCK x: 0 y: 1 z: 1
    BEDROCK x: 0 y: 1 z: 2
    BEDROCK x: 0 y: 1 z: 3
    BEDROCK x: 0 y: 1 z: 4
    BEDROCK x: 0 y: 1 z: 5
    BEDROCK x: 0 y: 1 z: 6
    BEDROCK x: 0 y: 1 z: 7
    BEDROCK x: 0 y: 1 z: 8
    BEDROCK x: 0 y: 1 z: 9
    AIR x: 0 y: 2 z: 0
    BEDROCK x: 0 y: 2 z: 1
    BEDROCK x: 0 y: 2 z: 2
    IRON_ORE x: 0 y: 2 z: 3
    BEDROCK x: 0 y: 2 z: 4
    STONE x: 0 y: 2 z: 5
    BEDROCK x: 0 y: 2 z: 6
    BEDROCK x: 0 y: 2 z: 7
    STONE x: 0 y: 2 z: 8
    STONE x: 0 y: 2 z: 9
    AIR x: 0 y: 3 z: 0
    BEDROCK x: 0 y: 3 z: 1
    BEDROCK x: 0 y: 3 z: 2
    STONE x: 0 y: 3 z: 3
    BEDROCK x: 0 y: 3 z: 4
    STONE x: 0 y: 3 z: 5
    STONE x: 0 y: 3 z: 6
    STONE x: 0 y: 3 z: 7
    STONE x: 0 y: 3 z: 8
    STONE x: 0 y: 3 z: 9
    AIR x: 0 y: 4 z: 0
    As you can see I'm currently writing the coordinates just to debug. I'm also using coordinates 0 to 10, so I'm registering the 10x10x10 block starting at coords 0 0 0 (just to make it work, I'll later change it so its flexible).

    Is there anything I'd have to change?
     
    #18 Jaume, Apr 26, 2017
    Last edited: Apr 26, 2017
  19. Choco

    Moderator

    There are no Block methods that return MaterialData. There is however a method that returns its Material (which you seem to have already found, #getType()), and a method to return its byte data, #getData(). Using those values, just create a new MaterialData() instance with the returned values
    Code (Java):
    Block block = // some block from somewhere
    MaterialData data = new MaterialData(block.getType(), block.getData());
    Do note that #getData() and the constructor are deprecated, but that is because they will likely be removed in 1.13 when block ID changes will inevitably happen. Don't worry about the deprecation for now, and just SuppressWarnings. You can worry about it later when API changes are made.

    I still recommend the WorldEdit way though. It's still relatively easy to use and it's a dependency that most servers have.
     
    • Agree Agree x 1
  20. Thanks, I'll change it tomorrow. How would you write it to the file then? Because if there's data I couldn't write it as I already did (take a look at the edit in my latest reply). If I write it just how it comes, will I be able to read it?