From 820e72f17a1f86f983d3bc9c2697f4eeeec73fa5 Mon Sep 17 00:00:00 2001 From: SenseiKiwi Date: Thu, 26 Dec 2013 15:29:20 -0400 Subject: [PATCH 01/16] Added First Step of Maze Generation Added classes for generating maze dungeons. At the moment, the dungeons are generated in place of our pocket dimensions to make testing easy. We'll need to restore pocket generation later and integrate the mazes into the dungeon packs later. Currently, the structures are very incomplete (they don't even have doorways), but this is only the first step. --- .../experimental/MazeGenerator.java | 172 ++++++++++++++++++ .../experimental/SpatialNode.java | 105 +++++++++++ .../mod_pocketDim/world/PocketBuilder.java | 5 + 3 files changed, 282 insertions(+) create mode 100644 src/main/java/StevenDimDoors/experimental/MazeGenerator.java create mode 100644 src/main/java/StevenDimDoors/experimental/SpatialNode.java diff --git a/src/main/java/StevenDimDoors/experimental/MazeGenerator.java b/src/main/java/StevenDimDoors/experimental/MazeGenerator.java new file mode 100644 index 00000000..5eb9e638 --- /dev/null +++ b/src/main/java/StevenDimDoors/experimental/MazeGenerator.java @@ -0,0 +1,172 @@ +package StevenDimDoors.experimental; + +import java.util.Random; + +import net.minecraft.block.Block; +import net.minecraft.util.MathHelper; +import net.minecraft.world.World; +import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.chunk.storage.ExtendedBlockStorage; +import StevenDimDoors.mod_pocketDim.Point3D; + +public class MazeGenerator +{ + public static final int ROOT_WIDTH = 40; + public static final int ROOT_LENGTH = 40; + public static final int ROOT_HEIGHT = 19; + private static final int MIN_HEIGHT = 4; + private static final int MIN_SIDE = 3; + private static final int SPLIT_COUNT = 8; + + private MazeGenerator() { } + + public static void generate(World world, int x, int y, int z, Random random) + { + SpatialNode root = partitionRooms(ROOT_WIDTH, ROOT_HEIGHT, ROOT_LENGTH, SPLIT_COUNT, random); + buildRooms(root, world, new Point3D(x - ROOT_WIDTH / 2, y - ROOT_HEIGHT - 1, z - ROOT_WIDTH / 2)); + + } + + private static SpatialNode partitionRooms(int width, int height, int length, int maxLevels, Random random) + { + SpatialNode root = new SpatialNode(width, height, length); + splitByRandomX(root, maxLevels, random); + return root; + } + + private static void splitByRandomX(SpatialNode node, int levels, Random random) + { + if (node.width() >= 2 * MIN_SIDE) + { + node.splitByX(MathHelper.getRandomIntegerInRange(random, + node.minCorner().getX() + MIN_SIDE, node.maxCorner().getX() - MIN_SIDE + 1)); + + if (levels > 1) + { + splitByRandomZ(node.leftChild(), levels - 1, random); + splitByRandomZ(node.rightChild(), levels - 1, random); + } + } + else if (levels > 1) + { + splitByRandomZ(node, levels - 1, random); + } + } + + private static void splitByRandomZ(SpatialNode node, int levels, Random random) + { + if (node.length() >= 2 * MIN_SIDE) + { + node.splitByZ(MathHelper.getRandomIntegerInRange(random, + node.minCorner().getZ() + MIN_SIDE, node.maxCorner().getZ() - MIN_SIDE + 1)); + + if (levels > 1) + { + splitByRandomY(node.leftChild(), levels - 1, random); + splitByRandomY(node.rightChild(), levels - 1, random); + } + } + else if (levels > 1) + { + splitByRandomY(node, levels - 1, random); + } + } + + private static void splitByRandomY(SpatialNode node, int levels, Random random) + { + if (node.height() >= 2 * MIN_HEIGHT) + { + node.splitByY(MathHelper.getRandomIntegerInRange(random, + node.minCorner().getY() + MIN_HEIGHT, node.maxCorner().getY() - MIN_HEIGHT + 1)); + + if (levels > 1) + { + splitByRandomX(node.leftChild(), levels - 1, random); + splitByRandomX(node.rightChild(), levels - 1, random); + } + } + else if (levels > 1) + { + splitByRandomX(node, levels - 1, random); + } + } + + private static void buildRooms(SpatialNode node, World world, Point3D offset) + { + if (node.isLeaf()) + { + buildBox(world, offset, node.minCorner(), node.maxCorner()); + } + else + { + buildRooms(node.leftChild(), world, offset); + buildRooms(node.rightChild(), world, offset); + } + } + + private static void buildBox(World world, Point3D offset, Point3D minCorner, Point3D maxCorner) + { + int minX = minCorner.getX() + offset.getX(); + int minY = minCorner.getY() + offset.getY(); + int minZ = minCorner.getZ() + offset.getZ(); + + int maxX = maxCorner.getX() + offset.getX(); + int maxY = maxCorner.getY() + offset.getY(); + int maxZ = maxCorner.getZ() + offset.getZ(); + + int x, y, z; + int blockID = Block.stoneBrick.blockID; + + for (x = minX; x <= maxX; x++) + { + for (z = minZ; z <= maxZ; z++) + { + setBlockDirectly(world, x, minY, z, blockID, 0); + setBlockDirectly(world, x, maxY, z, blockID, 0); + } + } + for (x = minX; x <= maxX; x++) + { + for (y = minY; y <= maxY; y++) + { + setBlockDirectly(world, x, y, minZ, blockID, 0); + setBlockDirectly(world, x, y, maxZ, blockID, 0); + } + } + for (z = minZ; z <= maxZ; z++) + { + for (y = minY; y <= maxY; y++) + { + setBlockDirectly(world, minX, y, z, blockID, 0); + setBlockDirectly(world, maxX, y, z, blockID, 0); + } + } + } + + private static void setBlockDirectly(World world, int x, int y, int z, int blockID, int metadata) + { + if (blockID != 0 && Block.blocksList[blockID] == null) + { + return; + } + + int cX = x >> 4; + int cZ = z >> 4; + int cY = y >> 4; + Chunk chunk; + + int localX = (x % 16) < 0 ? (x % 16) + 16 : (x % 16); + int localZ = (z % 16) < 0 ? (z % 16) + 16 : (z % 16); + ExtendedBlockStorage extBlockStorage; + + chunk = world.getChunkFromChunkCoords(cX, cZ); + extBlockStorage = chunk.getBlockStorageArray()[cY]; + if (extBlockStorage == null) + { + extBlockStorage = new ExtendedBlockStorage(cY << 4, !world.provider.hasNoSky); + chunk.getBlockStorageArray()[cY] = extBlockStorage; + } + extBlockStorage.setExtBlockID(localX, y & 15, localZ, blockID); + extBlockStorage.setExtBlockMetadata(localX, y & 15, localZ, metadata); + } +} diff --git a/src/main/java/StevenDimDoors/experimental/SpatialNode.java b/src/main/java/StevenDimDoors/experimental/SpatialNode.java new file mode 100644 index 00000000..0aef08f1 --- /dev/null +++ b/src/main/java/StevenDimDoors/experimental/SpatialNode.java @@ -0,0 +1,105 @@ +package StevenDimDoors.experimental; + +import StevenDimDoors.mod_pocketDim.Point3D; + +public class SpatialNode +{ + private Point3D minCorner; + private Point3D maxCorner; + private SpatialNode leftChild = null; + private SpatialNode rightChild = null; + + public SpatialNode(int width, int height, int length) + { + minCorner = new Point3D(0, 0, 0); + maxCorner = new Point3D(width - 1, height - 1, length - 1); + } + + private SpatialNode(Point3D minCorner, Point3D maxCorner) + { + this.minCorner = minCorner; + this.maxCorner = maxCorner; + } + + public int width() + { + return (maxCorner.getX() - minCorner.getX() + 1); + } + + public int height() + { + return (maxCorner.getY() - minCorner.getY() + 1); + } + + public int length() + { + return (maxCorner.getZ() - minCorner.getZ() + 1); + } + + public boolean isLeaf() + { + return (leftChild == null); + } + + public SpatialNode leftChild() + { + return leftChild; + } + + public SpatialNode rightChild() + { + return rightChild; + } + + public Point3D minCorner() + { + return minCorner; + } + + public Point3D maxCorner() + { + return maxCorner; + } + + public void splitByX(int rightStart) + { + if (leftChild != null) + { + throw new IllegalStateException("This node has already been split."); + } + if (rightStart <= minCorner.getX() || rightStart > maxCorner.getX()) + { + throw new IllegalArgumentException("The specified cutting plane is invalid."); + } + leftChild = new SpatialNode(minCorner, new Point3D(rightStart - 1, maxCorner.getY(), maxCorner.getZ())); + rightChild = new SpatialNode(new Point3D(rightStart, minCorner.getY(), minCorner.getZ()), maxCorner); + } + + public void splitByY(int rightStart) + { + if (leftChild != null) + { + throw new IllegalStateException("This node has already been split."); + } + if (rightStart <= minCorner.getY() || rightStart > maxCorner.getY()) + { + throw new IllegalArgumentException("The specified cutting plane is invalid."); + } + leftChild = new SpatialNode(minCorner, new Point3D(maxCorner.getX(), rightStart - 1, maxCorner.getZ())); + rightChild = new SpatialNode(new Point3D(minCorner.getX(), rightStart, minCorner.getZ()), maxCorner); + } + + public void splitByZ(int rightStart) + { + if (leftChild != null) + { + throw new IllegalStateException("This node has already been split."); + } + if (rightStart <= minCorner.getZ() || rightStart > maxCorner.getZ()) + { + throw new IllegalArgumentException("The specified cutting plane is invalid."); + } + leftChild = new SpatialNode(minCorner, new Point3D(maxCorner.getX(), maxCorner.getY(), rightStart - 1)); + rightChild = new SpatialNode(new Point3D(minCorner.getX(), minCorner.getY(), rightStart), maxCorner); + } +} diff --git a/src/main/java/StevenDimDoors/mod_pocketDim/world/PocketBuilder.java b/src/main/java/StevenDimDoors/mod_pocketDim/world/PocketBuilder.java index 70c1f014..a6172a34 100644 --- a/src/main/java/StevenDimDoors/mod_pocketDim/world/PocketBuilder.java +++ b/src/main/java/StevenDimDoors/mod_pocketDim/world/PocketBuilder.java @@ -8,6 +8,7 @@ import net.minecraft.world.World; import net.minecraft.world.chunk.Chunk; import net.minecraft.world.chunk.storage.ExtendedBlockStorage; import net.minecraftforge.common.DimensionManager; +import StevenDimDoors.experimental.MazeGenerator; import StevenDimDoors.mod_pocketDim.DDProperties; import StevenDimDoors.mod_pocketDim.Point3D; import StevenDimDoors.mod_pocketDim.blocks.IDimDoor; @@ -472,6 +473,7 @@ public class PocketBuilder Point3D door = new Point3D(x, y, z); BlockRotator.transformPoint(center, door, orientation - BlockRotator.EAST_DOOR_METADATA, door); + /* //Build the outer layer of Eternal Fabric buildBox(world, center.getX(), center.getY(), center.getZ(), (size / 2), properties.PermaFabricBlockID, false, 0); @@ -481,6 +483,9 @@ public class PocketBuilder buildBox(world, center.getX(), center.getY(), center.getZ(), (size / 2) - layer, properties.FabricBlockID, layer < (wallThickness - 1) && properties.TNFREAKINGT_Enabled, properties.NonTntWeight); } + */ + + MazeGenerator.generate(world, x, y, z, random); //Build the door int doorOrientation = BlockRotator.transformMetadata(BlockRotator.EAST_DOOR_METADATA, orientation - BlockRotator.EAST_DOOR_METADATA + 2, properties.DimensionalDoorID); From 82da53b99262959a913fdb92661e756cbdaca9a8 Mon Sep 17 00:00:00 2001 From: SenseiKiwi Date: Thu, 26 Dec 2013 15:29:42 -0400 Subject: [PATCH 02/16] Minor Change Added blank lines to Point3D to space out functions. <_< --- src/main/java/StevenDimDoors/mod_pocketDim/Point3D.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/StevenDimDoors/mod_pocketDim/Point3D.java b/src/main/java/StevenDimDoors/mod_pocketDim/Point3D.java index 204edb8c..f639ce18 100644 --- a/src/main/java/StevenDimDoors/mod_pocketDim/Point3D.java +++ b/src/main/java/StevenDimDoors/mod_pocketDim/Point3D.java @@ -61,10 +61,12 @@ public class Point3D implements Serializable { { return new Point3D(x, y, z); } + public int[] toIntArray() { return new int[]{x,y,z}; } + public boolean equals(Point3D other) { if (other == null) From a9f0bc5069ac7812d2340b9f88f803c12e97f545 Mon Sep 17 00:00:00 2001 From: SenseiKiwi Date: Fri, 27 Dec 2013 03:11:59 -0400 Subject: [PATCH 03/16] More Maze Generation Made some changes to maze generation to prune out random rooms from the structure. This doesn't look as nice as I'd hoped, so I'm going to try some other approaches. --- .../experimental/MazeGenerator.java | 54 +++++++++++++++++-- .../experimental/SpatialNode.java | 42 +++++++++++---- 2 files changed, 81 insertions(+), 15 deletions(-) diff --git a/src/main/java/StevenDimDoors/experimental/MazeGenerator.java b/src/main/java/StevenDimDoors/experimental/MazeGenerator.java index 5eb9e638..aa7e99c4 100644 --- a/src/main/java/StevenDimDoors/experimental/MazeGenerator.java +++ b/src/main/java/StevenDimDoors/experimental/MazeGenerator.java @@ -1,5 +1,7 @@ package StevenDimDoors.experimental; +import java.util.ArrayList; +import java.util.Collections; import java.util.Random; import net.minecraft.block.Block; @@ -13,18 +15,60 @@ public class MazeGenerator { public static final int ROOT_WIDTH = 40; public static final int ROOT_LENGTH = 40; - public static final int ROOT_HEIGHT = 19; + public static final int ROOT_HEIGHT = 20; private static final int MIN_HEIGHT = 4; private static final int MIN_SIDE = 3; - private static final int SPLIT_COUNT = 8; + private static final int SPLIT_COUNT = 9; private MazeGenerator() { } public static void generate(World world, int x, int y, int z, Random random) { SpatialNode root = partitionRooms(ROOT_WIDTH, ROOT_HEIGHT, ROOT_LENGTH, SPLIT_COUNT, random); + // Collect all the leaf nodes by performing a tree traversal + ArrayList rooms = new ArrayList(1 << SPLIT_COUNT); + listRooms(root, rooms); + removeRandomRooms(rooms, random); buildRooms(root, world, new Point3D(x - ROOT_WIDTH / 2, y - ROOT_HEIGHT - 1, z - ROOT_WIDTH / 2)); + } + + private static void listRooms(SpatialNode node, ArrayList rooms) + { + if (node.isLeaf()) + { + rooms.add(node); + } + else + { + listRooms(node.leftChild(), rooms); + listRooms(node.rightChild(), rooms); + } + } + private static void removeRandomRooms(ArrayList rooms, Random random) + { + // Randomly remove a fraction of the rooms + Collections.shuffle(rooms, random); + int remaining = rooms.size() / 2; + for (int k = rooms.size() - 1; k >= remaining; k--) + { + removeRoom(rooms.remove(k)); + } + } + + private static void removeRoom(SpatialNode node) + { + // Remove a node and any of its ancestors that become leaf nodes + SpatialNode parent; + SpatialNode current; + + current = node; + while (current != null && current.isLeaf()) + { + parent = current.parent(); + current.remove(); + current = parent; + } } private static SpatialNode partitionRooms(int width, int height, int length, int maxLevels, Random random) @@ -99,8 +143,10 @@ public class MazeGenerator } else { - buildRooms(node.leftChild(), world, offset); - buildRooms(node.rightChild(), world, offset); + if (node.leftChild() != null) + buildRooms(node.leftChild(), world, offset); + if (node.rightChild() != null) + buildRooms(node.rightChild(), world, offset); } } diff --git a/src/main/java/StevenDimDoors/experimental/SpatialNode.java b/src/main/java/StevenDimDoors/experimental/SpatialNode.java index 0aef08f1..1b53bbe0 100644 --- a/src/main/java/StevenDimDoors/experimental/SpatialNode.java +++ b/src/main/java/StevenDimDoors/experimental/SpatialNode.java @@ -6,17 +6,20 @@ public class SpatialNode { private Point3D minCorner; private Point3D maxCorner; + private SpatialNode parent; private SpatialNode leftChild = null; private SpatialNode rightChild = null; public SpatialNode(int width, int height, int length) { + parent = null; minCorner = new Point3D(0, 0, 0); maxCorner = new Point3D(width - 1, height - 1, length - 1); } - private SpatialNode(Point3D minCorner, Point3D maxCorner) + private SpatialNode(SpatialNode parent, Point3D minCorner, Point3D maxCorner) { + this.parent = parent; this.minCorner = minCorner; this.maxCorner = maxCorner; } @@ -38,7 +41,7 @@ public class SpatialNode public boolean isLeaf() { - return (leftChild == null); + return (leftChild == null && rightChild == null); } public SpatialNode leftChild() @@ -61,9 +64,14 @@ public class SpatialNode return maxCorner; } + public SpatialNode parent() + { + return parent; + } + public void splitByX(int rightStart) { - if (leftChild != null) + if (!this.isLeaf()) { throw new IllegalStateException("This node has already been split."); } @@ -71,13 +79,13 @@ public class SpatialNode { throw new IllegalArgumentException("The specified cutting plane is invalid."); } - leftChild = new SpatialNode(minCorner, new Point3D(rightStart - 1, maxCorner.getY(), maxCorner.getZ())); - rightChild = new SpatialNode(new Point3D(rightStart, minCorner.getY(), minCorner.getZ()), maxCorner); + leftChild = new SpatialNode(this, minCorner, new Point3D(rightStart - 1, maxCorner.getY(), maxCorner.getZ())); + rightChild = new SpatialNode(this, new Point3D(rightStart, minCorner.getY(), minCorner.getZ()), maxCorner); } public void splitByY(int rightStart) { - if (leftChild != null) + if (!this.isLeaf()) { throw new IllegalStateException("This node has already been split."); } @@ -85,13 +93,13 @@ public class SpatialNode { throw new IllegalArgumentException("The specified cutting plane is invalid."); } - leftChild = new SpatialNode(minCorner, new Point3D(maxCorner.getX(), rightStart - 1, maxCorner.getZ())); - rightChild = new SpatialNode(new Point3D(minCorner.getX(), rightStart, minCorner.getZ()), maxCorner); + leftChild = new SpatialNode(this, minCorner, new Point3D(maxCorner.getX(), rightStart - 1, maxCorner.getZ())); + rightChild = new SpatialNode(this, new Point3D(minCorner.getX(), rightStart, minCorner.getZ()), maxCorner); } public void splitByZ(int rightStart) { - if (leftChild != null) + if (!this.isLeaf()) { throw new IllegalStateException("This node has already been split."); } @@ -99,7 +107,19 @@ public class SpatialNode { throw new IllegalArgumentException("The specified cutting plane is invalid."); } - leftChild = new SpatialNode(minCorner, new Point3D(maxCorner.getX(), maxCorner.getY(), rightStart - 1)); - rightChild = new SpatialNode(new Point3D(minCorner.getX(), minCorner.getY(), rightStart), maxCorner); + leftChild = new SpatialNode(this, minCorner, new Point3D(maxCorner.getX(), maxCorner.getY(), rightStart - 1)); + rightChild = new SpatialNode(this, new Point3D(minCorner.getX(), minCorner.getY(), rightStart), maxCorner); + } + + public void remove() + { + if (parent != null) + { + if (parent.leftChild == this) + parent.leftChild = null; + else + parent.rightChild = null; + parent = null; + } } } From 81bac5d1c2a4ece2344f707f331ff5609a8c17ac Mon Sep 17 00:00:00 2001 From: SenseiKiwi Date: Fri, 27 Dec 2013 21:58:17 -0400 Subject: [PATCH 04/16] Progress on Second Step Made some progress on the second step of our maze generation algorithm: building an adjacency graph for setting up doorways later. Renamed the SpatialNode class to PartitionNode to better represent its role. --- .../experimental/DoorwayData.java | 50 +++++++++++++++++ .../experimental/MazeGenerator.java | 26 ++++----- .../{SpatialNode.java => PartitionNode.java} | 30 +++++----- .../StevenDimDoors/experimental/RoomNode.java | 56 +++++++++++++++++++ 4 files changed, 134 insertions(+), 28 deletions(-) create mode 100644 src/main/java/StevenDimDoors/experimental/DoorwayData.java rename src/main/java/StevenDimDoors/experimental/{SpatialNode.java => PartitionNode.java} (65%) create mode 100644 src/main/java/StevenDimDoors/experimental/RoomNode.java diff --git a/src/main/java/StevenDimDoors/experimental/DoorwayData.java b/src/main/java/StevenDimDoors/experimental/DoorwayData.java new file mode 100644 index 00000000..a1a09f1c --- /dev/null +++ b/src/main/java/StevenDimDoors/experimental/DoorwayData.java @@ -0,0 +1,50 @@ +package StevenDimDoors.experimental; + +import StevenDimDoors.mod_pocketDim.Point3D; + +public class DoorwayData +{ + public final char X_AXIS = 'X'; + public final char Y_AXIS = 'Y'; + public final char Z_AXIS = 'Z'; + + private RoomNode head; + private RoomNode tail; + private Point3D minCorner; + private Point3D maxCorner; + private char axis; + + public DoorwayData(RoomNode head, RoomNode tail, Point3D minCorner, Point3D maxCorner, char axis) + { + this.head = head; + this.tail = tail; + this.minCorner = minCorner; + this.maxCorner = maxCorner; + this.axis = axis; + } + + public RoomNode head() + { + return head; + } + + public RoomNode tail() + { + return tail; + } + + public Point3D minCorner() + { + return minCorner; + } + + public Point3D maxCorner() + { + return maxCorner; + } + + public char axis() + { + return axis; + } +} diff --git a/src/main/java/StevenDimDoors/experimental/MazeGenerator.java b/src/main/java/StevenDimDoors/experimental/MazeGenerator.java index aa7e99c4..ba328405 100644 --- a/src/main/java/StevenDimDoors/experimental/MazeGenerator.java +++ b/src/main/java/StevenDimDoors/experimental/MazeGenerator.java @@ -24,15 +24,15 @@ public class MazeGenerator public static void generate(World world, int x, int y, int z, Random random) { - SpatialNode root = partitionRooms(ROOT_WIDTH, ROOT_HEIGHT, ROOT_LENGTH, SPLIT_COUNT, random); + PartitionNode root = partitionRooms(ROOT_WIDTH, ROOT_HEIGHT, ROOT_LENGTH, SPLIT_COUNT, random); // Collect all the leaf nodes by performing a tree traversal - ArrayList rooms = new ArrayList(1 << SPLIT_COUNT); + ArrayList rooms = new ArrayList(1 << SPLIT_COUNT); listRooms(root, rooms); removeRandomRooms(rooms, random); buildRooms(root, world, new Point3D(x - ROOT_WIDTH / 2, y - ROOT_HEIGHT - 1, z - ROOT_WIDTH / 2)); } - private static void listRooms(SpatialNode node, ArrayList rooms) + private static void listRooms(PartitionNode node, ArrayList rooms) { if (node.isLeaf()) { @@ -45,7 +45,7 @@ public class MazeGenerator } } - private static void removeRandomRooms(ArrayList rooms, Random random) + private static void removeRandomRooms(ArrayList rooms, Random random) { // Randomly remove a fraction of the rooms Collections.shuffle(rooms, random); @@ -56,11 +56,11 @@ public class MazeGenerator } } - private static void removeRoom(SpatialNode node) + private static void removeRoom(PartitionNode node) { // Remove a node and any of its ancestors that become leaf nodes - SpatialNode parent; - SpatialNode current; + PartitionNode parent; + PartitionNode current; current = node; while (current != null && current.isLeaf()) @@ -71,14 +71,14 @@ public class MazeGenerator } } - private static SpatialNode partitionRooms(int width, int height, int length, int maxLevels, Random random) + private static PartitionNode partitionRooms(int width, int height, int length, int maxLevels, Random random) { - SpatialNode root = new SpatialNode(width, height, length); + PartitionNode root = new PartitionNode(width, height, length); splitByRandomX(root, maxLevels, random); return root; } - private static void splitByRandomX(SpatialNode node, int levels, Random random) + private static void splitByRandomX(PartitionNode node, int levels, Random random) { if (node.width() >= 2 * MIN_SIDE) { @@ -97,7 +97,7 @@ public class MazeGenerator } } - private static void splitByRandomZ(SpatialNode node, int levels, Random random) + private static void splitByRandomZ(PartitionNode node, int levels, Random random) { if (node.length() >= 2 * MIN_SIDE) { @@ -116,7 +116,7 @@ public class MazeGenerator } } - private static void splitByRandomY(SpatialNode node, int levels, Random random) + private static void splitByRandomY(PartitionNode node, int levels, Random random) { if (node.height() >= 2 * MIN_HEIGHT) { @@ -135,7 +135,7 @@ public class MazeGenerator } } - private static void buildRooms(SpatialNode node, World world, Point3D offset) + private static void buildRooms(PartitionNode node, World world, Point3D offset) { if (node.isLeaf()) { diff --git a/src/main/java/StevenDimDoors/experimental/SpatialNode.java b/src/main/java/StevenDimDoors/experimental/PartitionNode.java similarity index 65% rename from src/main/java/StevenDimDoors/experimental/SpatialNode.java rename to src/main/java/StevenDimDoors/experimental/PartitionNode.java index 1b53bbe0..9c14eb01 100644 --- a/src/main/java/StevenDimDoors/experimental/SpatialNode.java +++ b/src/main/java/StevenDimDoors/experimental/PartitionNode.java @@ -2,22 +2,22 @@ package StevenDimDoors.experimental; import StevenDimDoors.mod_pocketDim.Point3D; -public class SpatialNode +public class PartitionNode { private Point3D minCorner; private Point3D maxCorner; - private SpatialNode parent; - private SpatialNode leftChild = null; - private SpatialNode rightChild = null; + private PartitionNode parent; + private PartitionNode leftChild = null; + private PartitionNode rightChild = null; - public SpatialNode(int width, int height, int length) + public PartitionNode(int width, int height, int length) { parent = null; minCorner = new Point3D(0, 0, 0); maxCorner = new Point3D(width - 1, height - 1, length - 1); } - private SpatialNode(SpatialNode parent, Point3D minCorner, Point3D maxCorner) + private PartitionNode(PartitionNode parent, Point3D minCorner, Point3D maxCorner) { this.parent = parent; this.minCorner = minCorner; @@ -44,12 +44,12 @@ public class SpatialNode return (leftChild == null && rightChild == null); } - public SpatialNode leftChild() + public PartitionNode leftChild() { return leftChild; } - public SpatialNode rightChild() + public PartitionNode rightChild() { return rightChild; } @@ -64,7 +64,7 @@ public class SpatialNode return maxCorner; } - public SpatialNode parent() + public PartitionNode parent() { return parent; } @@ -79,8 +79,8 @@ public class SpatialNode { throw new IllegalArgumentException("The specified cutting plane is invalid."); } - leftChild = new SpatialNode(this, minCorner, new Point3D(rightStart - 1, maxCorner.getY(), maxCorner.getZ())); - rightChild = new SpatialNode(this, new Point3D(rightStart, minCorner.getY(), minCorner.getZ()), maxCorner); + leftChild = new PartitionNode(this, minCorner, new Point3D(rightStart - 1, maxCorner.getY(), maxCorner.getZ())); + rightChild = new PartitionNode(this, new Point3D(rightStart, minCorner.getY(), minCorner.getZ()), maxCorner); } public void splitByY(int rightStart) @@ -93,8 +93,8 @@ public class SpatialNode { throw new IllegalArgumentException("The specified cutting plane is invalid."); } - leftChild = new SpatialNode(this, minCorner, new Point3D(maxCorner.getX(), rightStart - 1, maxCorner.getZ())); - rightChild = new SpatialNode(this, new Point3D(minCorner.getX(), rightStart, minCorner.getZ()), maxCorner); + leftChild = new PartitionNode(this, minCorner, new Point3D(maxCorner.getX(), rightStart - 1, maxCorner.getZ())); + rightChild = new PartitionNode(this, new Point3D(minCorner.getX(), rightStart, minCorner.getZ()), maxCorner); } public void splitByZ(int rightStart) @@ -107,8 +107,8 @@ public class SpatialNode { throw new IllegalArgumentException("The specified cutting plane is invalid."); } - leftChild = new SpatialNode(this, minCorner, new Point3D(maxCorner.getX(), maxCorner.getY(), rightStart - 1)); - rightChild = new SpatialNode(this, new Point3D(minCorner.getX(), minCorner.getY(), rightStart), maxCorner); + leftChild = new PartitionNode(this, minCorner, new Point3D(maxCorner.getX(), maxCorner.getY(), rightStart - 1)); + rightChild = new PartitionNode(this, new Point3D(minCorner.getX(), minCorner.getY(), rightStart), maxCorner); } public void remove() diff --git a/src/main/java/StevenDimDoors/experimental/RoomNode.java b/src/main/java/StevenDimDoors/experimental/RoomNode.java new file mode 100644 index 00000000..78a70027 --- /dev/null +++ b/src/main/java/StevenDimDoors/experimental/RoomNode.java @@ -0,0 +1,56 @@ +package StevenDimDoors.experimental; + +import java.util.ArrayList; + +public class RoomNode +{ + private ArrayList outbound; + private ArrayList inbound; + private PartitionNode bounds; + private int distance; + private boolean visited; + + public RoomNode(PartitionNode bounds) + { + this.bounds = bounds; + this.distance = 0; + this.visited = false; + this.outbound = new ArrayList(); + this.inbound = new ArrayList(); + } + + public int distance() + { + return distance; + } + + public boolean isVisited() + { + return visited; + } + + public void setDistance(int value) + { + distance = value; + } + + public void setVisited(boolean value) + { + visited = value; + } + + public PartitionNode bounds() + { + return bounds; + } + + public void addInboundDoorway(DoorwayData data) + { + inbound.add(data); + } + + public void addOutboundDoorway(DoorwayData data) + { + outbound.add(data); + } +} From 31f0c1ca0c234634171ae6efcffb1e173ce036e6 Mon Sep 17 00:00:00 2001 From: SenseiKiwi Date: Sat, 28 Dec 2013 22:49:11 -0400 Subject: [PATCH 05/16] Completed Second Step of Maze Generation Completed the second step of maze generation. In this step, a graph is built that describes which rooms can be connected to each other by doorways. A maze is created by removing nodes (rooms) from the graph. The algorithm is incomplete, but it already produces interesting mazes comparable to DD's hand-made mazes. A few details for the future: 1. Doorways are currently carved into walls at default locations. This is just for testing and placement should be improved later. Some doorways should be removed and redundant doorways should be possible. 2. The size of a section should be assessed and the section should be discarded if it has too few rooms. 3. An NPE occurs every so often when a maze is generated. It's possible that it happens because of removing a node from the graph that is coincidentally the current node for LinkedList's iterator. The solution would be to add nodes to a list and defer removals until after the iteration is done. --- .../experimental/DirectedGraph.java | 225 ++++++++++ .../experimental/DoorwayData.java | 22 +- .../StevenDimDoors/experimental/IEdge.java | 8 + .../experimental/IGraphNode.java | 10 + .../experimental/ILinkedListNode.java | 11 + .../experimental/LinkedList.java | 236 +++++++++++ .../experimental/MazeGenerator.java | 393 ++++++++++++++++-- .../experimental/PartitionNode.java | 36 ++ .../StevenDimDoors/experimental/RoomNode.java | 56 --- 9 files changed, 898 insertions(+), 99 deletions(-) create mode 100644 src/main/java/StevenDimDoors/experimental/DirectedGraph.java create mode 100644 src/main/java/StevenDimDoors/experimental/IEdge.java create mode 100644 src/main/java/StevenDimDoors/experimental/IGraphNode.java create mode 100644 src/main/java/StevenDimDoors/experimental/ILinkedListNode.java create mode 100644 src/main/java/StevenDimDoors/experimental/LinkedList.java delete mode 100644 src/main/java/StevenDimDoors/experimental/RoomNode.java diff --git a/src/main/java/StevenDimDoors/experimental/DirectedGraph.java b/src/main/java/StevenDimDoors/experimental/DirectedGraph.java new file mode 100644 index 00000000..cd56c751 --- /dev/null +++ b/src/main/java/StevenDimDoors/experimental/DirectedGraph.java @@ -0,0 +1,225 @@ +package StevenDimDoors.experimental; + +/** + * Provides a complete implementation of a directed graph. + * @author SenseiKiwi + * + * @param The type of data to store in the graph's nodes + * @param The type of data to store in the graph's edges + */ +public class DirectedGraph +{ + private static class GraphNode implements IGraphNode + { + private LinkedList> inbound; + private LinkedList> outbound; + private ILinkedListNode> graphEntry; + private P data; + + public GraphNode(P data, LinkedList> graphList) + { + this.data = data; + this.inbound = new LinkedList>(); + this.outbound = new LinkedList>(); + this.graphEntry = graphList.addLast(this); + } + + public int indegree() + { + return inbound.size(); + } + + public int outdegree() + { + return outbound.size(); + } + + public Iterable> inbound() + { + return inbound; + } + + public Iterable> outbound() + { + return outbound; + } + + public P data() + { + return data; + } + + public void remove() + { + graphEntry.remove(); + graphEntry = null; + + for (Edge edge : inbound) + edge.remove(); + + for (Edge edge : outbound) + edge.remove(); + + inbound = null; + outbound = null; + data = null; + } + } + + private static class Edge implements IEdge + { + private GraphNode head; + private GraphNode tail; + private ILinkedListNode> headEntry; + private ILinkedListNode> tailEntry; + private ILinkedListNode> graphEntry; + private Q data; + + public Edge(GraphNode head, GraphNode tail, Q data, LinkedList> graphList) + { + this.head = head; + this.tail = tail; + this.data = data; + this.graphEntry = graphList.addLast(this); + this.headEntry = head.outbound.addLast(this); + this.tailEntry = tail.inbound.addLast(this); + } + + public IGraphNode head() + { + return head; + } + + public IGraphNode tail() + { + return tail; + } + + public Q data() + { + return data; + } + + public void remove() + { + headEntry.remove(); + tailEntry.remove(); + graphEntry.remove(); + headEntry = null; + tailEntry = null; + graphEntry = null; + head = null; + tail = null; + data = null; + } + } + + private LinkedList> nodes; + private LinkedList> edges; + + public DirectedGraph() + { + nodes = new LinkedList>(); + edges = new LinkedList>(); + } + + public int nodeCount() + { + return nodes.size(); + } + + public int edgeCount() + { + return edges.size(); + } + + public boolean isEmpty() + { + return nodes.isEmpty(); + } + + public Iterable> nodes() + { + return nodes; + } + + public Iterable> edges() + { + return edges; + } + + private GraphNode checkNode(IGraphNode node) + { + GraphNode innerNode = (GraphNode) node; + + // Check that this node actually belongs to this graph instance. + // Accepting foreign nodes could corrupt the graph's internal state. + if (innerNode.graphEntry.owner() != nodes) + { + throw new IllegalArgumentException("The specified node does not belong to this graph."); + } + return innerNode; + } + + private Edge checkEdge(IEdge edge) + { + Edge innerEdge = (Edge) edge; + + // Check that this node actually belongs to this graph instance. + // Accepting foreign nodes could corrupt the graph's internal state. + if (innerEdge.graphEntry.owner() != edges) + { + throw new IllegalArgumentException("The specified edge does not belong to this graph."); + } + return innerEdge; + } + + public IGraphNode addNode(U data) + { + return new GraphNode(data, nodes); + } + + public IEdge addEdge(IGraphNode head, IGraphNode tail, V data) + { + GraphNode innerHead = checkNode(head); + GraphNode innerTail = checkNode(tail); + return new Edge(innerHead, innerTail, data, edges); + } + + public U removeNode(IGraphNode node) + { + GraphNode innerNode = checkNode(node); + U data = innerNode.data(); + innerNode.remove(); + return data; + } + + public V removeEdge(IEdge edge) + { + Edge innerEdge = checkEdge(edge); + V data = innerEdge.data(); + innerEdge.remove(); + return data; + } + + public IEdge findEdge(IGraphNode head, IGraphNode tail) + { + for (IEdge edge : head.outbound()) + { + if (edge.tail() == tail) + return edge; + } + return null; + } + + public void clear() + { + // Remove each node individually to guarantee that all external + // references are invalidated. That'll prevent memory leaks and + // keep external code from using removed nodes or edges. + for (GraphNode node : nodes) + { + node.remove(); + } + } +} diff --git a/src/main/java/StevenDimDoors/experimental/DoorwayData.java b/src/main/java/StevenDimDoors/experimental/DoorwayData.java index a1a09f1c..ec1c5cc8 100644 --- a/src/main/java/StevenDimDoors/experimental/DoorwayData.java +++ b/src/main/java/StevenDimDoors/experimental/DoorwayData.java @@ -4,35 +4,21 @@ import StevenDimDoors.mod_pocketDim.Point3D; public class DoorwayData { - public final char X_AXIS = 'X'; - public final char Y_AXIS = 'Y'; - public final char Z_AXIS = 'Z'; + public static final char X_AXIS = 'X'; + public static final char Y_AXIS = 'Y'; + public static final char Z_AXIS = 'Z'; - private RoomNode head; - private RoomNode tail; private Point3D minCorner; private Point3D maxCorner; private char axis; - public DoorwayData(RoomNode head, RoomNode tail, Point3D minCorner, Point3D maxCorner, char axis) + public DoorwayData(Point3D minCorner, Point3D maxCorner, char axis) { - this.head = head; - this.tail = tail; this.minCorner = minCorner; this.maxCorner = maxCorner; this.axis = axis; } - public RoomNode head() - { - return head; - } - - public RoomNode tail() - { - return tail; - } - public Point3D minCorner() { return minCorner; diff --git a/src/main/java/StevenDimDoors/experimental/IEdge.java b/src/main/java/StevenDimDoors/experimental/IEdge.java new file mode 100644 index 00000000..1d268116 --- /dev/null +++ b/src/main/java/StevenDimDoors/experimental/IEdge.java @@ -0,0 +1,8 @@ +package StevenDimDoors.experimental; + +public interface IEdge +{ + public IGraphNode head(); + public IGraphNode tail(); + public V data(); +} diff --git a/src/main/java/StevenDimDoors/experimental/IGraphNode.java b/src/main/java/StevenDimDoors/experimental/IGraphNode.java new file mode 100644 index 00000000..65fd2fbf --- /dev/null +++ b/src/main/java/StevenDimDoors/experimental/IGraphNode.java @@ -0,0 +1,10 @@ +package StevenDimDoors.experimental; + +public interface IGraphNode +{ + public Iterable> inbound(); + public Iterable> outbound(); + public int indegree(); + public int outdegree(); + public U data(); +} diff --git a/src/main/java/StevenDimDoors/experimental/ILinkedListNode.java b/src/main/java/StevenDimDoors/experimental/ILinkedListNode.java new file mode 100644 index 00000000..a7a54adc --- /dev/null +++ b/src/main/java/StevenDimDoors/experimental/ILinkedListNode.java @@ -0,0 +1,11 @@ +package StevenDimDoors.experimental; + +public interface ILinkedListNode +{ + public ILinkedListNode next(); + public ILinkedListNode prev(); + public T data(); + public void setData(T data); + public LinkedList owner(); + public T remove(); +} diff --git a/src/main/java/StevenDimDoors/experimental/LinkedList.java b/src/main/java/StevenDimDoors/experimental/LinkedList.java new file mode 100644 index 00000000..8d6b9751 --- /dev/null +++ b/src/main/java/StevenDimDoors/experimental/LinkedList.java @@ -0,0 +1,236 @@ +package StevenDimDoors.experimental; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * Provides an implementation of a linked list that exposes its internal nodes. + * This differs from Java's implementation, which does not expose nodes. Access + * to the nodes allows certain operations to be implemented more efficiently. + * Not all operations are supported, but we can add them as the need arises. + * @author SenseiKiwi + * + * @param The type of data to be stored in the LinkedList + */ +public class LinkedList implements Iterable +{ + private static class Node

