Fri. Apr 19th, 2024

My son has gotten into Minecraft and Star Wars. He wanted the two of us to build the Death Star in Minecraft. That is not a small undertaking. I got thinking about it, and came up with the idea to use Java to create function files that would create the sphere for us. So I present here the code for doing so.

First thing was to create some constants for the block names that I would be using.

public static final String CONCRETE = "concrete";
public static final String NETHER_BRICK_FENCE = "nether_brick_fence";
public static final String GLASS = "glass";
public static final String SEALANTERN = "sealantern";
public static final String BARRIER = "barrier";
public static final String AIR = "air";
public static final String MAGMA = "magma";
public static final String BLUE_CONCRETE = "concrete 3";
public static final String EMERALD = "emerald_block";

Next I needed to define a block placement. So I created the Block class.

public class Block {
        int x;
        int y;
        int z;
        String type;
        String description;

        public Block(Double x, Double y, Double z, String type, int px, int py, int pz, String description) {
            this.x = x.intValue() + px;
            this.y = y.intValue() + py;
            this.z = z.intValue() + pz;
            this.type = type;
            this.description = description;
        }

        public Block(int x, int y, int z, String type, String description) {
            this.x = x;
            this.y = y;
            this.z = z;
            this.type = type;
            this.description = description;
        }

        public int getX() {
            return x;
        }

        public void setX(int x) {
            this.x = x;
        }

        public int getY() {
            return y;
        }

        public void setY(int y) {
            this.y = y;
        }

        public int getZ() {
            return z;
        }

        public void setZ(int z) {
            this.z = z;
        }

        public String getType() {
            return type;
        }

        public void setType(String type) {
            this.type = type;
        }

        public String getDescription() {
            return description;
        }

        public void setDescription(String description) {
            this.description = description;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Block that = (Block) o;
            return x == that.x &&
                    y == that.y &&
                    z == that.z;
        }

        @Override
        public int hashCode() {
            return Objects.hash(x, y, z);
        }

        public String toCommand() {
            return "setblock " + x + " " + z + " " + y + " " + type;
        }

        @Override
        public String toString() {
            return "Block{" +
                    "x=" + x +
                    ", y=" + y +
                    ", z=" + z +
                    ", type='" + type + '\'' +
                    ", description='" + description + '\'' +
                    '}';
        }
    }

The Block class has two constructors. One that takes a relative set of coordinates and a second that takes a specific set of coordinates. The toCommand() is used to output the command that is needed to set the block.

I implemented the equals() to check if a block exists in that location already, as I didn’t want to have redundant setblock calls.

Now to maintain my list of Blocks that I have created, I use a synchronizedList, as I use streams and parrellel streams to speed things up.

List<Block> blocks = Collections.synchronizedList(new ArrayList<>());

Now for reference on my variables I used these as my center point for building:

double radius = 100.0D;
int px = 500;
int py = 500;
int pz = 120;

Radius of 100 is pretty big, and I do mean pretty big. Building on this scale is slow, but it pays off in the end.

Now then to create the sphere is the call I made:

shell(radius, blocks,  px, py, pz, 0.1D);

Here is the implementation of the shell():

private static void shell(double radius, List<Block> blocks, int ppx, int ppy, int ppz, double increment)  {
        ArrayList<Double> angles = new ArrayList<>();
        for(double angle = 0; angle < 360; angle = angle + increment) {
            angles.add(angle);
        }

        final ArrayList<Double> angleX = angles;
        final ArrayList<Double> angleY = angles;


        angleX.parallelStream().forEach(px -> {
            angleY.stream().forEach(py -> {
                double x = radius * Math.sin(px)*Math.cos(py);
                double y = radius * Math.sin(px) * Math.sin(py);
                double z = radius * Math.cos(px);

                Block block = new Block(x,y,z, CONCRETE, ppx, ppy, ppz,"Shell");
                if(block.getX() % 10 == 0 || block.getY() % 10 == 0 || block.getZ() %10 == 0) {
                    block.setType(BLUE_CONCRETE);
                }

                if(!blocks.contains(block)) {
                    blocks.add(block);
                }
            });
        });

        System.out.print("\n");
    }

Now you can see here I have special code in that would change from Concrete to Blue Concrete in a grid pattern. This gave the sphere more depth, and feel free to change this as you wish.

Now not being satisfied and knowing I had many more blocks to lay down I created a method to generate a hollow horizontal cylinder:

horizontal_cylinder(25,3, -3,  blocks, CONCRETE, px, py, pz, 0.1D,"Core");

Here is the implementation:

private static void horizontal_cylinder(double radius, double top, double bottom, List<Block> blocks, String type, int px, int py, int pz, double increment, String description) {
        ArrayList<Double> angles = new ArrayList<>();
        for(double angle = 0; angle < 360; angle = angle + increment) {
            angles.add(angle);
        }

        angles.parallelStream().forEach(angle -> {
            for(double z = top; z >= bottom ; z = z - 1.0D) {
                Block block = new Block(radius * Math.cos(angle),
                        radius * Math.sin(angle),
                        z,
                        type, px, py, pz, description);

                if (!blocks.contains(block)) {
                    blocks.add(block);
                }
            }
        });
    }

