From 22ab4e363943df4d85f95fa24832196ef5b8d5f2 Mon Sep 17 00:00:00 2001 From: SenseiKiwi Date: Sun, 29 Dec 2013 03:11:02 -0400 Subject: [PATCH 01/10] 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 4b5f076..0911c9a 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 02/10] 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 0000000..fc15269 --- /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 0000000..dd4142c --- /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 0000000..d71b956 --- /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 0911c9a..d74db28 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 f807bde..061f292 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 03/10] 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 dd4142c..5a14f6d 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 04/10] 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 d74db28..6bc128e 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 05/10] 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 8d6b975..98ee0fa 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 06/10] 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 6bc128e..09c6df4 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 07/10] 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 ec1c5cc..5d596c2 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 5a14f6d..e4ea77a 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 08/10] 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 fc15269..4b4150f 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 09c6df4..6fb89b9 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 09/10] 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 6fb89b9..289931a 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 10/10] 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 e4ea77a..aa469a4 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 0000000..95587b6 --- /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; + } +}