implements ILinkedListNode

+ { + private Node

next; + private Node

prev; + private P data; + private LinkedList

owner; + + public Node(Node

prev, Node

next, P data, LinkedList

owner) + { + this.prev = prev; + this.next = next; + this.data = data; + this.owner = owner; + } + + @Override + public ILinkedListNode

next() + { + return next; + } + + @Override + public ILinkedListNode

prev() + { + return prev; + } + + @Override + public P data() + { + return data; + } + + @Override + public void setData(P data) + { + if (this == owner.header || this == owner.trailer) + { + throw new IllegalStateException("Cannot set data for the header and trailer nodes of a list."); + } + + this.data = data; + } + + @Override + public LinkedList

owner() + { + return owner; + } + + @Override + public P remove() + { + if (this == owner.header || this == owner.trailer) + { + throw new IllegalStateException("Cannot remove the header and trailer nodes of a list."); + } + + P data = this.data; + this.prev.next = this.next; + this.next.prev = this.prev; + this.owner.size--; + + this.clear(); + return data; + } + + public void clear() + { + this.data = null; + this.prev = null; + this.next = null; + this.owner = null; + } + } + + private static class LinkedListIterator

implements Iterator

+ { + private Node

current; + private Node

trailer; + + public LinkedListIterator(LinkedList

list) + { + current = list.header.next; + trailer = list.trailer; + } + + @Override + public boolean hasNext() + { + return (current != trailer); + } + + @Override + public P next() + { + if (current == trailer) + { + throw new NoSuchElementException(); + } + else + { + P result = current.data; + current = current.next; + return result; + } + } + + @Override + public void remove() + { + throw new UnsupportedOperationException(); + } + } + + private Node header; // Sentinel node + private Node trailer; // Sentinel node + private int size; + + public LinkedList() + { + size = 0; + header = new Node(null, null, null, this); + trailer = new Node(null, null, null, this); + header.next = trailer; + trailer.prev = header; + } + + public ILinkedListNode header() + { + return header; + } + + public ILinkedListNode trailer() + { + return trailer; + } + + public int size() + { + return size; + } + + public boolean isEmpty() + { + return (size == 0); + } + + public void clear() + { + // Go through the list and wipe everything out + Node current; + Node next; + + size = 0; + current = header.next; + while (current != trailer) + { + next = current.next; + current.clear(); + current = next; + } + header.next = trailer; + trailer.prev = header; + } + + private Node checkNode(ILinkedListNode node) + { + Node innerNode = (Node) node; + + // Check that this node actually belongs to this list instance. + // Accepting foreign nodes could corrupt the list's internal state. + if (innerNode.owner() != this) + { + throw new IllegalArgumentException("The specified node does not belong to this list."); + } + return innerNode; + } + + public ILinkedListNode addFirst(T data) + { + return addAfter(header, data); + } + + public ILinkedListNode addLast(T data) + { + return addBefore(trailer, data); + } + + public ILinkedListNode addBefore(ILinkedListNode node, T data) + { + if (node == header) + { + throw new IllegalArgumentException("Cannot add a node before the header node."); + } + return addAfter( checkNode(node).prev, data ); + } + + public ILinkedListNode addAfter(ILinkedListNode node, T data) + { + if (node == trailer) + { + throw new IllegalArgumentException("Cannot add a node after the trailer node."); + } + return addAfter( checkNode(node), data ); + } + + private Node addAfter(Node node, T data) + { + Node addition = new Node(node, node.next, data, this); + node.next = addition; + addition.next.prev = addition; + return addition; + } + + public Iterator iterator() + { + return new LinkedListIterator(this); + } +} diff --git a/src/main/java/StevenDimDoors/experimental/MazeGenerator.java b/src/main/java/StevenDimDoors/experimental/MazeGenerator.java index ba328405..4b5f076d 100644 --- a/src/main/java/StevenDimDoors/experimental/MazeGenerator.java +++ b/src/main/java/StevenDimDoors/experimental/MazeGenerator.java @@ -2,6 +2,9 @@ package StevenDimDoors.experimental; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Queue; import java.util.Random; import net.minecraft.block.Block; @@ -24,24 +27,35 @@ public class MazeGenerator public static void generate(World world, int x, int y, int z, Random random) { + // Construct a random binary space partitioning of our maze volume PartitionNode root = partitionRooms(ROOT_WIDTH, ROOT_HEIGHT, ROOT_LENGTH, SPLIT_COUNT, random); - // Collect all the leaf nodes by performing a tree traversal - ArrayList rooms = new ArrayList(1 << SPLIT_COUNT); - listRooms(root, rooms); - removeRandomRooms(rooms, random); - buildRooms(root, world, new Point3D(x - ROOT_WIDTH / 2, y - ROOT_HEIGHT - 1, z - ROOT_WIDTH / 2)); + + // List all the leaf nodes of the partition tree, which denote individual rooms + ArrayList partitions = new ArrayList(1 << SPLIT_COUNT); + listRoomPartitions(root, partitions); + + // Construct an adjacency graph of the rooms we've carved out. Two rooms are + // considered adjacent if and only if a doorway could connect them. Their + // common boundary must be large enough for a doorway. + DirectedGraph rooms = createRoomGraph(root, partitions, random); + + // Cut out random subgraphs from the adjacency graph + ArrayList> cores = createMazeSections(rooms, random); + + + buildRooms(rooms, world, new Point3D(x - ROOT_WIDTH / 2, y - ROOT_HEIGHT - 1, z - ROOT_WIDTH / 2)); } - private static void listRooms(PartitionNode node, ArrayList rooms) + private static void listRoomPartitions(PartitionNode node, ArrayList partitions) { if (node.isLeaf()) { - rooms.add(node); + partitions.add(node); } else { - listRooms(node.leftChild(), rooms); - listRooms(node.rightChild(), rooms); + listRoomPartitions(node.leftChild(), partitions); + listRoomPartitions(node.rightChild(), partitions); } } @@ -135,22 +149,352 @@ public class MazeGenerator } } - private static void buildRooms(PartitionNode node, World world, Point3D offset) + private static DirectedGraph createRoomGraph(PartitionNode root, ArrayList partitions, Random random) { - if (node.isLeaf()) + DirectedGraph roomGraph = new DirectedGraph(); + HashMap> roomsToGraph = new HashMap>(2 * partitions.size()); + + // Shuffle the list of rooms so that they're not listed in any ordered way in the room graph + // This is the only convenient way of randomizing the maze sections generated later + Collections.shuffle(partitions, random); + + // Add all rooms to a graph + // Also add them to a map so we can associate rooms with their graph nodes + // The map is needed for linking graph nodes based on adjacent partitions + for (PartitionNode partition : partitions) { - buildBox(world, offset, node.minCorner(), node.maxCorner()); + roomsToGraph.put(partition, roomGraph.addNode(partition)); } - else + + // Add edges for each room + for (IGraphNode node : roomGraph.nodes()) { - if (node.leftChild() != null) - buildRooms(node.leftChild(), world, offset); - if (node.rightChild() != null) - buildRooms(node.rightChild(), world, offset); + findDoorways(node, root, roomsToGraph, roomGraph); + } + + return roomGraph; + } + + private static void findDoorways(IGraphNode roomNode, PartitionNode root, + HashMap> roomsToGraph, + DirectedGraph roomGraph) + { + // This function finds rooms adjacent to a specified room that could be connected + // to it through a doorway. Edges are added to the room graph to denote rooms that + // could be connected. The areas of their common bounds that could be carved + // out for a passage are stored in the edges. + + // Three directions have to be checked: up, forward, and right. The other three + // directions (down, back, left) aren't checked because other nodes will cover them. + // That is, down for this room is up for some other room, if it exists. Also, rooms + // are guaranteed to have at least one doorway to another room, because the minimum + // dimensions to which a room can be partitioned still allow passages along all + // its sides. A room's sibling in the partition tree is guaranteed to share a side + // through which a doorway could exist. Similar arguments guarantee the existence + // of passages such that the whole set of rooms is a connected graph - in other words, + // there will always be a way to walk from any room to any other room. + + boolean[][] detected; + PartitionNode adjacent; + + int a, b, c; + int p, q, r; + int minXI, minYI, minZI; + int maxXI, maxYI, maxZI; + Point3D otherMin; + Point3D otherMax; + DoorwayData doorway; + IGraphNode adjacentNode; + + PartitionNode room = roomNode.data(); + Point3D minCorner = room.minCorner(); + Point3D maxCorner = room.maxCorner(); + + int minX = minCorner.getX(); + int minY = minCorner.getY(); + int minZ = minCorner.getZ(); + + int maxX = maxCorner.getX(); + int maxY = maxCorner.getY(); + int maxZ = maxCorner.getZ(); + + int width = room.width(); + int height = room.height(); + int length = room.length(); + + if (maxZ < root.maxCorner().getZ()) + { + // Check for adjacent rooms along the XY plane + detected = new boolean[width][height]; + for (a = 0; a < width; a++) + { + for (b = 0; b < height; b++) + { + if (!detected[a][b]) + { + adjacent = root.findPoint(minX + a, minY + b, maxZ + 1); + if (adjacent != null) + { + // Compute the dimensions available for a doorway + otherMin = adjacent.minCorner(); + otherMax = adjacent.maxCorner(); + minXI = Math.max(minX, otherMin.getX()); + maxXI = Math.min(maxX, otherMax.getX()); + minYI = Math.max(minY, otherMin.getY()); + maxYI = Math.min(maxY, otherMax.getY()); + + for (p = a; p <= maxXI - minXI; p++) + { + for (q = b; q <= maxYI - minYI; q++) + { + detected[p][q] = true; + } + } + // Check if we meet the minimum dimensions needed for a doorway + if (maxXI - minXI + 1 >= MIN_SIDE && maxYI - minYI + 1 >= MIN_HEIGHT) + { + otherMin = new Point3D(minXI, minYI, maxZ); + otherMax = new Point3D(maxXI, maxYI, maxZ + 1); + doorway = new DoorwayData(otherMin, otherMax, DoorwayData.Z_AXIS); + adjacentNode = roomsToGraph.get(adjacent); + roomGraph.addEdge(roomNode, adjacentNode, doorway); + } + } + else + { + detected[a][b] = true; + } + } + } + } + } + + + if (maxX < root.maxCorner().getX()) + { + // Check for adjacent rooms along the YZ plane + detected = new boolean[height][length]; + for (b = 0; b < height; b++) + { + for (c = 0; c < length; c++) + { + if (!detected[b][c]) + { + adjacent = root.findPoint(maxX + 1, minY + b, minZ + c); + if (adjacent != null) + { + // Compute the dimensions available for a doorway + otherMin = adjacent.minCorner(); + otherMax = adjacent.maxCorner(); + minYI = Math.max(minY, otherMin.getY()); + maxYI = Math.min(maxY, otherMax.getY()); + minZI = Math.max(minZ, otherMin.getZ()); + maxZI = Math.min(maxZ, otherMax.getZ()); + + for (q = b; q <= maxYI - minYI; q++) + { + for (r = c; r <= maxZI - minZI; r++) + { + detected[q][r] = true; + } + } + // Check if we meet the minimum dimensions needed for a doorway + if (maxYI - minYI + 1 >= MIN_HEIGHT && maxZI - minZI + 1 >= MIN_SIDE) + { + otherMin = new Point3D(maxX, minYI, minZI); + otherMax = new Point3D(maxX + 1, maxYI, maxZI); + doorway = new DoorwayData(otherMin, otherMax, DoorwayData.X_AXIS); + adjacentNode = roomsToGraph.get(adjacent); + roomGraph.addEdge(roomNode, adjacentNode, doorway); + } + } + else + { + detected[b][c] = true; + } + } + } + } + } + + + if (maxY < root.maxCorner().getY()) + { + // Check for adjacent rooms along the XZ plane + detected = new boolean[width][length]; + for (a = 0; a < width; a++) + { + for (c = 0; c < length; c++) + { + if (!detected[a][c]) + { + adjacent = root.findPoint(minX + a, maxY + 1, minZ + c); + if (adjacent != null) + { + // Compute the dimensions available for a doorway + otherMin = adjacent.minCorner(); + otherMax = adjacent.maxCorner(); + minXI = Math.max(minX, otherMin.getX()); + maxXI = Math.min(maxX, otherMax.getX()); + minZI = Math.max(minZ, otherMin.getZ()); + maxZI = Math.min(maxZ, otherMax.getZ()); + + for (p = a; p <= maxXI - minXI; p++) + { + for (r = c; r <= maxZI - minZI; r++) + { + detected[p][r] = true; + } + } + // Check if we meet the minimum dimensions needed for a doorway + if (maxXI - minXI + 1 >= MIN_SIDE && maxZI - minZI + 1 >= MIN_SIDE) + { + otherMin = new Point3D(minXI, maxY, minZI); + otherMax = new Point3D(maxXI, maxY + 1, maxZI); + doorway = new DoorwayData(otherMin, otherMax, DoorwayData.Y_AXIS); + adjacentNode = roomsToGraph.get(adjacent); + roomGraph.addEdge(roomNode, adjacentNode, doorway); + } + } + else + { + detected[a][c] = true; + } + } + } + } + } + + //Done! + } + + private static ArrayList> createMazeSections(DirectedGraph roomGraph, Random random) + { + // The randomness of the sections generated here hinges on + // the nodes in the graph being in a random order. We assume + // that was handled in a previous step! + + final int MAX_DISTANCE = 2; + + int distance; + IGraphNode current; + IGraphNode neighbor; + + ArrayList> cores = new ArrayList>(); + Queue> ordering = new LinkedList>(); + HashMap, Integer> distances = new HashMap, Integer>(); + + // Repeatedly generate sections until all nodes have been visited + for (IGraphNode node : roomGraph.nodes()) + { + // If this node has an indegree and outdegree of 0, then it has no neighbors, + // which means it could not have been visited. This could happen if its neighbors + // were pruned away before. Single rooms look weird, so remove it. + if (node.indegree() == 0 && node.outdegree() == 0) + { + roomGraph.removeNode(node); + } + + // If this node hasn't been visited, then use it as the core of a new section + // Otherwise, ignore it, since it already belongs to a section + else if (!distances.containsKey(node)) + { + cores.add(node); + + // Perform a breadth-first search to tag surrounding nodes with distances + distances.put(node, 0); + ordering.add(node); + while (!ordering.isEmpty()) + { + current = ordering.remove(); + distance = distances.get(current) + 1; + + if (distance <= MAX_DISTANCE + 1) + { + // Visit neighboring nodes and assign them distances, if they don't + // have a distance assigned already + for (IEdge edge : current.inbound()) + { + neighbor = edge.head(); + if (!distances.containsKey(neighbor)) + { + distances.put(neighbor, distance); + ordering.add(neighbor); + } + } + for (IEdge edge : current.outbound()) + { + neighbor = edge.tail(); + if (!distances.containsKey(neighbor)) + { + distances.put(neighbor, distance); + ordering.add(neighbor); + } + } + } + else + { + roomGraph.removeNode(current); + break; + } + } + + // Remove all nodes that have a distance of exactly MAX_DISTANCE + 1 + // Those are precisely the nodes that remain in the queue + while (!ordering.isEmpty()) + { + roomGraph.removeNode( ordering.remove() ); + } + } + } + return cores; + } + + private static void buildRooms(DirectedGraph roomGraph, World world, Point3D offset) + { + for (IGraphNode node : roomGraph.nodes()) + { + PartitionNode room = node.data(); + buildBox(world, offset, room.minCorner(), room.maxCorner(), Block.stoneBrick.blockID, 0); + } + + // TESTING!!! + // This code carves out cheap doorways + // The final system will be better + // This has to happen after all the rooms have been built or the passages will be overwritten sometimes + for (IGraphNode node : roomGraph.nodes()) + { + for (IEdge doorway : node.outbound()) + { + char axis = doorway.data().axis(); + Point3D lower = doorway.data().minCorner(); + + if (axis == DoorwayData.Z_AXIS) + { + setBlockDirectly(world, offset.getX() + lower.getX() + 1, offset.getY() + lower.getY() + 1, offset.getZ() + lower.getZ(), 0, 0); + setBlockDirectly(world, offset.getX() + lower.getX() + 1, offset.getY() + lower.getY() + 2, offset.getZ() + lower.getZ(), 0, 0); + setBlockDirectly(world, offset.getX() + lower.getX() + 1, offset.getY() + lower.getY() + 1, offset.getZ() + lower.getZ() + 1, 0, 0); + setBlockDirectly(world, offset.getX() + lower.getX() + 1, offset.getY() + lower.getY() + 2, offset.getZ() + lower.getZ() + 1, 0, 0); + } + else if (axis == DoorwayData.X_AXIS) + { + setBlockDirectly(world, offset.getX() + lower.getX(), offset.getY() + lower.getY() + 1, offset.getZ() + lower.getZ() + 1, 0, 0); + setBlockDirectly(world, offset.getX() + lower.getX(), offset.getY() + lower.getY() + 2, offset.getZ() + lower.getZ() + 1, 0, 0); + setBlockDirectly(world, offset.getX() + lower.getX() + 1, offset.getY() + lower.getY() + 1, offset.getZ() + lower.getZ() + 1, 0, 0); + setBlockDirectly(world, offset.getX() + lower.getX() + 1, offset.getY() + lower.getY() + 2, offset.getZ() + lower.getZ() + 1, 0, 0); + } + else + { + setBlockDirectly(world, offset.getX() + lower.getX() + 1, offset.getY() + lower.getY(), offset.getZ() + lower.getZ() + 1, 0, 0); + setBlockDirectly(world, offset.getX() + lower.getX() + 1, offset.getY() + lower.getY(), offset.getZ() + lower.getZ() + 1, 0, 0); + setBlockDirectly(world, offset.getX() + lower.getX() + 1, offset.getY() + lower.getY() + 1, offset.getZ() + lower.getZ() + 1, 0, 0); + setBlockDirectly(world, offset.getX() + lower.getX() + 1, offset.getY() + lower.getY() + 1, offset.getZ() + lower.getZ() + 1, 0, 0); + } + } } } - private static void buildBox(World world, Point3D offset, Point3D minCorner, Point3D maxCorner) + private static void buildBox(World world, Point3D offset, Point3D minCorner, Point3D maxCorner, int blockID, int metadata) { int minX = minCorner.getX() + offset.getX(); int minY = minCorner.getY() + offset.getY(); @@ -161,30 +505,29 @@ public class MazeGenerator int maxZ = maxCorner.getZ() + offset.getZ(); int x, y, z; - int blockID = Block.stoneBrick.blockID; for (x = minX; x <= maxX; x++) { for (z = minZ; z <= maxZ; z++) { - setBlockDirectly(world, x, minY, z, blockID, 0); - setBlockDirectly(world, x, maxY, z, blockID, 0); + setBlockDirectly(world, x, minY, z, blockID, metadata); + setBlockDirectly(world, x, maxY, z, blockID, metadata); } } for (x = minX; x <= maxX; x++) { for (y = minY; y <= maxY; y++) { - setBlockDirectly(world, x, y, minZ, blockID, 0); - setBlockDirectly(world, x, y, maxZ, blockID, 0); + setBlockDirectly(world, x, y, minZ, blockID, metadata); + setBlockDirectly(world, x, y, maxZ, blockID, metadata); } } for (z = minZ; z <= maxZ; z++) { for (y = minY; y <= maxY; y++) { - setBlockDirectly(world, minX, y, z, blockID, 0); - setBlockDirectly(world, maxX, y, z, blockID, 0); + setBlockDirectly(world, minX, y, z, blockID, metadata); + setBlockDirectly(world, maxX, y, z, blockID, metadata); } } } diff --git a/src/main/java/StevenDimDoors/experimental/PartitionNode.java b/src/main/java/StevenDimDoors/experimental/PartitionNode.java index 9c14eb01..25d8d6d1 100644 --- a/src/main/java/StevenDimDoors/experimental/PartitionNode.java +++ b/src/main/java/StevenDimDoors/experimental/PartitionNode.java @@ -122,4 +122,40 @@ public class PartitionNode parent = null; } } + + public boolean contains(int x, int y, int z) + { + return ((minCorner.getX() <= x && x <= maxCorner.getX()) && + (minCorner.getY() <= y && y <= maxCorner.getY()) && + (minCorner.getZ() <= z && z <= maxCorner.getZ())); + } + + public PartitionNode findPoint(int x, int y, int z) + { + // Find the lowest node that contains the specified point or return null + if (this.contains(x, y, z)) + { + return this.findPointInternal(x, y, z); + } + else + { + return null; + } + } + + private PartitionNode findPointInternal(int x, int y, int z) + { + if (leftChild != null && leftChild.contains(x, y, z)) + { + return leftChild.findPointInternal(x, y, z); + } + else if (rightChild != null && rightChild.contains(x, y, z)) + { + return rightChild.findPointInternal(x, y, z); + } + else + { + return this; + } + } } diff --git a/src/main/java/StevenDimDoors/experimental/RoomNode.java b/src/main/java/StevenDimDoors/experimental/RoomNode.java deleted file mode 100644 index 78a70027..00000000 --- a/src/main/java/StevenDimDoors/experimental/RoomNode.java +++ /dev/null @@ -1,56 +0,0 @@ -package StevenDimDoors.experimental; - -import java.util.ArrayList; - -public class RoomNode -{ - private ArrayList outbound; - private ArrayList inbound; - private PartitionNode bounds; - private int distance; - private boolean visited; - - public RoomNode(PartitionNode bounds) - { - this.bounds = bounds; - this.distance = 0; - this.visited = false; - this.outbound = new ArrayList(); - this.inbound = new ArrayList(); - } - - public int distance() - { - return distance; - } - - public boolean isVisited() - { - return visited; - } - - public void setDistance(int value) - { - distance = value; - } - - public void setVisited(boolean value) - { - visited = value; - } - - public PartitionNode bounds() - { - return bounds; - } - - public void addInboundDoorway(DoorwayData data) - { - inbound.add(data); - } - - public void addOutboundDoorway(DoorwayData data) - { - outbound.add(data); - } -} From 22ab4e363943df4d85f95fa24832196ef5b8d5f2 Mon Sep 17 00:00:00 2001 From: SenseiKiwi Date: Sun, 29 Dec 2013 03:11:02 -0400 Subject: [PATCH 06/16] Improved Second Step Deleted old function for removing random rooms. Fixed the occasional NPE described in my previous commit - deferred the removal of nodes from the graph until after iterating over their collection. Added conditions for removing maze sections that are too small to be useful. --- .../experimental/MazeGenerator.java | 57 ++++++++++--------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/src/main/java/StevenDimDoors/experimental/MazeGenerator.java b/src/main/java/StevenDimDoors/experimental/MazeGenerator.java index 4b5f076d..0911c9a7 100644 --- a/src/main/java/StevenDimDoors/experimental/MazeGenerator.java +++ b/src/main/java/StevenDimDoors/experimental/MazeGenerator.java @@ -58,17 +58,6 @@ public class MazeGenerator listRoomPartitions(node.rightChild(), partitions); } } - - private static void removeRandomRooms(ArrayList rooms, Random random) - { - // Randomly remove a fraction of the rooms - Collections.shuffle(rooms, random); - int remaining = rooms.size() / 2; - for (int k = rooms.size() - 1; k >= remaining; k--) - { - removeRoom(rooms.remove(k)); - } - } private static void removeRoom(PartitionNode node) { @@ -375,35 +364,31 @@ public class MazeGenerator // that was handled in a previous step! final int MAX_DISTANCE = 2; + final int MIN_SECTION_ROOMS = 5; int distance; IGraphNode current; IGraphNode neighbor; ArrayList> cores = new ArrayList>(); + ArrayList> removals = new ArrayList>(); + ArrayList> section = new ArrayList>(); + Queue> ordering = new LinkedList>(); HashMap, Integer> distances = new HashMap, Integer>(); // Repeatedly generate sections until all nodes have been visited for (IGraphNode node : roomGraph.nodes()) { - // If this node has an indegree and outdegree of 0, then it has no neighbors, - // which means it could not have been visited. This could happen if its neighbors - // were pruned away before. Single rooms look weird, so remove it. - if (node.indegree() == 0 && node.outdegree() == 0) - { - roomGraph.removeNode(node); - } - // If this node hasn't been visited, then use it as the core of a new section - // Otherwise, ignore it, since it already belongs to a section - else if (!distances.containsKey(node)) + // Otherwise, ignore it, since it was already processed + if (!distances.containsKey(node)) { - cores.add(node); - // Perform a breadth-first search to tag surrounding nodes with distances distances.put(node, 0); ordering.add(node); + section.clear(); + while (!ordering.isEmpty()) { current = ordering.remove(); @@ -411,6 +396,8 @@ public class MazeGenerator if (distance <= MAX_DISTANCE + 1) { + section.add(current); + // Visit neighboring nodes and assign them distances, if they don't // have a distance assigned already for (IEdge edge : current.inbound()) @@ -434,19 +421,37 @@ public class MazeGenerator } else { - roomGraph.removeNode(current); + removals.add(current); break; } } - // Remove all nodes that have a distance of exactly MAX_DISTANCE + 1 + // List nodes that have a distance of exactly MAX_DISTANCE + 1 // Those are precisely the nodes that remain in the queue + // We can't remove them immediately because that could break + // the iterator for the graph. while (!ordering.isEmpty()) { - roomGraph.removeNode( ordering.remove() ); + removals.add(ordering.remove()); + } + + // Check if this section contains enough rooms + if (section.size() >= MIN_SECTION_ROOMS) + { + cores.add(node); + } + else + { + removals.addAll(section); } } } + + // Remove all the nodes that were listed for removal + for (IGraphNode node : removals) + { + roomGraph.removeNode(node); + } return cores; } From cee400551357875e4aef00aa998b16d0ab706d86 Mon Sep 17 00:00:00 2001 From: SenseiKiwi Date: Sun, 29 Dec 2013 21:31:10 -0400 Subject: [PATCH 07/16] Reduced Floor Doorways Added code to minimize the number of doorways that involve dropping through the floor. Added a DisjointSet class as part of the implementation. I also split the maze construction process into two classes (MazeDesigner and MazeBuilder) to make it clearer. --- .../experimental/DisjointSet.java | 129 +++++++++++++ .../experimental/MazeBuilder.java | 137 +++++++++++++ .../experimental/MazeDesign.java | 48 +++++ .../{MazeGenerator.java => MazeDesigner.java} | 181 ++++++++---------- .../mod_pocketDim/world/PocketBuilder.java | 4 +- 5 files changed, 396 insertions(+), 103 deletions(-) create mode 100644 src/main/java/StevenDimDoors/experimental/DisjointSet.java create mode 100644 src/main/java/StevenDimDoors/experimental/MazeBuilder.java create mode 100644 src/main/java/StevenDimDoors/experimental/MazeDesign.java rename src/main/java/StevenDimDoors/experimental/{MazeGenerator.java => MazeDesigner.java} (74%) diff --git a/src/main/java/StevenDimDoors/experimental/DisjointSet.java b/src/main/java/StevenDimDoors/experimental/DisjointSet.java new file mode 100644 index 00000000..fc15269a --- /dev/null +++ b/src/main/java/StevenDimDoors/experimental/DisjointSet.java @@ -0,0 +1,129 @@ +package StevenDimDoors.experimental; + +import java.util.HashMap; + +public class DisjointSet +{ + // This class implements a disjoint set data structure that associates objects with sets. + + private static class SetNode