Also for good measure I create a method to remove blocks in a cylindrical area:

private static void clear_cylinder(double radius, double top, double bottom,List<Block> blocks, int px, int py, int pz, double increment, String description) {

        ArrayList<Double> angles = new ArrayList<>();
        for(double angle = 0; angle < 360; angle = angle + increment) {
            angles.add(angle);
        }
        angles.parallelStream().forEach(angle -> {
            for(double z = top; z >= bottom ; z = z - 1.0D) {
                for(int i = 1; i<= radius; i++) {
                    Block block = new Block(i * Math.cos(angle),
                            i* Math.sin(angle),
                            z,
                            AIR, px, py, pz, description);

                    if (blocks.contains(block)) {
                        blocks.remove(block);
                    }
                }
            }
        });
        System.out.print("\n");
    }

Now to have a method to crate vertical cylinder:

private static void vertical_cylinder(double radius, double top, double bottom, List<Block> blocks, String type, int px, int py, int pz, double increment, String description) throws IOException {
        ArrayList<Double> angles = new ArrayList<>();
        for(double angle = 0; angle < 360; angle = angle + increment) {
            angles.add(angle);
        }

        angles.parallelStream().forEach(angle -> {
            for(double y = top; y >= bottom ; y = y - 1.0D) {
                Block block = new Block(radius * Math.cos(angle),
                        y,
                        radius * Math.sin(angle),
                        type, px, py, pz, description);

                if (!blocks.contains(block)) {
                    blocks.add(block);
                }
            }
        });
    }

And again needed a method to clear a vertical cylindrical area:

private static void clear_vertical_cylinder(double radius, double top, double bottom,List<Block> blocks, int px, int py, int pz, double increment) throws IOException {
        ArrayList<Double> angles = new ArrayList<>();
        for(double angle = 0; angle < 360; angle = angle + increment) {
            angles.add(angle);
        }

        angles.parallelStream().forEach(angle -> {
            for(double y = top; y >= bottom ; y = y - 1.0D) {
                for(int i = 1; i < radius; i++) {
                    Block block = new Block(i * Math.cos(angle),
                            y,
                            i * Math.sin(angle),
                            AIR, px, py, pz, "clear_laser");
                    if (blocks.contains(block)) {
                        blocks.remove(block);
                    }
                }
            }
        });
        System.out.print("\n");
    }

Here is a method I used to create the decks for the station.

deck( 90.0D, blocks, "Concrete", px, py, pz, 90D, 270D,stopWatch);
deck( 80.0D, blocks, "Concrete", px, py, pz, 90D, 270D,stopWatch);
deck( 70.0D, blocks, "Concrete", px, py, pz, 90D, 270D,stopWatch);
deck( 60.0D, blocks, "Concrete", px, py, pz, 90D, 270D,stopWatch);
deck( 50.0D, blocks, "Concrete", px, py, pz, 90D, 270D,stopWatch);
deck( 40.0D, blocks, "Concrete", px, py, pz, 90D, 270D,stopWatch);
deck( 30.0D, blocks, "Concrete", px, py, pz, 90D, 270D,stopWatch);
deck( 20.0D, blocks, "Concrete", px, py, pz, 90D, 270D,stopWatch);
deck( -4.0D, blocks, "Concrete", px, py, pz, 45D, 315D,stopWatch);
deck( -10.0D, blocks, "Concrete", px, py, pz, 90D, 270D,stopWatch);
deck( -20.0D, blocks, "Concrete", px, py, pz, 90D, 270D,stopWatch);
deck( -30.0D, blocks, "Concrete", px, py, pz, 90D, 270D,stopWatch);
deck( -40.0D, blocks, "Concrete", px, py, pz, 90D, 270D,stopWatch);
deck( -50.0D, blocks, "Concrete", px, py, pz, 90D, 270D,stopWatch);
deck( -60.0D, blocks, "Concrete", px, py, pz, 90D, 270D,stopWatch);
deck( -70.0D, blocks, "Concrete", px, py, pz, 90D, 270D,stopWatch);
deck( -80.0D, blocks, "Concrete", px, py, pz, 90D, 270D,stopWatch);

Here is the implementation. Now I did set this method up to only create the “deck” over half of the sphere, instead of the entire sphere. 18 decks (17 decks, plus the bottom level) is extremely difficult to fill out!

