diff --git a/src/main/java/StevenDimDoors/experimental/DisjointSet.java b/src/main/java/StevenDimDoors/experimental/DisjointSet.java new file mode 100644 index 0000000..4b4150f --- /dev/null +++ b/src/main/java/StevenDimDoors/experimental/DisjointSet.java @@ -0,0 +1,134 @@ +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); + } + } + + public void clear() + { + mapping.clear(); + } +} 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/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; } diff --git a/src/main/java/StevenDimDoors/experimental/MazeBuilder.java b/src/main/java/StevenDimDoors/experimental/MazeBuilder.java new file mode 100644 index 0000000..aa469a4 --- /dev/null +++ b/src/main/java/StevenDimDoors/experimental/MazeBuilder.java @@ -0,0 +1,232 @@ +package StevenDimDoors.experimental; + +import java.util.Random; + +import net.minecraft.block.Block; +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); + 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, 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()) + { + PartitionNode room = node.data(); + buildBox(world, offset, room.minCorner(), room.maxCorner(), Block.stoneBrick.blockID, 0); + } + } + + private static void carveDoorways(DirectedGraph roomGraph, World world, + Point3D offset, SphereDecayOperation decay, Random random) + { + char axis; + Point3D lower; + DoorwayData doorway; + + for (IGraphNode node : roomGraph.nodes()) + { + for (IEdge passage : node.outbound()) + { + doorway = passage.data(); + axis = doorway.axis(); + lower = doorway.minCorner(); + 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); + 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(); + 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 67% rename from src/main/java/StevenDimDoors/experimental/MazeGenerator.java rename to src/main/java/StevenDimDoors/experimental/MazeDesigner.java index 4b5f076..289931a 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 = 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; 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 and some from the walls + for (IGraphNode core : cores) + { + pruneDoorways(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()) @@ -58,19 +60,8 @@ 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) + private static void removeRoomPartitions(PartitionNode node) { // Remove a node and any of its ancestors that become leaf nodes PartitionNode parent; @@ -243,11 +234,11 @@ public class MazeGenerator 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 @@ -291,11 +282,11 @@ public class MazeGenerator 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 @@ -339,11 +330,11 @@ public class MazeGenerator 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 @@ -375,35 +366,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 +398,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,128 +423,157 @@ 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 + // 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 pruneDoorways(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. 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. The same + // idea applies for the other doorways, plus some randomness. + + // 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); + rooms.removeEdge(passage); } } - for (x = minX; x <= maxX; x++) + + // 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) { - for (y = minY; y <= maxY; y++) + components.makeSet(room); + } + for (IGraphNode room : subgraph) + { + for (IEdge passage : room.outbound()) { - setBlockDirectly(world, x, y, minZ, blockID, metadata); - setBlockDirectly(world, x, y, maxZ, blockID, metadata); + if (passage.data().axis() == DoorwayData.Y_AXIS) + { + components.mergeSets(passage.head(), passage.tail()); + } + else + { + targets.add(passage); + } } } - for (z = minZ; z <= maxZ; z++) + Collections.shuffle(targets, random); + for (IEdge passage : targets) { - for (y = minY; y <= maxY; y++) + if (!components.mergeSets(passage.head(), passage.tail()) && random.nextBoolean()) { - 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/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; + } +} 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);