+ { + private int rank; + private SetNode

parent; + private P data; + + public SetNode(P data) + { + this.data = data; + this.rank = 0; + this.parent = null; + } + } + + private HashMap> mapping; + + public DisjointSet(int initialCapacity) + { + mapping = new HashMap>(initialCapacity); + } + + public boolean makeSet(T element) + { + if (!mapping.containsKey(element)) + { + mapping.put(element, new SetNode(element)); + return true; + } + else + { + return false; + } + } + + private SetNode findRootNode(T element) + { + SetNode node = mapping.get(element); + if (node == null) + { + return null; + } + if (node.parent != null) + { + node.parent = findRootNode(node.parent); + return node.parent; + } + else + { + return node; + } + } + + private SetNode findRootNode(SetNode node) + { + if (node.parent != null) + { + node.parent = findRootNode(node.parent); + return node.parent; + } + else + { + return node; + } + } + + public boolean mergeSets(T first, T second) + { + SetNode firstRoot = findRootNode(first); + SetNode secondRoot = findRootNode(second); + + if (firstRoot == null || secondRoot == null || + firstRoot == secondRoot) + { + return false; + } + + if (firstRoot.rank < secondRoot.rank) + { + firstRoot.parent = secondRoot; + } + else if (firstRoot.rank > secondRoot.rank) + { + secondRoot.parent = firstRoot; + } + else + { + secondRoot.parent = firstRoot; + firstRoot.rank++; + } + return true; + } + + public T find(T element) + { + SetNode root = findRootNode(element); + + if (root != null) + { + return root.data; + } + else + { + return null; + } + } + + public boolean haveSameSet(T first, T second) + { + SetNode firstRoot = findRootNode(first); + SetNode secondRoot = findRootNode(second); + + if (firstRoot == null || secondRoot == null) + { + return false; + } + else + { + return (firstRoot == secondRoot); + } + } +} diff --git a/src/main/java/StevenDimDoors/experimental/MazeBuilder.java b/src/main/java/StevenDimDoors/experimental/MazeBuilder.java new file mode 100644 index 00000000..dd4142cb --- /dev/null +++ b/src/main/java/StevenDimDoors/experimental/MazeBuilder.java @@ -0,0 +1,137 @@ +package StevenDimDoors.experimental; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Queue; +import java.util.Random; + +import net.minecraft.block.Block; +import net.minecraft.util.MathHelper; +import net.minecraft.world.World; +import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.chunk.storage.ExtendedBlockStorage; +import StevenDimDoors.mod_pocketDim.Point3D; + +public class MazeBuilder +{ + private MazeBuilder() { } + + public static void generate(World world, int x, int y, int z, Random random) + { + MazeDesign design = MazeDesigner.generate(random); + + buildRooms(design.getRoomGraph(), world, + new Point3D(x - design.width() / 2, y - design.height() - 1, z - design.length() / 2)); + } + + private static void buildRooms(DirectedGraph roomGraph, World world, Point3D offset) + { + for (IGraphNode node : roomGraph.nodes()) + { + PartitionNode room = node.data(); + buildBox(world, offset, room.minCorner(), room.maxCorner(), Block.stoneBrick.blockID, 0); + } + + // TESTING!!! + // This code carves out cheap doorways + // The final system will be better + // This has to happen after all the rooms have been built or the passages will be overwritten sometimes + for (IGraphNode node : roomGraph.nodes()) + { + for (IEdge doorway : node.outbound()) + { + char axis = doorway.data().axis(); + Point3D lower = doorway.data().minCorner(); + + if (axis == DoorwayData.Z_AXIS) + { + setBlockDirectly(world, offset.getX() + lower.getX() + 1, offset.getY() + lower.getY() + 1, offset.getZ() + lower.getZ(), 0, 0); + setBlockDirectly(world, offset.getX() + lower.getX() + 1, offset.getY() + lower.getY() + 2, offset.getZ() + lower.getZ(), 0, 0); + setBlockDirectly(world, offset.getX() + lower.getX() + 1, offset.getY() + lower.getY() + 1, offset.getZ() + lower.getZ() + 1, 0, 0); + setBlockDirectly(world, offset.getX() + lower.getX() + 1, offset.getY() + lower.getY() + 2, offset.getZ() + lower.getZ() + 1, 0, 0); + } + else if (axis == DoorwayData.X_AXIS) + { + setBlockDirectly(world, offset.getX() + lower.getX(), offset.getY() + lower.getY() + 1, offset.getZ() + lower.getZ() + 1, 0, 0); + setBlockDirectly(world, offset.getX() + lower.getX(), offset.getY() + lower.getY() + 2, offset.getZ() + lower.getZ() + 1, 0, 0); + setBlockDirectly(world, offset.getX() + lower.getX() + 1, offset.getY() + lower.getY() + 1, offset.getZ() + lower.getZ() + 1, 0, 0); + setBlockDirectly(world, offset.getX() + lower.getX() + 1, offset.getY() + lower.getY() + 2, offset.getZ() + lower.getZ() + 1, 0, 0); + } + else + { + setBlockDirectly(world, offset.getX() + lower.getX() + 1, offset.getY() + lower.getY(), offset.getZ() + lower.getZ() + 1, 0, 0); + setBlockDirectly(world, offset.getX() + lower.getX() + 1, offset.getY() + lower.getY(), offset.getZ() + lower.getZ() + 1, 0, 0); + setBlockDirectly(world, offset.getX() + lower.getX() + 1, offset.getY() + lower.getY() + 1, offset.getZ() + lower.getZ() + 1, 0, 0); + setBlockDirectly(world, offset.getX() + lower.getX() + 1, offset.getY() + lower.getY() + 1, offset.getZ() + lower.getZ() + 1, 0, 0); + } + } + } + } + + private static void buildBox(World world, Point3D offset, Point3D minCorner, Point3D maxCorner, int blockID, int metadata) + { + int minX = minCorner.getX() + offset.getX(); + int minY = minCorner.getY() + offset.getY(); + int minZ = minCorner.getZ() + offset.getZ(); + + int maxX = maxCorner.getX() + offset.getX(); + int maxY = maxCorner.getY() + offset.getY(); + int maxZ = maxCorner.getZ() + offset.getZ(); + + int x, y, z; + + for (x = minX; x <= maxX; x++) + { + for (z = minZ; z <= maxZ; z++) + { + setBlockDirectly(world, x, minY, z, blockID, metadata); + setBlockDirectly(world, x, maxY, z, blockID, metadata); + } + } + for (x = minX; x <= maxX; x++) + { + for (y = minY; y <= maxY; y++) + { + setBlockDirectly(world, x, y, minZ, blockID, metadata); + setBlockDirectly(world, x, y, maxZ, blockID, metadata); + } + } + for (z = minZ; z <= maxZ; z++) + { + for (y = minY; y <= maxY; y++) + { + setBlockDirectly(world, minX, y, z, blockID, metadata); + setBlockDirectly(world, maxX, y, z, blockID, metadata); + } + } + } + + private static void setBlockDirectly(World world, int x, int y, int z, int blockID, int metadata) + { + if (blockID != 0 && Block.blocksList[blockID] == null) + { + return; + } + + int cX = x >> 4; + int cZ = z >> 4; + int cY = y >> 4; + Chunk chunk; + + int localX = (x % 16) < 0 ? (x % 16) + 16 : (x % 16); + int localZ = (z % 16) < 0 ? (z % 16) + 16 : (z % 16); + ExtendedBlockStorage extBlockStorage; + + chunk = world.getChunkFromChunkCoords(cX, cZ); + extBlockStorage = chunk.getBlockStorageArray()[cY]; + if (extBlockStorage == null) + { + extBlockStorage = new ExtendedBlockStorage(cY << 4, !world.provider.hasNoSky); + chunk.getBlockStorageArray()[cY] = extBlockStorage; + } + extBlockStorage.setExtBlockID(localX, y & 15, localZ, blockID); + extBlockStorage.setExtBlockMetadata(localX, y & 15, localZ, metadata); + } +} diff --git a/src/main/java/StevenDimDoors/experimental/MazeDesign.java b/src/main/java/StevenDimDoors/experimental/MazeDesign.java new file mode 100644 index 00000000..d71b9566 --- /dev/null +++ b/src/main/java/StevenDimDoors/experimental/MazeDesign.java @@ -0,0 +1,48 @@ +package StevenDimDoors.experimental; + +import java.util.ArrayList; + +public class MazeDesign +{ + private PartitionNode root; + private DirectedGraph rooms; + private ArrayList> cores; + + public MazeDesign(PartitionNode root, DirectedGraph rooms, + ArrayList> cores) + { + this.root = root; + this.rooms = rooms; + this.cores = cores; + } + + public PartitionNode getRootPartition() + { + return root; + } + + public DirectedGraph getRoomGraph() + { + return rooms; + } + + public ArrayList> getCoreNodes() + { + return cores; + } + + public int width() + { + return root.width(); + } + + public int height() + { + return root.height(); + } + + public int length() + { + return root.length(); + } +} diff --git a/src/main/java/StevenDimDoors/experimental/MazeGenerator.java b/src/main/java/StevenDimDoors/experimental/MazeDesigner.java similarity index 74% rename from src/main/java/StevenDimDoors/experimental/MazeGenerator.java rename to src/main/java/StevenDimDoors/experimental/MazeDesigner.java index 0911c9a7..d74db285 100644 --- a/src/main/java/StevenDimDoors/experimental/MazeGenerator.java +++ b/src/main/java/StevenDimDoors/experimental/MazeDesigner.java @@ -6,29 +6,26 @@ import java.util.HashMap; import java.util.LinkedList; import java.util.Queue; import java.util.Random; +import java.util.Stack; -import net.minecraft.block.Block; import net.minecraft.util.MathHelper; -import net.minecraft.world.World; -import net.minecraft.world.chunk.Chunk; -import net.minecraft.world.chunk.storage.ExtendedBlockStorage; import StevenDimDoors.mod_pocketDim.Point3D; -public class MazeGenerator +public class MazeDesigner { - public static final int ROOT_WIDTH = 40; - public static final int ROOT_LENGTH = 40; - public static final int ROOT_HEIGHT = 20; + private static final int MAZE_WIDTH = 40; + private static final int MAZE_LENGTH = 40; + private static final int MAZE_HEIGHT = 20; private static final int MIN_HEIGHT = 4; private static final int MIN_SIDE = 3; private static final int SPLIT_COUNT = 9; - private MazeGenerator() { } + private MazeDesigner() { } - public static void generate(World world, int x, int y, int z, Random random) + public static MazeDesign generate(Random random) { // Construct a random binary space partitioning of our maze volume - PartitionNode root = partitionRooms(ROOT_WIDTH, ROOT_HEIGHT, ROOT_LENGTH, SPLIT_COUNT, random); + PartitionNode root = partitionRooms(MAZE_WIDTH, MAZE_HEIGHT, MAZE_LENGTH, SPLIT_COUNT, random); // List all the leaf nodes of the partition tree, which denote individual rooms ArrayList partitions = new ArrayList(1 << SPLIT_COUNT); @@ -42,10 +39,15 @@ public class MazeGenerator // Cut out random subgraphs from the adjacency graph ArrayList> cores = createMazeSections(rooms, random); + // Remove unnecessary passages through floors/ceilings + for (IGraphNode core : cores) + { + minimizeVerticalPassages(core, rooms, random); + } - buildRooms(rooms, world, new Point3D(x - ROOT_WIDTH / 2, y - ROOT_HEIGHT - 1, z - ROOT_WIDTH / 2)); + return new MazeDesign(root, rooms, cores); } - + private static void listRoomPartitions(PartitionNode node, ArrayList partitions) { if (node.isLeaf()) @@ -59,7 +61,7 @@ public class MazeGenerator } } - private static void removeRoom(PartitionNode node) + private static void removeRoomPartitions(PartitionNode node) { // Remove a node and any of its ancestors that become leaf nodes PartitionNode parent; @@ -448,119 +450,96 @@ public class MazeGenerator } // Remove all the nodes that were listed for removal + // Also remove unused partitions from the partition tree for (IGraphNode node : removals) { + removeRoomPartitions(node.data()); roomGraph.removeNode(node); } return cores; } - private static void buildRooms(DirectedGraph roomGraph, World world, Point3D offset) + private static void minimizeVerticalPassages(IGraphNode core, + DirectedGraph rooms, Random random) { - for (IGraphNode node : roomGraph.nodes()) + // We receive a node for one of the rooms in a section of the maze + // and we need to remove as many floor doorways as possible while + // still allowing any room to be reachable from any other room. + // In technical terms, we receive a node from a connected subgraph + // and we need to remove as many Y_AXIS-type edges as possible while + // preserving connectedness. + + // An efficient solution is to assign nodes to disjoint sets based + // on their components, ignoring all Y_AXIS edges, then iterate over + // a list of those edges and remove them if they connect two nodes + // in the same set. Otherwise, merge their sets and keep the edge. + // This is similar to algorithms for spanning trees. + + // First, list all nodes in the subgraph + IGraphNode current; + IGraphNode neighbor; + + Stack> ordering = new Stack>(); + ArrayList> subgraph = new ArrayList>(64); + DisjointSet> components = new DisjointSet>(128); + + ordering.add(core); + components.makeSet(core); + while (!ordering.isEmpty()) { - PartitionNode room = node.data(); - buildBox(world, offset, room.minCorner(), room.maxCorner(), Block.stoneBrick.blockID, 0); + current = ordering.pop(); + subgraph.add(current); + + for (IEdge edge : current.inbound()) + { + neighbor = edge.head(); + if (components.makeSet(neighbor)) + { + ordering.add(neighbor); + } + } + for (IEdge edge : current.outbound()) + { + neighbor = edge.tail(); + if (components.makeSet(neighbor)) + { + ordering.add(neighbor); + } + } } - // TESTING!!! - // This code carves out cheap doorways - // The final system will be better - // This has to happen after all the rooms have been built or the passages will be overwritten sometimes - for (IGraphNode node : roomGraph.nodes()) + // Now iterate over the list of nodes and merge their sets + // We only have to look at outbound edges since inbound edges mirror them + // Also list any Y_AXIS doorways we come across + ArrayList> targets = + new ArrayList>(); + + for (IGraphNode room : subgraph) { - for (IEdge doorway : node.outbound()) + for (IEdge passage : room.outbound()) { - char axis = doorway.data().axis(); - Point3D lower = doorway.data().minCorner(); - - if (axis == DoorwayData.Z_AXIS) + if (passage.data().axis() != DoorwayData.Y_AXIS) { - setBlockDirectly(world, offset.getX() + lower.getX() + 1, offset.getY() + lower.getY() + 1, offset.getZ() + lower.getZ(), 0, 0); - setBlockDirectly(world, offset.getX() + lower.getX() + 1, offset.getY() + lower.getY() + 2, offset.getZ() + lower.getZ(), 0, 0); - setBlockDirectly(world, offset.getX() + lower.getX() + 1, offset.getY() + lower.getY() + 1, offset.getZ() + lower.getZ() + 1, 0, 0); - setBlockDirectly(world, offset.getX() + lower.getX() + 1, offset.getY() + lower.getY() + 2, offset.getZ() + lower.getZ() + 1, 0, 0); - } - else if (axis == DoorwayData.X_AXIS) - { - setBlockDirectly(world, offset.getX() + lower.getX(), offset.getY() + lower.getY() + 1, offset.getZ() + lower.getZ() + 1, 0, 0); - setBlockDirectly(world, offset.getX() + lower.getX(), offset.getY() + lower.getY() + 2, offset.getZ() + lower.getZ() + 1, 0, 0); - setBlockDirectly(world, offset.getX() + lower.getX() + 1, offset.getY() + lower.getY() + 1, offset.getZ() + lower.getZ() + 1, 0, 0); - setBlockDirectly(world, offset.getX() + lower.getX() + 1, offset.getY() + lower.getY() + 2, offset.getZ() + lower.getZ() + 1, 0, 0); + components.mergeSets(passage.head(), passage.tail()); } else { - setBlockDirectly(world, offset.getX() + lower.getX() + 1, offset.getY() + lower.getY(), offset.getZ() + lower.getZ() + 1, 0, 0); - setBlockDirectly(world, offset.getX() + lower.getX() + 1, offset.getY() + lower.getY(), offset.getZ() + lower.getZ() + 1, 0, 0); - setBlockDirectly(world, offset.getX() + lower.getX() + 1, offset.getY() + lower.getY() + 1, offset.getZ() + lower.getZ() + 1, 0, 0); - setBlockDirectly(world, offset.getX() + lower.getX() + 1, offset.getY() + lower.getY() + 1, offset.getZ() + lower.getZ() + 1, 0, 0); + targets.add(passage); } } } - } - - private static void buildBox(World world, Point3D offset, Point3D minCorner, Point3D maxCorner, int blockID, int metadata) - { - int minX = minCorner.getX() + offset.getX(); - int minY = minCorner.getY() + offset.getY(); - int minZ = minCorner.getZ() + offset.getZ(); - int maxX = maxCorner.getX() + offset.getX(); - int maxY = maxCorner.getY() + offset.getY(); - int maxZ = maxCorner.getZ() + offset.getZ(); + // Shuffle the list of doorways to randomize which ones are removed + Collections.shuffle(targets, random); - int x, y, z; - - for (x = minX; x <= maxX; x++) + // Merge sets together and remove unnecessary doorways + for (IEdge passage : targets) { - for (z = minZ; z <= maxZ; z++) + if (!components.mergeSets(passage.head(), passage.tail())) { - setBlockDirectly(world, x, minY, z, blockID, metadata); - setBlockDirectly(world, x, maxY, z, blockID, metadata); - } - } - for (x = minX; x <= maxX; x++) - { - for (y = minY; y <= maxY; y++) - { - setBlockDirectly(world, x, y, minZ, blockID, metadata); - setBlockDirectly(world, x, y, maxZ, blockID, metadata); - } - } - for (z = minZ; z <= maxZ; z++) - { - for (y = minY; y <= maxY; y++) - { - setBlockDirectly(world, minX, y, z, blockID, metadata); - setBlockDirectly(world, maxX, y, z, blockID, metadata); + rooms.removeEdge(passage); } } } - private static void setBlockDirectly(World world, int x, int y, int z, int blockID, int metadata) - { - if (blockID != 0 && Block.blocksList[blockID] == null) - { - return; - } - - int cX = x >> 4; - int cZ = z >> 4; - int cY = y >> 4; - Chunk chunk; - - int localX = (x % 16) < 0 ? (x % 16) + 16 : (x % 16); - int localZ = (z % 16) < 0 ? (z % 16) + 16 : (z % 16); - ExtendedBlockStorage extBlockStorage; - - chunk = world.getChunkFromChunkCoords(cX, cZ); - extBlockStorage = chunk.getBlockStorageArray()[cY]; - if (extBlockStorage == null) - { - extBlockStorage = new ExtendedBlockStorage(cY << 4, !world.provider.hasNoSky); - chunk.getBlockStorageArray()[cY] = extBlockStorage; - } - extBlockStorage.setExtBlockID(localX, y & 15, localZ, blockID); - extBlockStorage.setExtBlockMetadata(localX, y & 15, localZ, metadata); - } } diff --git a/src/main/java/StevenDimDoors/mod_pocketDim/world/PocketBuilder.java b/src/main/java/StevenDimDoors/mod_pocketDim/world/PocketBuilder.java index f807bde0..061f2921 100644 --- a/src/main/java/StevenDimDoors/mod_pocketDim/world/PocketBuilder.java +++ b/src/main/java/StevenDimDoors/mod_pocketDim/world/PocketBuilder.java @@ -8,7 +8,7 @@ import net.minecraft.world.World; import net.minecraft.world.chunk.Chunk; import net.minecraft.world.chunk.storage.ExtendedBlockStorage; import net.minecraftforge.common.DimensionManager; -import StevenDimDoors.experimental.MazeGenerator; +import StevenDimDoors.experimental.MazeBuilder; import StevenDimDoors.mod_pocketDim.DDProperties; import StevenDimDoors.mod_pocketDim.Point3D; import StevenDimDoors.mod_pocketDim.blocks.IDimDoor; @@ -484,7 +484,7 @@ public class PocketBuilder } */ - MazeGenerator.generate(world, x, y, z, random); + MazeBuilder.generate(world, x, y, z, random); //Build the door int doorOrientation = BlockRotator.transformMetadata(BlockRotator.EAST_DOOR_METADATA, orientation - BlockRotator.EAST_DOOR_METADATA + 2, properties.DimensionalDoorID); From 3eaf6cdfb8315c0e01aee14aac442fd3f0c304b2 Mon Sep 17 00:00:00 2001 From: SenseiKiwi Date: Sun, 29 Dec 2013 22:35:13 -0400 Subject: [PATCH 08/16] Organized Doorway Code Separated the code that carves up doorways in mazes. Mostly a minor change in preparation for giving doors smarter placements. --- .../experimental/MazeBuilder.java | 51 ++++++++++++------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/src/main/java/StevenDimDoors/experimental/MazeBuilder.java b/src/main/java/StevenDimDoors/experimental/MazeBuilder.java index dd4142cb..5a14f6dc 100644 --- a/src/main/java/StevenDimDoors/experimental/MazeBuilder.java +++ b/src/main/java/StevenDimDoors/experimental/MazeBuilder.java @@ -21,9 +21,10 @@ public class MazeBuilder public static void generate(World world, int x, int y, int z, Random random) { MazeDesign design = MazeDesigner.generate(random); + Point3D offset = new Point3D(x - design.width() / 2, y - design.height() - 1, z - design.length() / 2); - buildRooms(design.getRoomGraph(), world, - new Point3D(x - design.width() / 2, y - design.height() - 1, z - design.length() / 2)); + buildRooms(design.getRoomGraph(), world, offset); + carveDoorways(design.getRoomGraph(), world, offset, random); } private static void buildRooms(DirectedGraph roomGraph, World world, Point3D offset) @@ -33,11 +34,10 @@ public class MazeBuilder PartitionNode room = node.data(); buildBox(world, offset, room.minCorner(), room.maxCorner(), Block.stoneBrick.blockID, 0); } - - // TESTING!!! - // This code carves out cheap doorways - // The final system will be better - // This has to happen after all the rooms have been built or the passages will be overwritten sometimes + } + + private static void carveDoorways(DirectedGraph roomGraph, World world, Point3D offset, Random random) + { for (IGraphNode node : roomGraph.nodes()) { for (IEdge doorway : node.outbound()) @@ -47,29 +47,42 @@ public class MazeBuilder if (axis == DoorwayData.Z_AXIS) { - setBlockDirectly(world, offset.getX() + lower.getX() + 1, offset.getY() + lower.getY() + 1, offset.getZ() + lower.getZ(), 0, 0); - setBlockDirectly(world, offset.getX() + lower.getX() + 1, offset.getY() + lower.getY() + 2, offset.getZ() + lower.getZ(), 0, 0); - setBlockDirectly(world, offset.getX() + lower.getX() + 1, offset.getY() + lower.getY() + 1, offset.getZ() + lower.getZ() + 1, 0, 0); - setBlockDirectly(world, offset.getX() + lower.getX() + 1, offset.getY() + lower.getY() + 2, offset.getZ() + lower.getZ() + 1, 0, 0); + carveDoorAlongZ(world, offset.getX() + lower.getX() + 1, offset.getY() + lower.getY() + 1, offset.getZ() + lower.getZ()); } else if (axis == DoorwayData.X_AXIS) { - setBlockDirectly(world, offset.getX() + lower.getX(), offset.getY() + lower.getY() + 1, offset.getZ() + lower.getZ() + 1, 0, 0); - setBlockDirectly(world, offset.getX() + lower.getX(), offset.getY() + lower.getY() + 2, offset.getZ() + lower.getZ() + 1, 0, 0); - setBlockDirectly(world, offset.getX() + lower.getX() + 1, offset.getY() + lower.getY() + 1, offset.getZ() + lower.getZ() + 1, 0, 0); - setBlockDirectly(world, offset.getX() + lower.getX() + 1, offset.getY() + lower.getY() + 2, offset.getZ() + lower.getZ() + 1, 0, 0); + carveDoorAlongX(world, offset.getX() + lower.getX(), offset.getY() + lower.getY() + 1, offset.getZ() + lower.getZ() + 1); } else { - setBlockDirectly(world, offset.getX() + lower.getX() + 1, offset.getY() + lower.getY(), offset.getZ() + lower.getZ() + 1, 0, 0); - setBlockDirectly(world, offset.getX() + lower.getX() + 1, offset.getY() + lower.getY(), offset.getZ() + lower.getZ() + 1, 0, 0); - setBlockDirectly(world, offset.getX() + lower.getX() + 1, offset.getY() + lower.getY() + 1, offset.getZ() + lower.getZ() + 1, 0, 0); - setBlockDirectly(world, offset.getX() + lower.getX() + 1, offset.getY() + lower.getY() + 1, offset.getZ() + lower.getZ() + 1, 0, 0); + carveHole(world, offset.getX() + lower.getX() + 1, offset.getY() + lower.getY(), offset.getZ() + lower.getZ() + 1); } } } } + private static void carveDoorAlongX(World world, int x, int y, int z) + { + setBlockDirectly(world, x, y, z, 0, 0); + setBlockDirectly(world, x, y + 1, z, 0, 0); + setBlockDirectly(world, x + 1, y, z, 0, 0); + setBlockDirectly(world, x + 1, y + 1, z, 0, 0); + } + + private static void carveDoorAlongZ(World world, int x, int y, int z) + { + setBlockDirectly(world, x, y, z, 0, 0); + setBlockDirectly(world, x, y + 1, z, 0, 0); + setBlockDirectly(world, x, y, z + 1, 0, 0); + setBlockDirectly(world, x, y + 1, z + 1, 0, 0); + } + + private static void carveHole(World world, int x, int y, int z) + { + setBlockDirectly(world, x, y, z, 0, 0); + setBlockDirectly(world, x, y + 1, z, 0, 0); + } + private static void buildBox(World world, Point3D offset, Point3D minCorner, Point3D maxCorner, int blockID, int metadata) { int minX = minCorner.getX() + offset.getX(); From d2da74ea760252fe0ee61ba7dbe9e7b9f8cad19c Mon Sep 17 00:00:00 2001 From: SenseiKiwi Date: Mon, 30 Dec 2013 00:25:08 -0400 Subject: [PATCH 09/16] Minor Change Adjusted the size of mazes slightly to reign in huge rooms. If the problem persists, we can consider other options such as dropping dungeon sizes a little more, increasing the number of splits, or biasing the split plane selection toward the middle of the range. --- src/main/java/StevenDimDoors/experimental/MazeDesigner.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/StevenDimDoors/experimental/MazeDesigner.java b/src/main/java/StevenDimDoors/experimental/MazeDesigner.java index d74db285..6bc128e0 100644 --- a/src/main/java/StevenDimDoors/experimental/MazeDesigner.java +++ b/src/main/java/StevenDimDoors/experimental/MazeDesigner.java @@ -13,8 +13,8 @@ import StevenDimDoors.mod_pocketDim.Point3D; public class MazeDesigner { - private static final int MAZE_WIDTH = 40; - private static final int MAZE_LENGTH = 40; + private static final int MAZE_WIDTH = 34; + private static final int MAZE_LENGTH = 34; private static final int MAZE_HEIGHT = 20; private static final int MIN_HEIGHT = 4; private static final int MIN_SIDE = 3; From 27b21f6674c53a182ec77484b826010caf2c8744 Mon Sep 17 00:00:00 2001 From: SenseiKiwi Date: Mon, 30 Dec 2013 01:22:31 -0400 Subject: [PATCH 10/16] Fixed Bug in LinkedList Fixed a bug in LinkedList - the size of the list never increased when elements were added. --- src/main/java/StevenDimDoors/experimental/LinkedList.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/StevenDimDoors/experimental/LinkedList.java b/src/main/java/StevenDimDoors/experimental/LinkedList.java index 8d6b9751..98ee0fa7 100644 --- a/src/main/java/StevenDimDoors/experimental/LinkedList.java +++ b/src/main/java/StevenDimDoors/experimental/LinkedList.java @@ -226,6 +226,7 @@ public class LinkedList implements Iterable Node addition = new Node(node, node.next, data, this); node.next = addition; addition.next.prev = addition; + size++; return addition; } From 7afcfedbded95596d9106699197ee256ab2317c5 Mon Sep 17 00:00:00 2001 From: SenseiKiwi Date: Mon, 30 Dec 2013 02:09:31 -0400 Subject: [PATCH 11/16] Fixed Bug in MazeDesigner Fixed a bug in MazeDesigner that would connect some rooms with duplicate doorways. The problem was in how intersections were being tracked to improve the efficiency of adjacency checks. --- .../experimental/MazeDesigner.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/StevenDimDoors/experimental/MazeDesigner.java b/src/main/java/StevenDimDoors/experimental/MazeDesigner.java index 6bc128e0..09c6df40 100644 --- a/src/main/java/StevenDimDoors/experimental/MazeDesigner.java +++ b/src/main/java/StevenDimDoors/experimental/MazeDesigner.java @@ -234,11 +234,11 @@ public class MazeDesigner minYI = Math.max(minY, otherMin.getY()); maxYI = Math.min(maxY, otherMax.getY()); - for (p = a; p <= maxXI - minXI; p++) + for (p = 0; p <= maxXI - minXI; p++) { - for (q = b; q <= maxYI - minYI; q++) + for (q = 0; q <= maxYI - minYI; q++) { - detected[p][q] = true; + detected[p + a][q + b] = true; } } // Check if we meet the minimum dimensions needed for a doorway @@ -282,11 +282,11 @@ public class MazeDesigner minZI = Math.max(minZ, otherMin.getZ()); maxZI = Math.min(maxZ, otherMax.getZ()); - for (q = b; q <= maxYI - minYI; q++) + for (q = 0; q <= maxYI - minYI; q++) { - for (r = c; r <= maxZI - minZI; r++) + for (r = 0; r <= maxZI - minZI; r++) { - detected[q][r] = true; + detected[q + b][r + c] = true; } } // Check if we meet the minimum dimensions needed for a doorway @@ -330,11 +330,11 @@ public class MazeDesigner minZI = Math.max(minZ, otherMin.getZ()); maxZI = Math.min(maxZ, otherMax.getZ()); - for (p = a; p <= maxXI - minXI; p++) + for (p = 0; p <= maxXI - minXI; p++) { - for (r = c; r <= maxZI - minZI; r++) + for (r = 0; r <= maxZI - minZI; r++) { - detected[p][r] = true; + detected[p + a][r + c] = true; } } // Check if we meet the minimum dimensions needed for a doorway From 3574b0468bd3ac4d63653b62c622e81b5639eba5 Mon Sep 17 00:00:00 2001 From: SenseiKiwi Date: Mon, 30 Dec 2013 02:44:18 -0400 Subject: [PATCH 12/16] Improved Doorway Placement Doorways are now placed in different ways, depending on the dimensions of the walls that they're on. This includes that large walls get two doorways connecting to the same room. --- .../experimental/DoorwayData.java | 15 ++++ .../experimental/MazeBuilder.java | 84 ++++++++++++++++--- 2 files changed, 86 insertions(+), 13 deletions(-) diff --git a/src/main/java/StevenDimDoors/experimental/DoorwayData.java b/src/main/java/StevenDimDoors/experimental/DoorwayData.java index ec1c5cc8..5d596c2b 100644 --- a/src/main/java/StevenDimDoors/experimental/DoorwayData.java +++ b/src/main/java/StevenDimDoors/experimental/DoorwayData.java @@ -33,4 +33,19 @@ public class DoorwayData { return axis; } + + public int width() + { + return (maxCorner.getX() - minCorner.getX() + 1); + } + + public int height() + { + return (maxCorner.getY() - minCorner.getY() + 1); + } + + public int length() + { + return (maxCorner.getZ() - minCorner.getZ() + 1); + } } diff --git a/src/main/java/StevenDimDoors/experimental/MazeBuilder.java b/src/main/java/StevenDimDoors/experimental/MazeBuilder.java index 5a14f6dc..e4ea77ab 100644 --- a/src/main/java/StevenDimDoors/experimental/MazeBuilder.java +++ b/src/main/java/StevenDimDoors/experimental/MazeBuilder.java @@ -38,24 +38,82 @@ public class MazeBuilder private static void carveDoorways(DirectedGraph roomGraph, World world, Point3D offset, Random random) { + final int MIN_DOUBLE_DOOR_SPAN = 10; + + int gap; + char axis; + Point3D lower; + Point3D upper; + DoorwayData doorway; + for (IGraphNode node : roomGraph.nodes()) { - for (IEdge doorway : node.outbound()) + for (IEdge passage : node.outbound()) { - char axis = doorway.data().axis(); - Point3D lower = doorway.data().minCorner(); + doorway = passage.data(); + axis = doorway.axis(); + lower = doorway.minCorner(); + upper = doorway.maxCorner(); - if (axis == DoorwayData.Z_AXIS) + switch (axis) { - carveDoorAlongZ(world, offset.getX() + lower.getX() + 1, offset.getY() + lower.getY() + 1, offset.getZ() + lower.getZ()); - } - else if (axis == DoorwayData.X_AXIS) - { - carveDoorAlongX(world, offset.getX() + lower.getX(), offset.getY() + lower.getY() + 1, offset.getZ() + lower.getZ() + 1); - } - else - { - carveHole(world, offset.getX() + lower.getX() + 1, offset.getY() + lower.getY(), offset.getZ() + lower.getZ() + 1); + case DoorwayData.X_AXIS: + if (doorway.length() >= MIN_DOUBLE_DOOR_SPAN) + { + gap = (doorway.length() - 2) / 3; + carveDoorAlongX(world, offset.getX() + lower.getX(), offset.getY() + lower.getY() + 1, offset.getZ() + lower.getZ() + gap); + carveDoorAlongX(world, offset.getX() + lower.getX(), offset.getY() + lower.getY() + 1, offset.getZ() + upper.getZ() - gap); + } + else if (doorway.length() > 3) + { + switch (random.nextInt(3)) + { + case 0: + carveDoorAlongX(world, offset.getX() + lower.getX(), offset.getY() + lower.getY() + 1, offset.getZ() + (lower.getZ() + upper.getZ()) / 2); + break; + case 1: + carveDoorAlongX(world, offset.getX() + lower.getX(), offset.getY() + lower.getY() + 1, offset.getZ() + lower.getZ() + 2); + break; + case 2: + carveDoorAlongX(world, offset.getX() + lower.getX(), offset.getY() + lower.getY() + 1, offset.getZ() + upper.getZ() - 2); + break; + } + } + else + { + carveDoorAlongX(world, offset.getX() + lower.getX(), offset.getY() + lower.getY() + 1, offset.getZ() + lower.getZ() + 1); + } + break; + case DoorwayData.Z_AXIS: + if (doorway.width() >= MIN_DOUBLE_DOOR_SPAN) + { + gap = (doorway.width() - 2) / 3; + carveDoorAlongZ(world, offset.getX() + lower.getX() + gap, offset.getY() + lower.getY() + 1, offset.getZ() + lower.getZ()); + carveDoorAlongZ(world, offset.getX() + upper.getX() - gap, offset.getY() + lower.getY() + 1, offset.getZ() + lower.getZ()); + } + else if (doorway.length() > 3) + { + switch (random.nextInt(3)) + { + case 0: + carveDoorAlongZ(world, offset.getX() + (lower.getX() + upper.getX()) / 2, offset.getY() + lower.getY() + 1, offset.getZ() + lower.getZ()); + break; + case 1: + carveDoorAlongZ(world, offset.getX() + lower.getX() + 2, offset.getY() + lower.getY() + 1, offset.getZ() + lower.getZ()); + break; + case 2: + carveDoorAlongZ(world, offset.getX() + upper.getX() - 2, offset.getY() + lower.getY() + 1, offset.getZ() + lower.getZ()); + break; + } + } + else + { + carveDoorAlongZ(world, offset.getX() + lower.getX() + 1, offset.getY() + lower.getY() + 1, offset.getZ() + lower.getZ()); + } + break; + case DoorwayData.Y_AXIS: + carveHole(world, offset.getX() + lower.getX() + 1, offset.getY() + lower.getY(), offset.getZ() + lower.getZ() + 1); + break; } } } From 70ae2fd407461332a6a84060966cdc6c04fb1288 Mon Sep 17 00:00:00 2001 From: SenseiKiwi Date: Mon, 30 Dec 2013 03:21:42 -0400 Subject: [PATCH 13/16] Improved Doorway Placement Changed MazeDesigner to prune out some unnecessary doorways at random by removing edges from the room graph. Previous mazes allowed all side paths to exist, which resulted in a kind of circular layout. Although that was disorienting to people at first, if you realized it was a circle, it became simple to navigate through the maze. This update makes branching paths more common. --- .../experimental/DisjointSet.java | 5 +++ .../experimental/MazeDesigner.java | 44 ++++++++++++++++--- 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/src/main/java/StevenDimDoors/experimental/DisjointSet.java b/src/main/java/StevenDimDoors/experimental/DisjointSet.java index fc15269a..4b4150fc 100644 --- a/src/main/java/StevenDimDoors/experimental/DisjointSet.java +++ b/src/main/java/StevenDimDoors/experimental/DisjointSet.java @@ -126,4 +126,9 @@ public class DisjointSet return (firstRoot == secondRoot); } } + + public void clear() + { + mapping.clear(); + } } diff --git a/src/main/java/StevenDimDoors/experimental/MazeDesigner.java b/src/main/java/StevenDimDoors/experimental/MazeDesigner.java index 09c6df40..6fb89b90 100644 --- a/src/main/java/StevenDimDoors/experimental/MazeDesigner.java +++ b/src/main/java/StevenDimDoors/experimental/MazeDesigner.java @@ -39,10 +39,10 @@ public class MazeDesigner // Cut out random subgraphs from the adjacency graph ArrayList> cores = createMazeSections(rooms, random); - // Remove unnecessary passages through floors/ceilings + // Remove unnecessary passages through floors/ceilings and some from the walls for (IGraphNode core : cores) { - minimizeVerticalPassages(core, rooms, random); + pruneDoorways(core, rooms, random); } return new MazeDesign(root, rooms, cores); @@ -459,7 +459,7 @@ public class MazeDesigner return cores; } - private static void minimizeVerticalPassages(IGraphNode core, + private static void pruneDoorways(IGraphNode core, DirectedGraph rooms, Random random) { // We receive a node for one of the rooms in a section of the maze @@ -467,13 +467,15 @@ public class MazeDesigner // still allowing any room to be reachable from any other room. // In technical terms, we receive a node from a connected subgraph // and we need to remove as many Y_AXIS-type edges as possible while - // preserving connectedness. + // preserving connectedness. We also want to randomly remove some of + // the other doorways without breaking connectedness. // An efficient solution is to assign nodes to disjoint sets based // on their components, ignoring all Y_AXIS edges, then iterate over // a list of those edges and remove them if they connect two nodes // in the same set. Otherwise, merge their sets and keep the edge. - // This is similar to algorithms for spanning trees. + // This is similar to algorithms for spanning trees. The same + // idea applies for the other doorways with some chance added. // First, list all nodes in the subgraph IGraphNode current; @@ -540,6 +542,38 @@ public class MazeDesigner rooms.removeEdge(passage); } } + + // Repeat the pruning process with X_AXIS and Z_AXIS doorways + // In this case, unnecessary edges might be kept at random + components.clear(); + targets.clear(); + + for (IGraphNode room : subgraph) + { + components.makeSet(room); + } + for (IGraphNode room : subgraph) + { + for (IEdge passage : room.outbound()) + { + if (passage.data().axis() == DoorwayData.Y_AXIS) + { + components.mergeSets(passage.head(), passage.tail()); + } + else + { + targets.add(passage); + } + } + } + Collections.shuffle(targets, random); + for (IEdge passage : targets) + { + if (!components.mergeSets(passage.head(), passage.tail()) && random.nextBoolean()) + { + rooms.removeEdge(passage); + } + } } } From 3e33b94c9888dd0d927c25b2003eaa52373718bf Mon Sep 17 00:00:00 2001 From: SenseiKiwi Date: Mon, 30 Dec 2013 03:29:05 -0400 Subject: [PATCH 14/16] Minor Change Changed a comment --- src/main/java/StevenDimDoors/experimental/MazeDesigner.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/StevenDimDoors/experimental/MazeDesigner.java b/src/main/java/StevenDimDoors/experimental/MazeDesigner.java index 6fb89b90..289931a5 100644 --- a/src/main/java/StevenDimDoors/experimental/MazeDesigner.java +++ b/src/main/java/StevenDimDoors/experimental/MazeDesigner.java @@ -475,7 +475,7 @@ public class MazeDesigner // a list of those edges and remove them if they connect two nodes // in the same set. Otherwise, merge their sets and keep the edge. // This is similar to algorithms for spanning trees. The same - // idea applies for the other doorways with some chance added. + // idea applies for the other doorways, plus some randomness. // First, list all nodes in the subgraph IGraphNode current; From 5e60960661461d6ac17d40278ed60cf74f2bed0b Mon Sep 17 00:00:00 2001 From: SenseiKiwi Date: Mon, 30 Dec 2013 19:59:17 -0400 Subject: [PATCH 15/16] Completed Doorway Placement All doorways are placed sensibly now, including vertical passages between different floors. --- .../experimental/MazeBuilder.java | 174 ++++++++++-------- .../experimental/SphereDecayOperation.java | 84 +++++++++ 2 files changed, 183 insertions(+), 75 deletions(-) create mode 100644 src/main/java/StevenDimDoors/experimental/SphereDecayOperation.java diff --git a/src/main/java/StevenDimDoors/experimental/MazeBuilder.java b/src/main/java/StevenDimDoors/experimental/MazeBuilder.java index e4ea77ab..aa469a46 100644 --- a/src/main/java/StevenDimDoors/experimental/MazeBuilder.java +++ b/src/main/java/StevenDimDoors/experimental/MazeBuilder.java @@ -1,14 +1,8 @@ package StevenDimDoors.experimental; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.Queue; import java.util.Random; import net.minecraft.block.Block; -import net.minecraft.util.MathHelper; import net.minecraft.world.World; import net.minecraft.world.chunk.Chunk; import net.minecraft.world.chunk.storage.ExtendedBlockStorage; @@ -22,11 +16,20 @@ public class MazeBuilder { MazeDesign design = MazeDesigner.generate(random); Point3D offset = new Point3D(x - design.width() / 2, y - design.height() - 1, z - design.length() / 2); + SphereDecayOperation decay = new SphereDecayOperation(random, 0, 0, Block.stoneBrick.blockID, 2); buildRooms(design.getRoomGraph(), world, offset); - carveDoorways(design.getRoomGraph(), world, offset, random); + carveDoorways(design.getRoomGraph(), world, offset, decay, random); + + applyRandomDestruction(design, world, offset, decay, random); } + private static void applyRandomDestruction(MazeDesign design, World world, + Point3D offset, SphereDecayOperation decay, Random random) + { + //final int DECAY_BOX_SIZE = 8 + } + private static void buildRooms(DirectedGraph roomGraph, World world, Point3D offset) { for (IGraphNode node : roomGraph.nodes()) @@ -36,14 +39,11 @@ public class MazeBuilder } } - private static void carveDoorways(DirectedGraph roomGraph, World world, Point3D offset, Random random) - { - final int MIN_DOUBLE_DOOR_SPAN = 10; - - int gap; + private static void carveDoorways(DirectedGraph roomGraph, World world, + Point3D offset, SphereDecayOperation decay, Random random) + { char axis; Point3D lower; - Point3D upper; DoorwayData doorway; for (IGraphNode node : roomGraph.nodes()) @@ -53,72 +53,95 @@ public class MazeBuilder doorway = passage.data(); axis = doorway.axis(); lower = doorway.minCorner(); - upper = doorway.maxCorner(); - - switch (axis) - { - case DoorwayData.X_AXIS: - if (doorway.length() >= MIN_DOUBLE_DOOR_SPAN) - { - gap = (doorway.length() - 2) / 3; - carveDoorAlongX(world, offset.getX() + lower.getX(), offset.getY() + lower.getY() + 1, offset.getZ() + lower.getZ() + gap); - carveDoorAlongX(world, offset.getX() + lower.getX(), offset.getY() + lower.getY() + 1, offset.getZ() + upper.getZ() - gap); - } - else if (doorway.length() > 3) - { - switch (random.nextInt(3)) - { - case 0: - carveDoorAlongX(world, offset.getX() + lower.getX(), offset.getY() + lower.getY() + 1, offset.getZ() + (lower.getZ() + upper.getZ()) / 2); - break; - case 1: - carveDoorAlongX(world, offset.getX() + lower.getX(), offset.getY() + lower.getY() + 1, offset.getZ() + lower.getZ() + 2); - break; - case 2: - carveDoorAlongX(world, offset.getX() + lower.getX(), offset.getY() + lower.getY() + 1, offset.getZ() + upper.getZ() - 2); - break; - } - } - else - { - carveDoorAlongX(world, offset.getX() + lower.getX(), offset.getY() + lower.getY() + 1, offset.getZ() + lower.getZ() + 1); - } - break; - case DoorwayData.Z_AXIS: - if (doorway.width() >= MIN_DOUBLE_DOOR_SPAN) - { - gap = (doorway.width() - 2) / 3; - carveDoorAlongZ(world, offset.getX() + lower.getX() + gap, offset.getY() + lower.getY() + 1, offset.getZ() + lower.getZ()); - carveDoorAlongZ(world, offset.getX() + upper.getX() - gap, offset.getY() + lower.getY() + 1, offset.getZ() + lower.getZ()); - } - else if (doorway.length() > 3) - { - switch (random.nextInt(3)) - { - case 0: - carveDoorAlongZ(world, offset.getX() + (lower.getX() + upper.getX()) / 2, offset.getY() + lower.getY() + 1, offset.getZ() + lower.getZ()); - break; - case 1: - carveDoorAlongZ(world, offset.getX() + lower.getX() + 2, offset.getY() + lower.getY() + 1, offset.getZ() + lower.getZ()); - break; - case 2: - carveDoorAlongZ(world, offset.getX() + upper.getX() - 2, offset.getY() + lower.getY() + 1, offset.getZ() + lower.getZ()); - break; - } - } - else - { - carveDoorAlongZ(world, offset.getX() + lower.getX() + 1, offset.getY() + lower.getY() + 1, offset.getZ() + lower.getZ()); - } - break; - case DoorwayData.Y_AXIS: - carveHole(world, offset.getX() + lower.getX() + 1, offset.getY() + lower.getY(), offset.getZ() + lower.getZ() + 1); - break; - } + carveDoorway(world, axis, offset.getX() + lower.getX(), offset.getY() + lower.getY(), + offset.getZ() + lower.getZ(), doorway.width(), doorway.height(), doorway.length(), + decay, random); } } } + private static void carveDoorway(World world, char axis, int x, int y, int z, int width, int height, + int length, SphereDecayOperation decay, Random random) + { + final int MIN_DOUBLE_DOOR_SPAN = 10; + + int gap; + switch (axis) + { + case DoorwayData.X_AXIS: + if (length >= MIN_DOUBLE_DOOR_SPAN) + { + gap = (length - 2) / 3; + carveDoorAlongX(world, x, y + 1, z + gap); + carveDoorAlongX(world, x, y + 1, z + length - gap - 1); + } + else if (length > 3) + { + switch (random.nextInt(3)) + { + case 0: + carveDoorAlongX(world, x, y + 1, z + (length - 1) / 2); + break; + case 1: + carveDoorAlongX(world, x, y + 1, z + 2); + break; + case 2: + carveDoorAlongX(world, x, y + 1, z + length - 3); + break; + } + } + else + { + carveDoorAlongX(world, x, y + 1, z + 1); + } + break; + case DoorwayData.Z_AXIS: + if (width >= MIN_DOUBLE_DOOR_SPAN) + { + gap = (width - 2) / 3; + carveDoorAlongZ(world, x + gap, y + 1, z); + carveDoorAlongZ(world, x + width - gap - 1, y + 1, z); + } + else if (length > 3) + { + switch (random.nextInt(3)) + { + case 0: + carveDoorAlongZ(world, x + (width - 1) / 2, y + 1, z); + break; + case 1: + carveDoorAlongZ(world, x + 2, y + 1, z); + break; + case 2: + carveDoorAlongZ(world, x + width - 3, y + 1, z); + break; + } + } + else + { + carveDoorAlongZ(world, x + 1, y + 1, z); + } + break; + case DoorwayData.Y_AXIS: + gap = Math.min(width, length) - 2; + if (gap > 1) + { + if (gap > 6) + { + gap = 6; + } + decay.apply(world, + x + random.nextInt(width - gap - 1) + 1, y - 1, + z + random.nextInt(length - gap - 1) + 1, gap, 4, gap); + } + else + { + carveHole(world, x + 1, y, z + 1); + } + break; + } + } + private static void carveDoorAlongX(World world, int x, int y, int z) { setBlockDirectly(world, x, y, z, 0, 0); @@ -140,6 +163,7 @@ public class MazeBuilder setBlockDirectly(world, x, y, z, 0, 0); setBlockDirectly(world, x, y + 1, z, 0, 0); } + private static void buildBox(World world, Point3D offset, Point3D minCorner, Point3D maxCorner, int blockID, int metadata) { diff --git a/src/main/java/StevenDimDoors/experimental/SphereDecayOperation.java b/src/main/java/StevenDimDoors/experimental/SphereDecayOperation.java new file mode 100644 index 00000000..95587b64 --- /dev/null +++ b/src/main/java/StevenDimDoors/experimental/SphereDecayOperation.java @@ -0,0 +1,84 @@ +package StevenDimDoors.experimental; + +import java.util.Random; + +import net.minecraft.block.Block; +import net.minecraft.block.BlockContainer; +import net.minecraft.inventory.IInventory; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.tileentity.TileEntityChest; +import net.minecraft.tileentity.TileEntityDispenser; +import net.minecraft.world.World; +import StevenDimDoors.mod_pocketDim.DDLoot; +import StevenDimDoors.mod_pocketDim.DDProperties; +import StevenDimDoors.mod_pocketDim.Point3D; +import StevenDimDoors.mod_pocketDim.schematic.WorldOperation; + +/** + * Provides an operation for damaging structures based on a spherical area. The chance of damage decreases + * with the square of the distance from the center of the sphere. + * @author SenseiKiwi + * + */ +public class SphereDecayOperation extends WorldOperation +{ + private Random random; + private double scaling; + private double centerX; + private double centerY; + private double centerZ; + private int primaryBlockID; + private int primaryMetadata; + private int secondaryBlockID; + private int secondaryMetadata; + + public SphereDecayOperation(Random random, int primaryBlockID, int primaryMetadata, int secondaryBlockID, int secondaryMetadata) + { + super("SphereDecayOperation"); + this.random = random; + this.primaryBlockID = primaryBlockID; + this.primaryMetadata = primaryMetadata; + this.secondaryBlockID = secondaryBlockID; + this.secondaryMetadata = secondaryMetadata; + } + + @Override + protected boolean initialize(World world, int x, int y, int z, int width, int height, int length) + { + // Calculate a scaling factor so that the probability of decay + // at the edge of the largest dimension of our bounds is 20%. + scaling = Math.max(width - 1, Math.max(height - 1, length - 1)) / 2.0; + scaling *= scaling * 0.20; + + centerX = x + width / 2.0; + centerY = y + height / 2.0; + centerZ = z + length / 2.0; + return true; + } + + @Override + protected boolean applyToBlock(World world, int x, int y, int z) + { + // Don't raise any notifications. This operation is only designed to run + // when a dimension is being generated, which means there are no players around. + if (!world.isAirBlock(x, y, z)) + { + double dx = (centerX - x - 0.5); + double dy = (centerY - y - 0.5); + double dz = (centerZ - z - 0.5); + double squareDistance = dx * dx + dy * dy + dz * dz; + + if (squareDistance < 0.5 || random.nextDouble() < scaling / squareDistance) + { + world.setBlock(x, y, z, primaryBlockID, primaryMetadata, 1); + } + else if (random.nextDouble() < scaling / squareDistance) + { + world.setBlock(x, y, z, secondaryBlockID, secondaryMetadata, 1); + } + } + return true; + } +} From 394f92d5fc12d759c16734bd3fd93618c459387a Mon Sep 17 00:00:00 2001 From: StevenRS11 Date: Sat, 4 Jan 2014 01:10:33 -0600 Subject: [PATCH 16/16] Fixed rending in pockets for doors DDoors get a TE when they are generated now. May fix other bugs too, who knows. --- .../mod_pocketDim/blocks/BaseDimDoor.java | 8 +++++- .../mod_pocketDim/blocks/IDimDoor.java | 2 ++ .../mod_pocketDim/blocks/TransTrapdoor.java | 7 +++++ .../mod_pocketDim/core/PocketManager.java | 23 +++++++++++----- .../dungeon/DungeonSchematic.java | 27 +++++++++++++++++++ .../tileentities/TileEntityDimDoor.java | 5 ++-- .../mod_pocketDim/world/PocketBuilder.java | 6 ++--- 7 files changed, 65 insertions(+), 13 deletions(-) diff --git a/src/main/java/StevenDimDoors/mod_pocketDim/blocks/BaseDimDoor.java b/src/main/java/StevenDimDoors/mod_pocketDim/blocks/BaseDimDoor.java index 54f844f0..e0084c64 100644 --- a/src/main/java/StevenDimDoors/mod_pocketDim/blocks/BaseDimDoor.java +++ b/src/main/java/StevenDimDoors/mod_pocketDim/blocks/BaseDimDoor.java @@ -25,7 +25,6 @@ import StevenDimDoors.mod_pocketDim.tileentities.TileEntityDimDoor; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; -@SuppressWarnings("deprecation") public abstract class BaseDimDoor extends BlockDoor implements IDimDoor, ITileEntityProvider { protected final DDProperties properties; @@ -429,4 +428,11 @@ public abstract class BaseDimDoor extends BlockDoor implements IDimDoor, ITileEn int direction = MathHelper.floor_double((entity.rotationYaw + 90) * 4.0F / 360.0F + 0.5D) & 3; return ((metadata & 3) == direction); } + + @Override + public void initDoorTE(World world, int x, int y, int z) + { + TileEntity te = this.createNewTileEntity(world); + world.setBlockTileEntity(x, y, z, te); + } } \ No newline at end of file diff --git a/src/main/java/StevenDimDoors/mod_pocketDim/blocks/IDimDoor.java b/src/main/java/StevenDimDoors/mod_pocketDim/blocks/IDimDoor.java index 39bfcd20..e5939152 100644 --- a/src/main/java/StevenDimDoors/mod_pocketDim/blocks/IDimDoor.java +++ b/src/main/java/StevenDimDoors/mod_pocketDim/blocks/IDimDoor.java @@ -10,4 +10,6 @@ public interface IDimDoor public void placeLink(World world, int x, int y, int z); public int getDrops(); + + public void initDoorTE(World world, int x, int y, int z); } diff --git a/src/main/java/StevenDimDoors/mod_pocketDim/blocks/TransTrapdoor.java b/src/main/java/StevenDimDoors/mod_pocketDim/blocks/TransTrapdoor.java index 1ff68132..6eac539b 100644 --- a/src/main/java/StevenDimDoors/mod_pocketDim/blocks/TransTrapdoor.java +++ b/src/main/java/StevenDimDoors/mod_pocketDim/blocks/TransTrapdoor.java @@ -118,4 +118,11 @@ public class TransTrapdoor extends BlockTrapDoor implements IDimDoor, ITileEntit { return (metadata & 8) == 0; } + + @Override + public void initDoorTE(World world, int x, int y, int z) + { + TileEntity te = this.createNewTileEntity(world); + world.setBlockTileEntity(x, y, z, te); + } } \ No newline at end of file diff --git a/src/main/java/StevenDimDoors/mod_pocketDim/core/PocketManager.java b/src/main/java/StevenDimDoors/mod_pocketDim/core/PocketManager.java index a27b86a2..2df0f186 100644 --- a/src/main/java/StevenDimDoors/mod_pocketDim/core/PocketManager.java +++ b/src/main/java/StevenDimDoors/mod_pocketDim/core/PocketManager.java @@ -300,7 +300,6 @@ public class PocketManager PocketManager.dimensionData.put(dimData.id, dimData); dimWatcher.onCreated(new ClientDimData(dimData)); - return true; } public static boolean deletePocket(NewDimData target, boolean deleteFolder) @@ -403,7 +402,7 @@ public class PocketManager */ private static void loadInternal() { - System.out.println(!FMLCommonHandler.instance().getSide().isClient()); + //System.out.println(!FMLCommonHandler.instance().getSide().isClient()); File saveDir = DimensionManager.getCurrentSaveRootDirectory(); if (saveDir != null) @@ -507,14 +506,24 @@ public class PocketManager DimensionManager.registerDimension(dimensionID, properties.PocketProviderID); return registerDimension(dimensionID, (InnerDimData) parent, true, isDungeon); } - + /** + * Registers a dimension with DD but NOT with forge. + * @param dimensionID + * @param parent + * @param isPocket + * @param isDungeon + * @return + */ private static NewDimData registerDimension(int dimensionID, InnerDimData parent, boolean isPocket, boolean isDungeon) - { + { if (dimensionData.containsKey(dimensionID)) { + if(PocketManager.dimensionIDBlackList.contains(dimensionID)) + { + throw new IllegalArgumentException("Cannot register a dimension with ID = " + dimensionID + " because it has been blacklisted."); + } throw new IllegalArgumentException("Cannot register a dimension with ID = " + dimensionID + " because it has already been registered."); } - //TODO blacklist stuff probably should happen here InnerDimData dimension = new InnerDimData(dimensionID, parent, isPocket, isDungeon, linkWatcher); dimensionData.put(dimensionID, dimension); if (!dimension.isPocketDimension()) @@ -529,6 +538,7 @@ public class PocketManager @SideOnly(Side.CLIENT) private static NewDimData registerClientDimension(int dimensionID, int rootID) { + System.out.println("Registered dim "+dimensionID+" on the client."); // No need to raise events heres since this code should only run on the client side // getDimensionData() always handles root dimensions properly, even if the weren't defined before @@ -560,7 +570,7 @@ public class PocketManager //Steven DimensionManager.registerDimension(dimensionID, mod_pocketDim.properties.PocketProviderID); } - return dimension; + return dimension; } public static NewDimData getDimensionData(World world) @@ -579,6 +589,7 @@ public class PocketManager if(PocketManager.dimensionData == null) { System.out.println("Something odd happend during shutdown"); + return null; } NewDimData dimension = PocketManager.dimensionData.get(dimensionID); if (dimension == null) diff --git a/src/main/java/StevenDimDoors/mod_pocketDim/dungeon/DungeonSchematic.java b/src/main/java/StevenDimDoors/mod_pocketDim/dungeon/DungeonSchematic.java index c3eec44e..621fc48c 100644 --- a/src/main/java/StevenDimDoors/mod_pocketDim/dungeon/DungeonSchematic.java +++ b/src/main/java/StevenDimDoors/mod_pocketDim/dungeon/DungeonSchematic.java @@ -17,10 +17,13 @@ import net.minecraft.tileentity.TileEntity; import net.minecraft.world.World; import StevenDimDoors.mod_pocketDim.DDProperties; import StevenDimDoors.mod_pocketDim.Point3D; +import StevenDimDoors.mod_pocketDim.mod_pocketDim; +import StevenDimDoors.mod_pocketDim.blocks.IDimDoor; import StevenDimDoors.mod_pocketDim.core.DimLink; import StevenDimDoors.mod_pocketDim.core.LinkTypes; import StevenDimDoors.mod_pocketDim.core.NewDimData; import StevenDimDoors.mod_pocketDim.core.PocketManager; +import StevenDimDoors.mod_pocketDim.items.ItemDimensionalDoor; import StevenDimDoors.mod_pocketDim.schematic.BlockRotator; import StevenDimDoors.mod_pocketDim.schematic.CompoundFilter; import StevenDimDoors.mod_pocketDim.schematic.InvalidSchematicException; @@ -293,6 +296,8 @@ public class DungeonSchematic extends Schematic { Point4D destination = entryLink.source(); NewDimData prevDim = PocketManager.getDimensionData(destination.getDimension()); prevDim.setDestination(reverseLink, destination.getX(), destination.getY(), destination.getZ()); + initDoorTileEntity(world, pocketCenter); + } private static void createExitDoorLink(World world, NewDimData dimension, Point3D point, Point3D entrance, int rotation, Point3D pocketCenter) @@ -313,6 +318,8 @@ public class DungeonSchematic extends Schematic { int metadata = world.getBlockMetadata(x, y, z); setBlockDirectly(world, x, y + 1, z, blockID, metadata); } + initDoorTileEntity(world, location); + } private static void createDimensionalDoorLink(NewDimData dimension, Point3D point, Point3D entrance, int rotation, Point3D pocketCenter,World world) @@ -323,6 +330,9 @@ public class DungeonSchematic extends Schematic { int orientation = world.getBlockMetadata(location.getX(), location.getY()-1, location.getZ()); dimension.createLink(location.getX(), location.getY(), location.getZ(), LinkTypes.DUNGEON, orientation); + initDoorTileEntity(world, location); + + } private static void spawnMonolith(World world, Point3D point, Point3D entrance, int rotation, Point3D pocketCenter, boolean canSpawn) @@ -340,4 +350,21 @@ public class DungeonSchematic extends Schematic { world.spawnEntityInWorld(mob); } } + private static void initDoorTileEntity(World world, Point3D point) + { + Block door = Block.blocksList[world.getBlockId(point.getX(), point.getY(), point.getZ())]; + Block door2 = Block.blocksList[world.getBlockId(point.getX(), point.getY()-1, point.getZ())]; + + if(door instanceof IDimDoor&&door2 instanceof IDimDoor) + { + ((IDimDoor) door).initDoorTE(world, point.getX(), point.getY(), point.getZ()); + ((IDimDoor) door).initDoorTE(world, point.getX(), point.getY()-1, point.getZ()); + + } + else + { + throw new IllegalArgumentException("Tried to init a dim door TE on a block that isnt a Dim Door!!"); + } + + } } diff --git a/src/main/java/StevenDimDoors/mod_pocketDim/tileentities/TileEntityDimDoor.java b/src/main/java/StevenDimDoors/mod_pocketDim/tileentities/TileEntityDimDoor.java index dcf8b0ad..12fcd654 100644 --- a/src/main/java/StevenDimDoors/mod_pocketDim/tileentities/TileEntityDimDoor.java +++ b/src/main/java/StevenDimDoors/mod_pocketDim/tileentities/TileEntityDimDoor.java @@ -19,15 +19,14 @@ public class TileEntityDimDoor extends TileEntity @Override public boolean canUpdate() { - return false; + return true; } @Override public void updateEntity() { - } - + @Override public Packet getDescriptionPacket() { if(PocketManager.getLink(xCoord, yCoord, zCoord, worldObj)!=null) diff --git a/src/main/java/StevenDimDoors/mod_pocketDim/world/PocketBuilder.java b/src/main/java/StevenDimDoors/mod_pocketDim/world/PocketBuilder.java index 061f2921..3470cd17 100644 --- a/src/main/java/StevenDimDoors/mod_pocketDim/world/PocketBuilder.java +++ b/src/main/java/StevenDimDoors/mod_pocketDim/world/PocketBuilder.java @@ -472,7 +472,7 @@ public class PocketBuilder Point3D door = new Point3D(x, y, z); BlockRotator.transformPoint(center, door, orientation - BlockRotator.EAST_DOOR_METADATA, door); - /* + //Build the outer layer of Eternal Fabric buildBox(world, center.getX(), center.getY(), center.getZ(), (size / 2), properties.PermaFabricBlockID, false, 0); @@ -482,9 +482,9 @@ public class PocketBuilder buildBox(world, center.getX(), center.getY(), center.getZ(), (size / 2) - layer, properties.FabricBlockID, layer < (wallThickness - 1) && properties.TNFREAKINGT_Enabled, properties.NonTntWeight); } - */ - MazeBuilder.generate(world, x, y, z, random); + + //MazeBuilder.generate(world, x, y, z, random); //Build the door int doorOrientation = BlockRotator.transformMetadata(BlockRotator.EAST_DOOR_METADATA, orientation - BlockRotator.EAST_DOOR_METADATA + 2, properties.DimensionalDoorID);