private static void deck(double z,List<Block> blocks, String type, int px, int py, int pz, double startAngle, double endAngle, StopWatch stopWatch) {
        Optional<Block> max = blocks.stream().filter(block -> (block.getZ() - pz) == z).max(Comparator.comparing(Block::getY));
        if(max.isPresent()) {

            double radius = max.get().getY() - py;
            System.out.println("Starting Deck " + z + ": " + stopWatch.toString() + " Deck Radius: " + radius + " Point:" + max.get().toString());
            String description = "Deck " + z;

            ArrayList<Double> angles = new ArrayList<>();
            for (double angle = startAngle; angle <= endAngle; angle = angle + 0.01D) {
                angles.add(angle);
            }

            angles.parallelStream().forEach(angle -> {
                for (double r = 11; r < radius; r++) {
                    Block block = new Block(r * Math.cos(angle),
                            r * Math.sin(angle),
                            z,
                            type, px, py, pz, description);

                    if (!blocks.contains(block)) {
                        blocks.add(block);

                        if (block.getZ() % 10 == 0 && block.getX() % 10 == 0 && block.getY() % 10 == 0) {
                            Block light = new Block(block.getX(), block.getY(), block.getZ() - 1, SEALANTERN, description);
                            if (!blocks.contains(light)) {
                                blocks.add(light);
                            }
                        }
                    }
                }
            });

            List<Block> removeList = blocks.parallelStream().filter(block -> block.getDescription().equals(description)).filter(block -> block.getY() > 500).collect(Collectors.toList());
            blocks.removeAll(removeList);
        }
    }

Now if you need to build a wall, here is your method. This will build a circular wall at the location with give radius. I used this to divide my station in half. One side being the decks, and the other side the open area where I would build in the super laser and other pieces.

private static void wall(Double radius,List<Block> blocks, String type, int px, int py, int pz) {
        ArrayList<Double> angles = new ArrayList<>();
        for (double angle = 0.0D; angle < 360.0D; angle = angle + 0.01D) {
            angles.add(angle);
        }

        angles.parallelStream().forEach(angle -> {
            for (double r = 11; r < radius; r++) {
                Block block = new Block(r * Math.cos(angle),
                        500D,
                        r * Math.sin(angle),
                        type, px, 0, pz, "wall");

                if (!blocks.contains(block)) {
                    blocks.add(block);
                }
            }
        });
    }

Now if you need to build a line of brick between two places here you :

private static void line(int sx, int sy, int sz, int ex, int ey, int ez, List<Block> blocks, String type, String description) {
        int cx = sx;
        int cy = sy;
        int cz = sz;

        boolean bx = (sx == ex);
        boolean by = (sy == ey);
        boolean bz = (sz == ez);

        do {
            Block block = new Block(cx,cy,cz,type,description);
            if (!blocks.contains(block)) {
                blocks.add(block);
            }

            if(!bx) {
                cx++;
            }
            if(!by) {
                cy++;
            }
            if(!bz) {
                cz++;
            }


            bx = (cx == ex);
            by = (cy == ey);
            bz = (cz == ez);

        } while(!bx || !by || !bz);

    }

Now I wanted to know how many bricks had been put down and what type they were. I created this simple method to generate that list for me.

 private static void printBlockReport(List<Block> blocks) {
        System.out.println("Blocks: " + blocks.size());
        System.out.println("               concrete: " + blocks.stream().filter(block -> block.getType().equalsIgnoreCase(CONCRETE)).count());
        System.out.println("                  glass: " + blocks.stream().filter(block -> block.getType().equalsIgnoreCase(GLASS)).count());
        System.out.println("     nether_brick_fence: " + blocks.stream().filter(block -> block.getType().equalsIgnoreCase(NETHER_BRICK_FENCE)).count());
        System.out.println("             sealantern: " + blocks.stream().filter(block -> block.getType().equalsIgnoreCase(SEALANTERN)).count());
        System.out.println("                  magma: " + blocks.stream().filter(block -> block.getType().equalsIgnoreCase(MAGMA)).count());
        System.out.println("                barrier: " + blocks.stream().filter(block -> block.getType().equalsIgnoreCase(BARRIER)).count());
        System.out.println("                    air: " + blocks.stream().filter(block -> block.getType().equalsIgnoreCase(AIR)).count());
        System.out.println("          blue concrete: " + blocks.stream().filter(block -> block.getType().equalsIgnoreCase(BLUE_CONCRETE)).count());
    }

Now the last piece of code I’m going to share is to take your list of blocks and output them into files for function calls:

 blocks.sort(Comparator.comparing(Block::getX).thenComparing(Block::getY).thenComparing(Block::getZ));

        int pages = ListUtils.pages(blocks,9999);

        for(int page = 1; page <= pages; page++) {
            FileWriter myWriter = new FileWriter("d:\\temp\\station" + page + ".mcfunction");
            for (Block block : ListUtils.page(blocks, 9999, page)) {
                myWriter.write(block.toCommand());
                myWriter.write("\n");
            }
            myWriter.close();
        }

        FileWriter myWriter = new FileWriter("d:\\temp\\block-list.txt");
        for(Block block : blocks) {
            myWriter.write(block.toCommand());
            myWriter.write("\n");
        }
        myWriter.close();
        stopWatch.finish();
        System.out.println("Completed in: " + stopWatch.toString());

You will need to modify this to your code, and file location. It does need to be limited to 9999 as that is the maximum number of commands in a function file.

I’m not going to give you my exact recipe I used for my station. Take these and work with them to see what you can build. If you make any extenstions to this I would love to hear about them. I am thinking of making this into a library to use, if I see there is interest in it.

By Jeffery Miller

I am known for being able to quickly decipher difficult problems to assist development teams in producing a solution. I have been called upon to be the Team Lead for multiple large-scale projects. I have a keen interest in learning new technologies, always ready for a new challenge.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.