From d5e5e12cf920ddc9c742a57ddeea8dfbd38ee5e4 Mon Sep 17 00:00:00 2001 From: SenseiKiwi Date: Mon, 7 Apr 2014 09:25:20 -0400 Subject: [PATCH 01/11] Refactored Maze Generation to use RoomData Rewrote portions of our maze generation code to use RoomData. This provides an object that unifies all room data instead of having it spread across various data structures and linked loosely by hash maps. We'll need this to implement the remaining generation features. --- .../experimental/MazeBuilder.java | 17 +- .../experimental/MazeDesign.java | 24 +-- .../experimental/MazeDesigner.java | 202 +++++++++--------- .../experimental/MazeLinkData.java | 6 + .../experimental/PartitionNode.java | 13 +- .../StevenDimDoors/experimental/RoomData.java | 75 +++++++ 6 files changed, 206 insertions(+), 131 deletions(-) create mode 100644 src/main/java/StevenDimDoors/experimental/MazeLinkData.java create mode 100644 src/main/java/StevenDimDoors/experimental/RoomData.java diff --git a/src/main/java/StevenDimDoors/experimental/MazeBuilder.java b/src/main/java/StevenDimDoors/experimental/MazeBuilder.java index 9e31e9a..00348c1 100644 --- a/src/main/java/StevenDimDoors/experimental/MazeBuilder.java +++ b/src/main/java/StevenDimDoors/experimental/MazeBuilder.java @@ -18,8 +18,8 @@ public class MazeBuilder 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); + buildRooms(design.getLayout(), world, offset); + carveDoorways(design.getLayout(), world, offset, decay, random); //placeDoors(design, world, offset); @@ -32,25 +32,25 @@ public class MazeBuilder //final int DECAY_BOX_SIZE = 8 } - private static void buildRooms(DirectedGraph roomGraph, World world, Point3D offset) + private static void buildRooms(DirectedGraph layout, World world, Point3D offset) { - for (IGraphNode node : roomGraph.nodes()) + for (IGraphNode node : layout.nodes()) { - PartitionNode room = node.data(); + PartitionNode room = node.data().getPartitionNode(); buildBox(world, offset, room.minCorner(), room.maxCorner(), Block.stoneBrick.blockID, 0); } } - private static void carveDoorways(DirectedGraph roomGraph, World world, + private static void carveDoorways(DirectedGraph layout, World world, Point3D offset, SphereDecayOperation decay, Random random) { char axis; Point3D lower; DoorwayData doorway; - for (IGraphNode node : roomGraph.nodes()) + for (IGraphNode node : layout.nodes()) { - for (IEdge passage : node.outbound()) + for (IEdge passage : node.outbound()) { doorway = passage.data(); axis = doorway.axis(); @@ -165,7 +165,6 @@ 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/MazeDesign.java b/src/main/java/StevenDimDoors/experimental/MazeDesign.java index e7d4b4c..c88f1cd 100644 --- a/src/main/java/StevenDimDoors/experimental/MazeDesign.java +++ b/src/main/java/StevenDimDoors/experimental/MazeDesign.java @@ -5,16 +5,12 @@ import java.util.ArrayList; public class MazeDesign { private PartitionNode root; - private DirectedGraph rooms; - private ArrayList> cores; - private ArrayList protectedAreas; + private DirectedGraph layout; - public MazeDesign(PartitionNode root, DirectedGraph rooms, - ArrayList> cores) + public MazeDesign(PartitionNode root, DirectedGraph layout) { this.root = root; - this.rooms = rooms; - this.cores = cores; + this.layout = layout; } public PartitionNode getRootPartition() @@ -22,19 +18,9 @@ public class MazeDesign return root; } - public DirectedGraph getRoomGraph() + public DirectedGraph getLayout() { - return rooms; - } - - public ArrayList> getCoreNodes() - { - return cores; - } - - public ArrayList getProtectedAreas() - { - return protectedAreas; + return layout; } public int width() diff --git a/src/main/java/StevenDimDoors/experimental/MazeDesigner.java b/src/main/java/StevenDimDoors/experimental/MazeDesigner.java index 289931a..e9d7c31 100644 --- a/src/main/java/StevenDimDoors/experimental/MazeDesigner.java +++ b/src/main/java/StevenDimDoors/experimental/MazeDesigner.java @@ -25,55 +25,66 @@ public class MazeDesigner public static MazeDesign generate(Random random) { // Construct a random binary space partitioning of our maze volume - PartitionNode root = partitionRooms(MAZE_WIDTH, MAZE_HEIGHT, MAZE_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); - listRoomPartitions(root, partitions); + // Attach rooms to all the leaf nodes of the partition tree + ArrayList rooms = new ArrayList(1 << SPLIT_COUNT); + attachRooms(root, rooms); + + // 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(rooms, random); // 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); + DirectedGraph layout = createRoomGraph(root, rooms, random); // Cut out random subgraphs from the adjacency graph - ArrayList> cores = createMazeSections(rooms, random); + ArrayList cores = createMazeSections(layout, random); // Remove unnecessary passages through floors/ceilings and some from the walls - for (IGraphNode core : cores) + for (RoomData core : cores) { - pruneDoorways(core, rooms, random); + pruneDoorways(core.getLayoutNode(), layout, random); } - return new MazeDesign(root, rooms, cores); + return new MazeDesign(root, layout); } - private static void listRoomPartitions(PartitionNode node, ArrayList partitions) + private static void attachRooms(PartitionNode node, ArrayList partitions) { if (node.isLeaf()) { - partitions.add(node); + partitions.add(new RoomData(node)); } else { - listRoomPartitions(node.leftChild(), partitions); - listRoomPartitions(node.rightChild(), partitions); + attachRooms(node.leftChild(), partitions); + attachRooms(node.rightChild(), partitions); } } - private static void removeRoomPartitions(PartitionNode node) + private static void removeRoom(RoomData room, DirectedGraph layout) { - // Remove a node and any of its ancestors that become leaf nodes + // Remove the room from the partition tree and from the layout graph. + // Also remove any ancestors that become leaf nodes. PartitionNode parent; PartitionNode current; - current = node; + current = room.getPartitionNode(); while (current != null && current.isLeaf()) { parent = current.parent(); current.remove(); current = parent; } + + // Remove the room from the layout graph + layout.removeNode(room.getLayoutNode()); + + // Wipe the room's data, as a precaution. + room.clear(); } private static PartitionNode partitionRooms(int width, int height, int length, int maxLevels, Random random) @@ -140,35 +151,25 @@ public class MazeDesigner } } - private static DirectedGraph createRoomGraph(PartitionNode root, ArrayList partitions, Random random) + private static DirectedGraph createRoomGraph(PartitionNode root, ArrayList rooms, Random random) { - 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); + DirectedGraph layout = new DirectedGraph(); // 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) + for (RoomData room : rooms) { - roomsToGraph.put(partition, roomGraph.addNode(partition)); + room.addToLayout(layout); } - // Add edges for each room - for (IGraphNode node : roomGraph.nodes()) + for (IGraphNode node : layout.nodes()) { - findDoorways(node, root, roomsToGraph, roomGraph); + findDoorways(node.data(), root, layout); } - - return roomGraph; + return layout; } - private static void findDoorways(IGraphNode roomNode, PartitionNode root, - HashMap> roomsToGraph, - DirectedGraph roomGraph) + private static void findDoorways(RoomData room, PartitionNode root, + DirectedGraph layout) { // 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 @@ -186,7 +187,7 @@ public class MazeDesigner // there will always be a way to walk from any room to any other room. boolean[][] detected; - PartitionNode adjacent; + PartitionNode adjacent; int a, b, c; int p, q, r; @@ -195,11 +196,10 @@ public class MazeDesigner Point3D otherMin; Point3D otherMax; DoorwayData doorway; - IGraphNode adjacentNode; - PartitionNode room = roomNode.data(); - Point3D minCorner = room.minCorner(); - Point3D maxCorner = room.maxCorner(); + PartitionNode partition = room.getPartitionNode(); + Point3D minCorner = partition.minCorner(); + Point3D maxCorner = partition.maxCorner(); int minX = minCorner.getX(); int minY = minCorner.getY(); @@ -209,9 +209,9 @@ public class MazeDesigner int maxY = maxCorner.getY(); int maxZ = maxCorner.getZ(); - int width = room.width(); - int height = room.height(); - int length = room.length(); + int width = partition.width(); + int height = partition.height(); + int length = partition.length(); if (maxZ < root.maxCorner().getZ()) { @@ -247,8 +247,7 @@ public class MazeDesigner 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); + layout.addEdge(room.getLayoutNode(), adjacent.getData().getLayoutNode(), doorway); } } else @@ -295,8 +294,7 @@ public class MazeDesigner 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); + layout.addEdge(room.getLayoutNode(), adjacent.getData().getLayoutNode(), doorway); } } else @@ -343,8 +341,7 @@ public class MazeDesigner 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); + layout.addEdge(room.getLayoutNode(), adjacent.getData().getLayoutNode(), doorway); } } else @@ -359,7 +356,7 @@ public class MazeDesigner //Done! } - private static ArrayList> createMazeSections(DirectedGraph roomGraph, Random random) + private static ArrayList createMazeSections(DirectedGraph layout, Random random) { // The randomness of the sections generated here hinges on // the nodes in the graph being in a random order. We assume @@ -369,66 +366,68 @@ public class MazeDesigner final int MIN_SECTION_ROOMS = 5; int distance; - IGraphNode current; - IGraphNode neighbor; + RoomData room; + RoomData neighbor; + IGraphNode current; - ArrayList> cores = new ArrayList>(); - ArrayList> removals = new ArrayList>(); - ArrayList> section = new ArrayList>(); + ArrayList cores = new ArrayList(); + ArrayList removals = new ArrayList(); + ArrayList section = new ArrayList(); - Queue> ordering = new LinkedList>(); - HashMap, Integer> distances = new HashMap, Integer>(); + Queue ordering = new LinkedList(); // Repeatedly generate sections until all nodes have been visited - for (IGraphNode node : roomGraph.nodes()) + for (IGraphNode node : layout.nodes()) { - // If this node hasn't been visited, then use it as the core of a new section + // If this room hasn't been visited (distance = -1), then use it as the core of a new section // Otherwise, ignore it, since it was already processed - if (!distances.containsKey(node)) + room = node.data(); + if (room.getDistance() < 0) { // Perform a breadth-first search to tag surrounding nodes with distances - distances.put(node, 0); - ordering.add(node); + room.setDistance(0); + ordering.add(room); section.clear(); while (!ordering.isEmpty()) { - current = ordering.remove(); - distance = distances.get(current) + 1; + room = ordering.remove(); + distance = room.getDistance() + 1; if (distance <= MAX_DISTANCE + 1) { - section.add(current); + section.add(room); + current = room.getLayoutNode(); - // Visit neighboring nodes and assign them distances, if they don't - // have a distance assigned already - for (IEdge edge : current.inbound()) + // Visit neighboring rooms and assign them distances, if they don't + // have a proper distance assigned already + for (IEdge edge : current.inbound()) { - neighbor = edge.head(); - if (!distances.containsKey(neighbor)) + neighbor = edge.head().data(); + if (neighbor.getDistance() < 0) { - distances.put(neighbor, distance); + neighbor.setDistance(distance); ordering.add(neighbor); } } - for (IEdge edge : current.outbound()) + for (IEdge edge : current.outbound()) { - neighbor = edge.tail(); - if (!distances.containsKey(neighbor)) + neighbor = edge.tail().data(); + if (neighbor.getDistance() < 0) { - distances.put(neighbor, distance); + neighbor.setDistance(distance); ordering.add(neighbor); } } } else { - removals.add(current); + removals.add(room); break; } } - // List nodes that have a distance of exactly MAX_DISTANCE + 1 + // List rooms 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. @@ -440,7 +439,7 @@ public class MazeDesigner // Check if this section contains enough rooms if (section.size() >= MIN_SECTION_ROOMS) { - cores.add(node); + cores.add(node.data()); } else { @@ -449,18 +448,17 @@ public class MazeDesigner } } - // Remove all the nodes that were listed for removal + // Remove all the rooms that were listed for removal // Also remove unused partitions from the partition tree - for (IGraphNode node : removals) + for (RoomData target : removals) { - removeRoomPartitions(node.data()); - roomGraph.removeNode(node); + removeRoom(target, layout); } return cores; } - private static void pruneDoorways(IGraphNode core, - DirectedGraph rooms, Random random) + private static void pruneDoorways(IGraphNode core, + DirectedGraph layout, Random random) { // 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 @@ -478,12 +476,12 @@ public class MazeDesigner // idea applies for the other doorways, plus some randomness. // First, list all nodes in the subgraph - IGraphNode current; - IGraphNode neighbor; + IGraphNode current; + IGraphNode neighbor; - Stack> ordering = new Stack>(); - ArrayList> subgraph = new ArrayList>(64); - DisjointSet> components = new DisjointSet>(128); + Stack> ordering = new Stack>(); + ArrayList> subgraph = new ArrayList>(64); + DisjointSet> components = new DisjointSet>(128); ordering.add(core); components.makeSet(core); @@ -492,7 +490,7 @@ public class MazeDesigner current = ordering.pop(); subgraph.add(current); - for (IEdge edge : current.inbound()) + for (IEdge edge : current.inbound()) { neighbor = edge.head(); if (components.makeSet(neighbor)) @@ -500,7 +498,7 @@ public class MazeDesigner ordering.add(neighbor); } } - for (IEdge edge : current.outbound()) + for (IEdge edge : current.outbound()) { neighbor = edge.tail(); if (components.makeSet(neighbor)) @@ -513,12 +511,12 @@ public class MazeDesigner // 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>(); + ArrayList> targets = + new ArrayList>(); - for (IGraphNode room : subgraph) + for (IGraphNode room : subgraph) { - for (IEdge passage : room.outbound()) + for (IEdge passage : room.outbound()) { if (passage.data().axis() != DoorwayData.Y_AXIS) { @@ -535,11 +533,11 @@ public class MazeDesigner Collections.shuffle(targets, random); // Merge sets together and remove unnecessary doorways - for (IEdge passage : targets) + for (IEdge passage : targets) { if (!components.mergeSets(passage.head(), passage.tail())) { - rooms.removeEdge(passage); + layout.removeEdge(passage); } } @@ -548,13 +546,13 @@ public class MazeDesigner components.clear(); targets.clear(); - for (IGraphNode room : subgraph) + for (IGraphNode room : subgraph) { components.makeSet(room); } - for (IGraphNode room : subgraph) + for (IGraphNode room : subgraph) { - for (IEdge passage : room.outbound()) + for (IEdge passage : room.outbound()) { if (passage.data().axis() == DoorwayData.Y_AXIS) { @@ -567,11 +565,11 @@ public class MazeDesigner } } Collections.shuffle(targets, random); - for (IEdge passage : targets) + for (IEdge passage : targets) { if (!components.mergeSets(passage.head(), passage.tail()) && random.nextBoolean()) { - rooms.removeEdge(passage); + layout.removeEdge(passage); } } } diff --git a/src/main/java/StevenDimDoors/experimental/MazeLinkData.java b/src/main/java/StevenDimDoors/experimental/MazeLinkData.java new file mode 100644 index 0000000..efff141 --- /dev/null +++ b/src/main/java/StevenDimDoors/experimental/MazeLinkData.java @@ -0,0 +1,6 @@ +package StevenDimDoors.experimental; + +public class MazeLinkData +{ + +} diff --git a/src/main/java/StevenDimDoors/experimental/PartitionNode.java b/src/main/java/StevenDimDoors/experimental/PartitionNode.java index df53854..5226378 100644 --- a/src/main/java/StevenDimDoors/experimental/PartitionNode.java +++ b/src/main/java/StevenDimDoors/experimental/PartitionNode.java @@ -2,11 +2,12 @@ package StevenDimDoors.experimental; import StevenDimDoors.mod_pocketDim.Point3D; -public class PartitionNode extends BoundingBox +public class PartitionNode extends BoundingBox { private PartitionNode parent; private PartitionNode leftChild = null; private PartitionNode rightChild = null; + private T data = null; public PartitionNode(int width, int height, int length) { @@ -122,4 +123,14 @@ public class PartitionNode extends BoundingBox return this; } } + + public void setData(T value) + { + this.data = value; + } + + public T getData() + { + return data; + } } diff --git a/src/main/java/StevenDimDoors/experimental/RoomData.java b/src/main/java/StevenDimDoors/experimental/RoomData.java new file mode 100644 index 0000000..eacfcaf --- /dev/null +++ b/src/main/java/StevenDimDoors/experimental/RoomData.java @@ -0,0 +1,75 @@ +package StevenDimDoors.experimental; + +import java.util.ArrayList; + +public class RoomData +{ + private int distance; + private boolean decayed; + private PartitionNode partitionNode; + private ArrayList inboundLinks; + private ArrayList outboundLinks; + private IGraphNode layoutNode; + + public RoomData(PartitionNode partitionNode) + { + this.partitionNode = partitionNode; + this.inboundLinks = new ArrayList(); + this.outboundLinks = new ArrayList(); + this.layoutNode = null; + this.distance = -1; + this.decayed = false; + partitionNode.setData(this); + } + + public PartitionNode getPartitionNode() + { + return this.partitionNode; + } + + public IGraphNode getLayoutNode() + { + return this.layoutNode; + } + + public void addToLayout(DirectedGraph layout) + { + this.layoutNode = layout.addNode(this); + } + + public boolean isDecayed() + { + return decayed; + } + + public void setDecayed(boolean value) + { + this.decayed = value; + } + + public ArrayList getInboundLinks() + { + return this.inboundLinks; + } + + public ArrayList getOutboundLinks() + { + return this.outboundLinks; + } + + public int getDistance() + { + return distance; + } + + public void setDistance(int value) + { + distance = value; + } + + public void clear() + { + partitionNode = null; + layoutNode = null; + } +} From 5210de2e71740aafdd7c52e2ad69e2f588f16cc7 Mon Sep 17 00:00:00 2001 From: SenseiKiwi Date: Mon, 7 Apr 2014 09:39:10 -0400 Subject: [PATCH 02/11] Hacked PocketBuilder to Generate Mazes Made a minor change to PocketBuilder so that mazes generate instead of regular pocket dimensions. I'm only doing this to test dungeon generation - it'll get switched back once mazes are ready. --- .../StevenDimDoors/mod_pocketDim/world/PocketBuilder.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/StevenDimDoors/mod_pocketDim/world/PocketBuilder.java b/src/main/java/StevenDimDoors/mod_pocketDim/world/PocketBuilder.java index f26939b..913c282 100644 --- a/src/main/java/StevenDimDoors/mod_pocketDim/world/PocketBuilder.java +++ b/src/main/java/StevenDimDoors/mod_pocketDim/world/PocketBuilder.java @@ -465,6 +465,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); @@ -474,8 +475,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); From 935070e4367a2f68eb5c37cf11856002ff4c0990 Mon Sep 17 00:00:00 2001 From: SenseiKiwi Date: Tue, 8 Apr 2014 06:34:45 -0400 Subject: [PATCH 03/11] Minor Change Minor correction to a comment in DirectedGraph --- src/main/java/StevenDimDoors/experimental/DirectedGraph.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/StevenDimDoors/experimental/DirectedGraph.java b/src/main/java/StevenDimDoors/experimental/DirectedGraph.java index cd56c75..df2aa6c 100644 --- a/src/main/java/StevenDimDoors/experimental/DirectedGraph.java +++ b/src/main/java/StevenDimDoors/experimental/DirectedGraph.java @@ -165,8 +165,8 @@ public class DirectedGraph { Edge innerEdge = (Edge) edge; - // Check that this node actually belongs to this graph instance. - // Accepting foreign nodes could corrupt the graph's internal state. + // Check that this edge actually belongs to this graph instance. + // Accepting foreign edges could corrupt the graph's internal state. if (innerEdge.graphEntry.owner() != edges) { throw new IllegalArgumentException("The specified edge does not belong to this graph."); From f92020323f4685c04db4bdc915c23bc3c1a3ff63 Mon Sep 17 00:00:00 2001 From: SenseiKiwi Date: Tue, 8 Apr 2014 06:40:45 -0400 Subject: [PATCH 04/11] Partial Implementation of Doors in Mazes Started implementing the placement of Dimensional Doors in mazes. Currently, a design is guaranteed to have enough space for some doors. MazeDesigner still needs more code to plan out which rooms will have doors and where those doors will lead. --- .../experimental/MazeDesigner.java | 99 +++++++++++-------- .../StevenDimDoors/experimental/RoomData.java | 7 ++ 2 files changed, 66 insertions(+), 40 deletions(-) diff --git a/src/main/java/StevenDimDoors/experimental/MazeDesigner.java b/src/main/java/StevenDimDoors/experimental/MazeDesigner.java index e9d7c31..ff01d9f 100644 --- a/src/main/java/StevenDimDoors/experimental/MazeDesigner.java +++ b/src/main/java/StevenDimDoors/experimental/MazeDesigner.java @@ -2,7 +2,6 @@ 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; @@ -362,13 +361,23 @@ public class MazeDesigner // the nodes in the graph being in a random order. We assume // that was handled in a previous step! + // We split the maze into sections by choosing core rooms and removing + // rooms that are a certain number of doorways away. However, for a section + // to be valid, it must also have enough space for at least two doors in + // rooms without floor holes. If a section can't fit two doors, more + // neighboring rooms are added until the necessary space is found or the + // search space is exhausted. + final int MAX_DISTANCE = 2; final int MIN_SECTION_ROOMS = 5; + final int MIN_SECTION_CAPACITY = 2; int distance; + int capacity; RoomData room; RoomData neighbor; - IGraphNode current; + boolean hasHoles; + IGraphNode roomNode; ArrayList cores = new ArrayList(); ArrayList removals = new ArrayList(); @@ -385,59 +394,67 @@ public class MazeDesigner if (room.getDistance() < 0) { // Perform a breadth-first search to tag surrounding nodes with distances - room.setDistance(0); ordering.add(room); + room.setDistance(0); section.clear(); + capacity = 0; - while (!ordering.isEmpty()) + while (room != null && (room.getDistance() <= MAX_DISTANCE || capacity < 2)) { - room = ordering.remove(); + ordering.remove(); + section.add(room); + roomNode = room.getLayoutNode(); distance = room.getDistance() + 1; + hasHoles = false; - if (distance <= MAX_DISTANCE + 1) + // Visit neighboring rooms and assign them distances, + // if they don't have a proper distance assigned already. + // Also check for floor holes. + for (IEdge edge : roomNode.inbound()) { - section.add(room); - current = room.getLayoutNode(); - - // Visit neighboring rooms and assign them distances, if they don't - // have a proper distance assigned already - for (IEdge edge : current.inbound()) + neighbor = edge.head().data(); + if (neighbor.getDistance() < 0) { - neighbor = edge.head().data(); - if (neighbor.getDistance() < 0) - { - neighbor.setDistance(distance); - ordering.add(neighbor); - } + neighbor.setDistance(distance); + ordering.add(neighbor); } - for (IEdge edge : current.outbound()) + if (edge.data().axis() == DoorwayData.Y_AXIS) { - neighbor = edge.tail().data(); - if (neighbor.getDistance() < 0) - { - neighbor.setDistance(distance); - ordering.add(neighbor); - } + hasHoles = true; } } - else + for (IEdge edge : roomNode.outbound()) { - removals.add(room); - break; + neighbor = edge.tail().data(); + if (neighbor.getDistance() < 0) + { + neighbor.setDistance(distance); + ordering.add(neighbor); + } } + + // Count this room's door capacity if it has no floor holes + if (!hasHoles) + { + capacity += room.estimateDoorCapacity(); + } + + room = ordering.peek(); } - // List rooms 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()) + // The remaining rooms in the ordering are those that are at the + // frontier of structure. They must be removed to create a gap + // between this section and other sections. But we can't remove + // the rooms immediately because that could break the iterator + // for the graph. + if (!ordering.isEmpty()) { - removals.add(ordering.remove()); + removals.addAll(ordering); + ordering.clear(); } - // Check if this section contains enough rooms - if (section.size() >= MIN_SECTION_ROOMS) + // Check if this section contains enough rooms and capacity for doors + if (section.size() >= MIN_SECTION_ROOMS && capacity >= MIN_SECTION_CAPACITY) { cores.add(node.data()); } @@ -449,7 +466,6 @@ public class MazeDesigner } // Remove all the rooms that were listed for removal - // Also remove unused partitions from the partition tree for (RoomData target : removals) { removeRoom(target, layout); @@ -508,9 +524,12 @@ public class MazeDesigner } } - // 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 + // Now iterate over the list of nodes and merge their sets based on + // being connected by X_AXIS or Z_AXIS doorways. We only have to look + // at outbound edges since inbound edges mirror them. List any Y_AXIS + // doorways we come across to consider removing them later, depending + // on their impact on connectedness. + // doorways. ArrayList> targets = new ArrayList>(); diff --git a/src/main/java/StevenDimDoors/experimental/RoomData.java b/src/main/java/StevenDimDoors/experimental/RoomData.java index eacfcaf..0334d33 100644 --- a/src/main/java/StevenDimDoors/experimental/RoomData.java +++ b/src/main/java/StevenDimDoors/experimental/RoomData.java @@ -72,4 +72,11 @@ public class RoomData partitionNode = null; layoutNode = null; } + + public int estimateDoorCapacity() + { + int cellsX = (partitionNode.width() - 3) / 2; + int cellsZ = (partitionNode.length() - 3) / 2; + return Math.min(cellsX * cellsZ, 3); + } } From 53b5591149f1883291c57f08a221d75493430da4 Mon Sep 17 00:00:00 2001 From: SenseiKiwi Date: Fri, 11 Apr 2014 19:28:05 -0400 Subject: [PATCH 05/11] Partial Implementation of Doors in Mazes Made progress on implementing the placement of doors in mazes. Still incomplete. --- .../StevenDimDoors/experimental/LinkPlan.java | 87 ++++++++++++ .../experimental/MazeBuilder.java | 20 ++- .../experimental/MazeDesigner.java | 128 ++++++++++++++---- .../experimental/MazeLinkData.java | 6 - .../StevenDimDoors/experimental/RoomData.java | 69 ++++++++-- 5 files changed, 270 insertions(+), 40 deletions(-) create mode 100644 src/main/java/StevenDimDoors/experimental/LinkPlan.java delete mode 100644 src/main/java/StevenDimDoors/experimental/MazeLinkData.java diff --git a/src/main/java/StevenDimDoors/experimental/LinkPlan.java b/src/main/java/StevenDimDoors/experimental/LinkPlan.java new file mode 100644 index 0000000..3fe6f75 --- /dev/null +++ b/src/main/java/StevenDimDoors/experimental/LinkPlan.java @@ -0,0 +1,87 @@ +package StevenDimDoors.experimental; + +public class LinkPlan +{ + private RoomData source; + private RoomData destination; + private boolean entrance; + + private LinkPlan(RoomData source, RoomData destination, boolean entrance) + { + this.source = source; + this.destination = destination; + this.entrance = entrance; + } + + public static LinkPlan createInternalLink(RoomData source, RoomData destination) + { + if (source == null) + { + throw new IllegalArgumentException("source cannot be null."); + } + if (destination == null) + { + throw new IllegalArgumentException("destination cannot be null."); + } + LinkPlan plan = new LinkPlan(source, destination, false); + source.getOutboundLinks().add(plan); + destination.getInboundLinks().add(plan); + return plan; + } + + public static LinkPlan createEntranceLink(RoomData source) + { + if (source == null) + { + throw new IllegalArgumentException("source cannot be null."); + } + LinkPlan plan = new LinkPlan(source, null, true); + source.getOutboundLinks().add(plan); + return plan; + } + + public static LinkPlan createDungeonLink(RoomData source) + { + if (source == null) + { + throw new IllegalArgumentException("source cannot be null."); + } + LinkPlan plan = new LinkPlan(source, null, false); + source.getOutboundLinks().add(plan); + return plan; + } + + public RoomData source() + { + return this.source; + } + + public RoomData destination() + { + return this.destination; + } + + public boolean isEntrance() + { + return entrance; + } + + public boolean isInternal() + { + return (destination != null); + } + + public void remove() + { + if (source != null) + { + source.getOutboundLinks().remove(this); + source = null; + } + if (destination != null) + { + destination.getInboundLinks().remove(this); + destination = null; + } + } +} diff --git a/src/main/java/StevenDimDoors/experimental/MazeBuilder.java b/src/main/java/StevenDimDoors/experimental/MazeBuilder.java index 00348c1..6505f99 100644 --- a/src/main/java/StevenDimDoors/experimental/MazeBuilder.java +++ b/src/main/java/StevenDimDoors/experimental/MazeBuilder.java @@ -14,6 +14,8 @@ public class MazeBuilder public static void generate(World world, int x, int y, int z, Random random) { + // ISSUE FOR LATER: The room needs to be shifted so as to be centered on its entrance + 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); @@ -21,7 +23,7 @@ public class MazeBuilder buildRooms(design.getLayout(), world, offset); carveDoorways(design.getLayout(), world, offset, decay, random); - //placeDoors(design, world, offset); + placeDoors(design.getLayout(), world, offset); applyRandomDestruction(design, world, offset, decay, random); } @@ -41,6 +43,22 @@ public class MazeBuilder } } + private static void placeDoors(DirectedGraph layout, World world, Point3D offset) + { + for (IGraphNode node : layout.nodes()) + { + RoomData room = node.data(); + Point3D minCorner = room.getPartitionNode().minCorner(); + if (!room.getOutboundLinks().isEmpty()) + { + setBlockDirectly(world, offset.getX() + minCorner.getX(), offset.getY() + minCorner.getY() + 1, + offset.getZ() + minCorner.getZ(), Block.glowStone.blockID, 0); + setBlockDirectly(world, offset.getX() + minCorner.getX(), offset.getY() + minCorner.getY() + 2, + offset.getZ() + minCorner.getZ(), Block.glowStone.blockID, 0); + } + } + } + private static void carveDoorways(DirectedGraph layout, World world, Point3D offset, SphereDecayOperation decay, Random random) { diff --git a/src/main/java/StevenDimDoors/experimental/MazeDesigner.java b/src/main/java/StevenDimDoors/experimental/MazeDesigner.java index ff01d9f..0026303 100644 --- a/src/main/java/StevenDimDoors/experimental/MazeDesigner.java +++ b/src/main/java/StevenDimDoors/experimental/MazeDesigner.java @@ -1,7 +1,9 @@ package StevenDimDoors.experimental; +import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.LinkedList; import java.util.Queue; import java.util.Random; @@ -48,6 +50,9 @@ public class MazeDesigner pruneDoorways(core.getLayoutNode(), layout, random); } + // Set up the placement of dimensional doors within the maze + createMazeLinks(layout, cores, random); + return new MazeDesign(root, layout); } @@ -64,28 +69,6 @@ public class MazeDesigner } } - private static void removeRoom(RoomData room, DirectedGraph layout) - { - // Remove the room from the partition tree and from the layout graph. - // Also remove any ancestors that become leaf nodes. - PartitionNode parent; - PartitionNode current; - - current = room.getPartitionNode(); - while (current != null && current.isLeaf()) - { - parent = current.parent(); - current.remove(); - current = parent; - } - - // Remove the room from the layout graph - layout.removeNode(room.getLayoutNode()); - - // Wipe the room's data, as a precaution. - room.clear(); - } - private static PartitionNode partitionRooms(int width, int height, int length, int maxLevels, Random random) { PartitionNode root = new PartitionNode(width, height, length); @@ -468,7 +451,7 @@ public class MazeDesigner // Remove all the rooms that were listed for removal for (RoomData target : removals) { - removeRoom(target, layout); + target.remove(); } return cores; } @@ -529,7 +512,6 @@ public class MazeDesigner // at outbound edges since inbound edges mirror them. List any Y_AXIS // doorways we come across to consider removing them later, depending // on their impact on connectedness. - // doorways. ArrayList> targets = new ArrayList>(); @@ -557,6 +539,7 @@ public class MazeDesigner if (!components.mergeSets(passage.head(), passage.tail())) { layout.removeEdge(passage); + } } @@ -593,4 +576,101 @@ public class MazeDesigner } } + private static void createMazeLinks(DirectedGraph layout, + ArrayList cores, Random random) + { + // We have 4 objectives here... + // 1. Place the entrance to the maze + // 2. Place internal links connecting the different sections of the maze + // 3. Place links to other dungeons + // 4. Place more internal links to confuse people + + // We need to start by counting the door capacity of each section and + // listing which rooms can have doors or destinations for each section. + int index; + int[] capacity = new int[cores.size()]; + ArrayList[] sourceRooms = (ArrayList[]) Array.newInstance(cores.getClass(), cores.size()); + ArrayList[] destinationRooms = (ArrayList[]) Array.newInstance(cores.getClass(), cores.size()); + + for (index = 0; index < sourceRooms.length; index++) + { + sourceRooms[index] = new ArrayList(); + destinationRooms[index] = new ArrayList(); + capacity[index] = listLinkRooms(cores.get(index).getLayoutNode(), sourceRooms[index], destinationRooms[index]); + } + + // Now we select the room in which to place the entrance. + // We can safely assume all source room lists are non-empty because + // createMazeSections() guarantees that each section has at least + // the capacity for 2 doors. + index = random.nextInt(sourceRooms.length); + createEntranceLink(sourceRooms[index], random.nextInt(sourceRooms[index].size())); + + // The next task is to place internal links. These links must connect + // the different maze sections to create a strongly connected graph. + + } + + private static int listLinkRooms(IGraphNode core, + ArrayList sourceRooms, ArrayList destinationRooms) + { + int capacity = 0; + boolean hasHoles; + RoomData currentRoom; + IGraphNode current; + IGraphNode neighbor; + Stack> ordering = new Stack>(); + HashSet> visited = new HashSet>(); + + visited.add(core); + ordering.add(core); + while (!ordering.isEmpty()) + { + current = ordering.pop(); + hasHoles = false; + + for (IEdge edge : current.outbound()) + { + neighbor = edge.tail(); + if (visited.add(neighbor)) + { + ordering.add(neighbor); + } + } + for (IEdge edge : current.inbound()) + { + neighbor = edge.head(); + if (visited.add(neighbor)) + { + ordering.add(neighbor); + } + if (edge.data().axis() == DoorwayData.Y_AXIS) + { + hasHoles = true; + } + } + + if (!hasHoles) + { + currentRoom = current.data(); + destinationRooms.add(currentRoom); + if (currentRoom.estimateDoorCapacity() > 0) + { + capacity += currentRoom.estimateDoorCapacity(); + sourceRooms.add(currentRoom); + } + } + } + return capacity; + } + + private static void createEntranceLink(ArrayList sources, int index) + { + RoomData entranceRoom = sources.get(index); + LinkPlan.createEntranceLink(entranceRoom); + if (entranceRoom.getRemainingDoorCapacity() == 0) + { + sources.remove(index); + } + } } diff --git a/src/main/java/StevenDimDoors/experimental/MazeLinkData.java b/src/main/java/StevenDimDoors/experimental/MazeLinkData.java deleted file mode 100644 index efff141..0000000 --- a/src/main/java/StevenDimDoors/experimental/MazeLinkData.java +++ /dev/null @@ -1,6 +0,0 @@ -package StevenDimDoors.experimental; - -public class MazeLinkData -{ - -} diff --git a/src/main/java/StevenDimDoors/experimental/RoomData.java b/src/main/java/StevenDimDoors/experimental/RoomData.java index 0334d33..c87c406 100644 --- a/src/main/java/StevenDimDoors/experimental/RoomData.java +++ b/src/main/java/StevenDimDoors/experimental/RoomData.java @@ -4,20 +4,32 @@ import java.util.ArrayList; public class RoomData { + /* Implementation Note: + * Plans for links between rooms are stored in lists rather than a graph, + * even though they duplicate some graph functionality, because there are + * relatively few of them compared to the number of rooms. Moreover, some + * links don't even have destinations because they're intended to lead to + * other dungeons. + */ + + private int capacity; private int distance; private boolean decayed; private PartitionNode partitionNode; - private ArrayList inboundLinks; - private ArrayList outboundLinks; + private ArrayList inboundLinks; + private ArrayList outboundLinks; + private DirectedGraph layout; private IGraphNode layoutNode; public RoomData(PartitionNode partitionNode) { this.partitionNode = partitionNode; - this.inboundLinks = new ArrayList(); - this.outboundLinks = new ArrayList(); + this.inboundLinks = new ArrayList(); + this.outboundLinks = new ArrayList(); this.layoutNode = null; + this.layout = null; this.distance = -1; + this.capacity = -1; this.decayed = false; partitionNode.setData(this); } @@ -34,6 +46,7 @@ public class RoomData public void addToLayout(DirectedGraph layout) { + this.layout = layout; this.layoutNode = layout.addNode(this); } @@ -47,12 +60,12 @@ public class RoomData this.decayed = value; } - public ArrayList getInboundLinks() + public ArrayList getInboundLinks() { return this.inboundLinks; } - public ArrayList getOutboundLinks() + public ArrayList getOutboundLinks() { return this.outboundLinks; } @@ -67,16 +80,54 @@ public class RoomData distance = value; } - public void clear() + public void remove() { + // Remove the room from the partition tree and from the layout graph. + // Also remove any ancestors that become leaf nodes. + PartitionNode parent; + PartitionNode current = partitionNode; + while (current != null && current.isLeaf()) + { + parent = current.parent(); + current.remove(); + current = parent; + } + + // Remove the room from the layout graph + layout.removeNode(layoutNode); + + // Remove any links + while (!inboundLinks.isEmpty()) + inboundLinks.get(inboundLinks.size() - 1).remove(); + + while (!outboundLinks.isEmpty()) + outboundLinks.get(outboundLinks.size() - 1).remove(); + + // Wipe the room's data, as a precaution + layout = null; partitionNode = null; - layoutNode = null; + inboundLinks = null; + outboundLinks = null; } public int estimateDoorCapacity() { + if (capacity >= 0) + return capacity; + int cellsX = (partitionNode.width() - 3) / 2; int cellsZ = (partitionNode.length() - 3) / 2; - return Math.min(cellsX * cellsZ, 3); + capacity = Math.min(cellsX * cellsZ, 3); + return capacity; + } + + public int getRemainingDoorCapacity() + { + return (estimateDoorCapacity() - outboundLinks.size()); + } + + public boolean isProtected() + { + return !inboundLinks.isEmpty() || !outboundLinks.isEmpty(); } } From 906faf44eb338d05c2f883295a7dcecc8ce5c72e Mon Sep 17 00:00:00 2001 From: SenseiKiwi Date: Sun, 13 Apr 2014 16:17:42 -0400 Subject: [PATCH 06/11] Tweaked Maze Section Generation Tweaked maze section generation to use a random MAX_DISTANCE for including rooms in a section. Also changed the code to perform room removals as sections are processed rather than deferring them to the end. Deferring removals would cause the algorithm to detect holes from rooms that were going to be removed. This made section generation much stricter than necessary. --- .../experimental/MazeDesigner.java | 45 ++++++++++--------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/src/main/java/StevenDimDoors/experimental/MazeDesigner.java b/src/main/java/StevenDimDoors/experimental/MazeDesigner.java index 0026303..8707d46 100644 --- a/src/main/java/StevenDimDoors/experimental/MazeDesigner.java +++ b/src/main/java/StevenDimDoors/experimental/MazeDesigner.java @@ -351,7 +351,7 @@ public class MazeDesigner // neighboring rooms are added until the necessary space is found or the // search space is exhausted. - final int MAX_DISTANCE = 2; + final int MAX_DISTANCE = 2 + random.nextInt(2); final int MIN_SECTION_ROOMS = 5; final int MIN_SECTION_CAPACITY = 2; @@ -363,18 +363,27 @@ public class MazeDesigner IGraphNode roomNode; ArrayList cores = new ArrayList(); - ArrayList removals = new ArrayList(); ArrayList section = new ArrayList(); + ArrayList> nodes = new ArrayList>(layout.nodeCount()); Queue ordering = new LinkedList(); - // Repeatedly generate sections until all nodes have been visited + // List all graph nodes so that we can iterate over this list instead + // of using the graph's iterator. That avoids the risk of breaking + // the graph's iterator during removals. for (IGraphNode node : layout.nodes()) + { + nodes.add(node); + } + + // Repeatedly generate sections until all nodes have been visited + for (IGraphNode node : nodes) { // If this room hasn't been visited (distance = -1), then use it as the core of a new section - // Otherwise, ignore it, since it was already processed + // Otherwise, ignore it, since it was already processed. Also make sure to check that room + // isn't null, which happens if the room was removed previously. room = node.data(); - if (room.getDistance() < 0) + if (room != null && room.getDistance() < 0) { // Perform a breadth-first search to tag surrounding nodes with distances ordering.add(room); @@ -427,13 +436,10 @@ public class MazeDesigner // The remaining rooms in the ordering are those that are at the // frontier of structure. They must be removed to create a gap - // between this section and other sections. But we can't remove - // the rooms immediately because that could break the iterator - // for the graph. - if (!ordering.isEmpty()) + // between this section and other sections. + while (!ordering.isEmpty()) { - removals.addAll(ordering); - ordering.clear(); + ordering.remove().remove(); } // Check if this section contains enough rooms and capacity for doors @@ -443,16 +449,14 @@ public class MazeDesigner } else { - removals.addAll(section); + // Discard the whole section + for (RoomData target : section) + { + target.remove(); + } } } } - - // Remove all the rooms that were listed for removal - for (RoomData target : removals) - { - target.remove(); - } return cores; } @@ -581,8 +585,8 @@ public class MazeDesigner { // We have 4 objectives here... // 1. Place the entrance to the maze - // 2. Place internal links connecting the different sections of the maze - // 3. Place links to other dungeons + // 2. Place links to other dungeons + // 3. Place internal links connecting the different sections of the maze // 4. Place more internal links to confuse people // We need to start by counting the door capacity of each section and @@ -609,6 +613,7 @@ public class MazeDesigner // The next task is to place internal links. These links must connect // the different maze sections to create a strongly connected graph. + } private static int listLinkRooms(IGraphNode core, From 81b48158bd19b8b7441f3cf92e0cf59c44a72ae6 Mon Sep 17 00:00:00 2001 From: SenseiKiwi Date: Mon, 14 Apr 2014 22:24:59 -0400 Subject: [PATCH 07/11] Progress on Maze Generation * Finished implementing link planning for mazes. Doors aren't placed yet because that's up to Decorators and those haven't been implemented yet. * Added bounding walls to mazes. * Added decay effects to mazes. --- .../StevenDimDoors/experimental/LinkPlan.java | 85 +++++-- .../experimental/MazeBuilder.java | 139 +++++++++-- .../experimental/MazeDesign.java | 6 +- .../experimental/MazeDesigner.java | 235 ++++++++++++------ .../experimental/SectionData.java | 192 ++++++++++++++ .../StevenDimDoors/mod_pocketDim/Point3D.java | 7 + .../mod_pocketDim/world/PocketBuilder.java | 2 +- 7 files changed, 544 insertions(+), 122 deletions(-) create mode 100644 src/main/java/StevenDimDoors/experimental/SectionData.java diff --git a/src/main/java/StevenDimDoors/experimental/LinkPlan.java b/src/main/java/StevenDimDoors/experimental/LinkPlan.java index 3fe6f75..fbd90c1 100644 --- a/src/main/java/StevenDimDoors/experimental/LinkPlan.java +++ b/src/main/java/StevenDimDoors/experimental/LinkPlan.java @@ -1,52 +1,47 @@ package StevenDimDoors.experimental; +import StevenDimDoors.mod_pocketDim.Point3D; + public class LinkPlan { private RoomData source; private RoomData destination; - private boolean entrance; + private Point3D sourcePoint; + private Point3D destinationPoint; + private final boolean entrance; + private final boolean internal; - private LinkPlan(RoomData source, RoomData destination, boolean entrance) - { - this.source = source; - this.destination = destination; - this.entrance = entrance; - } - - public static LinkPlan createInternalLink(RoomData source, RoomData destination) + private LinkPlan(RoomData source, boolean entrance, boolean internal) { if (source == null) { throw new IllegalArgumentException("source cannot be null."); } - if (destination == null) - { - throw new IllegalArgumentException("destination cannot be null."); - } - LinkPlan plan = new LinkPlan(source, destination, false); + this.source = source; + this.destination = null; + this.sourcePoint = null; + this.destinationPoint = null; + this.entrance = entrance; + this.internal = internal; + } + + public static LinkPlan createInternalLink(RoomData source) + { + LinkPlan plan = new LinkPlan(source, false, true); source.getOutboundLinks().add(plan); - destination.getInboundLinks().add(plan); return plan; } public static LinkPlan createEntranceLink(RoomData source) { - if (source == null) - { - throw new IllegalArgumentException("source cannot be null."); - } - LinkPlan plan = new LinkPlan(source, null, true); + LinkPlan plan = new LinkPlan(source, true, false); source.getOutboundLinks().add(plan); return plan; } public static LinkPlan createDungeonLink(RoomData source) { - if (source == null) - { - throw new IllegalArgumentException("source cannot be null."); - } - LinkPlan plan = new LinkPlan(source, null, false); + LinkPlan plan = new LinkPlan(source, false, false); source.getOutboundLinks().add(plan); return plan; } @@ -68,7 +63,7 @@ public class LinkPlan public boolean isInternal() { - return (destination != null); + return internal; } public void remove() @@ -84,4 +79,42 @@ public class LinkPlan destination = null; } } + + public void setDestination(RoomData destination) + { + if (!internal) + { + throw new IllegalStateException("LinkPlan.setDestination() is only applicable to internal links."); + } + if (this.destination != null) + { + throw new IllegalStateException("destination can only be set once."); + } + if (destination == null) + { + throw new IllegalArgumentException("destination cannot be null."); + } + this.destination = destination; + destination.getInboundLinks().add(this); + } + + public Point3D sourcePoint() + { + return this.sourcePoint; + } + + public Point3D destinationPoint() + { + return this.destinationPoint; + } + + public void setSourcePoint(Point3D value) + { + this.sourcePoint = value; + } + + public void setDestinationPoint(Point3D value) + { + this.destinationPoint = value; + } } diff --git a/src/main/java/StevenDimDoors/experimental/MazeBuilder.java b/src/main/java/StevenDimDoors/experimental/MazeBuilder.java index 6505f99..760c399 100644 --- a/src/main/java/StevenDimDoors/experimental/MazeBuilder.java +++ b/src/main/java/StevenDimDoors/experimental/MazeBuilder.java @@ -1,18 +1,23 @@ package StevenDimDoors.experimental; +import java.util.ArrayList; import java.util.Random; +import java.util.Stack; 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; +import StevenDimDoors.mod_pocketDim.config.DDProperties; public class MazeBuilder { + private static final int POCKET_WALL_GAP = 4; + private MazeBuilder() { } - public static void generate(World world, int x, int y, int z, Random random) + public static void generate(World world, int x, int y, int z, Random random, DDProperties properties) { // ISSUE FOR LATER: The room needs to be shifted so as to be centered on its entrance @@ -22,16 +27,85 @@ public class MazeBuilder buildRooms(design.getLayout(), world, offset); carveDoorways(design.getLayout(), world, offset, decay, random); - - placeDoors(design.getLayout(), world, offset); - applyRandomDestruction(design, world, offset, decay, random); + decorateRooms(design.getLayout(), world, offset); + buildPocketWalls(design, world, offset, properties); } private static void applyRandomDestruction(MazeDesign design, World world, Point3D offset, SphereDecayOperation decay, Random random) { - //final int DECAY_BOX_SIZE = 8 + final int DECAY_BOX_SIZE = 7; + final int DECAY_OPERATIONS = 5 + random.nextInt(5); + final int DECAY_ATTEMPTS = 20; + + int x, y, z; + int successes = 0; + int attempts = 0; + PartitionNode root = design.getRootPartition(); + + for (; successes < DECAY_OPERATIONS && attempts < DECAY_ATTEMPTS; attempts++) + { + // Select the coordinates at which to apply the decay operation + x = random.nextInt(design.width()) - DECAY_BOX_SIZE / 2; + y = random.nextInt(design.height()) - DECAY_BOX_SIZE / 2; + z = random.nextInt(design.length()) - DECAY_BOX_SIZE / 2; + + // Check that the decay operation would not impact any protected areas + // and mark the affected areas as decayed + if (markDecayArea(x, y, z, DECAY_BOX_SIZE, root)) + { + // Apply decay + decay.apply(world, offset.getX() + x, offset.getY() + y, offset.getZ() + z, + DECAY_BOX_SIZE, DECAY_BOX_SIZE, DECAY_BOX_SIZE); + successes++; + } + } + } + + private static boolean markDecayArea(int x, int y, int z, int DECAY_BOX_SIZE, PartitionNode root) + { + // Check if a given PartitionNode intersects the decay area. If it's a leaf, then check + // if it's protected or not. Otherwise, check its children. The specific area is valid + // if and only if there are no protected rooms and at least one (unprotected) room in it. + // Also list the unprotected rooms to mark them if the decay operation will proceed. + + RoomData room; + PartitionNode partition; + ArrayList targets = new ArrayList(); + Stack> nodes = new Stack>(); + BoundingBox decayBounds = new BoundingBox(x, y, z, DECAY_BOX_SIZE, DECAY_BOX_SIZE, DECAY_BOX_SIZE); + + // Use depth-first search to explore all intersecting partitions + nodes.push(root); + while (!nodes.isEmpty()) + { + partition = nodes.pop(); + if (decayBounds.intersects(partition)) + { + if (partition.isLeaf()) + { + room = partition.getData(); + if (room.isProtected()) + return false; + targets.add(room); + } + else + { + if (partition.leftChild() != null) + nodes.push(partition.leftChild()); + if (partition.rightChild() != null) + nodes.push(partition.rightChild()); + } + } + } + // If execution has reached this point, then there were no protected rooms. + // Mark all intersecting rooms as decayed. + for (RoomData target : targets) + { + target.setDecayed(true); + } + return !targets.isEmpty(); } private static void buildRooms(DirectedGraph layout, World world, Point3D offset) @@ -43,19 +117,25 @@ public class MazeBuilder } } - private static void placeDoors(DirectedGraph layout, World world, Point3D offset) + private static void decorateRooms(DirectedGraph layout, World world, Point3D offset) { + RoomData room; + PartitionNode partition; + ArrayList links = new ArrayList(); + + // Iterate over all rooms and apply decorators for (IGraphNode node : layout.nodes()) { - RoomData room = node.data(); - Point3D minCorner = room.getPartitionNode().minCorner(); - if (!room.getOutboundLinks().isEmpty()) - { - setBlockDirectly(world, offset.getX() + minCorner.getX(), offset.getY() + minCorner.getY() + 1, - offset.getZ() + minCorner.getZ(), Block.glowStone.blockID, 0); - setBlockDirectly(world, offset.getX() + minCorner.getX(), offset.getY() + minCorner.getY() + 2, - offset.getZ() + minCorner.getZ(), Block.glowStone.blockID, 0); - } + room = node.data(); + partition = room.getPartitionNode(); + links.addAll(room.getOutboundLinks()); + + // TODO: Add decorator code here! + } + // Iterate over all links plans and place links in the world + for (LinkPlan link : links) + { + // TODO: Add link placement code here! } } @@ -70,12 +150,19 @@ public class MazeBuilder { for (IEdge passage : node.outbound()) { + // Carve out the passage 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); + + // If this is a vertical passage, then mark the upper room as decayed + if (axis == DoorwayData.Y_AXIS) + { + passage.tail().data().setDecayed(true); + } } } } @@ -86,6 +173,8 @@ public class MazeBuilder final int MIN_DOUBLE_DOOR_SPAN = 10; int gap; + int rx; + int rz; switch (axis) { case DoorwayData.X_AXIS: @@ -150,9 +239,10 @@ public class MazeBuilder { gap = 6; } - decay.apply(world, - x + random.nextInt(width - gap - 1) + 1, y - 1, - z + random.nextInt(length - gap - 1) + 1, gap, 4, gap); + rx = x + random.nextInt(width - gap - 1) + 1; + rz = z + random.nextInt(length - gap - 1) + 1; + carveHole(world, rx + gap / 2, y, rz + gap / 2); + decay.apply(world, rx, y - 1, rz, gap, 4, gap); } else { @@ -184,6 +274,19 @@ public class MazeBuilder setBlockDirectly(world, x, y + 1, z, 0, 0); } + private static void buildPocketWalls(MazeDesign design, World world, Point3D offset, DDProperties properties) + { + // Build the inner Fabric of Reality box + Point3D minCorner = new Point3D(-POCKET_WALL_GAP - 1, -POCKET_WALL_GAP - 1, -POCKET_WALL_GAP - 1); + Point3D maxCorner = new Point3D(design.width() + POCKET_WALL_GAP, design.height() + POCKET_WALL_GAP, design.length() + POCKET_WALL_GAP); + buildBox(world, offset, minCorner, maxCorner, properties.FabricBlockID, 0); + + // Build the outer Eternal Fabric box + minCorner.add(-1, -1, -1); + maxCorner.add(1, 1, 1); + buildBox(world, offset, minCorner, maxCorner, properties.PermaFabricBlockID, 0); + } + private static void buildBox(World world, Point3D offset, Point3D minCorner, Point3D maxCorner, int blockID, int metadata) { int minX = minCorner.getX() + offset.getX(); diff --git a/src/main/java/StevenDimDoors/experimental/MazeDesign.java b/src/main/java/StevenDimDoors/experimental/MazeDesign.java index c88f1cd..16c190b 100644 --- a/src/main/java/StevenDimDoors/experimental/MazeDesign.java +++ b/src/main/java/StevenDimDoors/experimental/MazeDesign.java @@ -4,16 +4,16 @@ import java.util.ArrayList; public class MazeDesign { - private PartitionNode root; + private PartitionNode root; private DirectedGraph layout; - public MazeDesign(PartitionNode root, DirectedGraph layout) + public MazeDesign(PartitionNode root, DirectedGraph layout) { this.root = root; this.layout = layout; } - public PartitionNode getRootPartition() + public PartitionNode getRootPartition() { return root; } diff --git a/src/main/java/StevenDimDoors/experimental/MazeDesigner.java b/src/main/java/StevenDimDoors/experimental/MazeDesigner.java index 8707d46..53113cb 100644 --- a/src/main/java/StevenDimDoors/experimental/MazeDesigner.java +++ b/src/main/java/StevenDimDoors/experimental/MazeDesigner.java @@ -589,93 +589,180 @@ public class MazeDesigner // 3. Place internal links connecting the different sections of the maze // 4. Place more internal links to confuse people - // We need to start by counting the door capacity of each section and - // listing which rooms can have doors or destinations for each section. + // We need to start by building up data for each section, such as their + // door capacities and the rooms available for placing doors. int index; - int[] capacity = new int[cores.size()]; - ArrayList[] sourceRooms = (ArrayList[]) Array.newInstance(cores.getClass(), cores.size()); - ArrayList[] destinationRooms = (ArrayList[]) Array.newInstance(cores.getClass(), cores.size()); + int count; + SectionData selection; + SectionData destination; + ArrayList allSections; + ArrayList usableSections; - for (index = 0; index < sourceRooms.length; index++) - { - sourceRooms[index] = new ArrayList(); - destinationRooms[index] = new ArrayList(); - capacity[index] = listLinkRooms(cores.get(index).getLayoutNode(), sourceRooms[index], destinationRooms[index]); - } - - // Now we select the room in which to place the entrance. - // We can safely assume all source room lists are non-empty because - // createMazeSections() guarantees that each section has at least - // the capacity for 2 doors. - index = random.nextInt(sourceRooms.length); - createEntranceLink(sourceRooms[index], random.nextInt(sourceRooms[index].size())); - - // The next task is to place internal links. These links must connect - // the different maze sections to create a strongly connected graph. - - - } - - private static int listLinkRooms(IGraphNode core, - ArrayList sourceRooms, ArrayList destinationRooms) - { - int capacity = 0; - boolean hasHoles; - RoomData currentRoom; - IGraphNode current; - IGraphNode neighbor; - Stack> ordering = new Stack>(); - HashSet> visited = new HashSet>(); - - visited.add(core); - ordering.add(core); - while (!ordering.isEmpty()) - { - current = ordering.pop(); - hasHoles = false; - - for (IEdge edge : current.outbound()) + // Check if there is only one section. Our concerns differ depending + // on whether there is one or more than one. + if (cores.size() > 1) + { + // More than 1 section + allSections = new ArrayList(cores.size()); + for (RoomData core : cores) { - neighbor = edge.tail(); - if (visited.add(neighbor)) - { - ordering.add(neighbor); - } + allSections.add( SectionData.createFromCore(core.getLayoutNode()) ); } - for (IEdge edge : current.inbound()) + usableSections = (ArrayList) allSections.clone(); + + // Select the room in which to place the entrance. + // We can safely consider all sections because createMazeSections() + // guarantees that each one has at least the capacity for 2 doors. + // Remove the selected section if it falls below a capacity of 2 + // since we need to leave at least 1 capacity for section linking. + index = random.nextInt(usableSections.size()); + selection = usableSections.get(index); + selection.createEntranceLink(random); + if (selection.capacity() <= 1) { - neighbor = edge.head(); - if (visited.add(neighbor)) + usableSections.remove(index); + } + + // Place 3 to 4 dungeon doors in random sections + // Remove any sections that fall under a capacity of 2. + count = 3 + random.nextInt(2); + for (; count > 0 && !usableSections.isEmpty(); count--) + { + index = random.nextInt(usableSections.size()); + selection = usableSections.get(index); + selection.createDungeonLink(random); + if (selection.capacity() <= 1) { - ordering.add(neighbor); - } - if (edge.data().axis() == DoorwayData.Y_AXIS) - { - hasHoles = true; + usableSections.remove(index); } } - if (!hasHoles) + // The next task is to place internal links. These links must connect + // the different maze sections to create a strongly connected graph. + linkMazeSections(allSections, random); + + // Add 1 to 3 extra internal links to confuse people + usableSections.clear(); + for (SectionData section : allSections) { - currentRoom = current.data(); - destinationRooms.add(currentRoom); - if (currentRoom.estimateDoorCapacity() > 0) + if (section.capacity() > 0) { - capacity += currentRoom.estimateDoorCapacity(); - sourceRooms.add(currentRoom); + usableSections.add(section); + } + } + count = 1 + random.nextInt(3); + for (; count > 0 && !usableSections.isEmpty(); count--) + { + index = random.nextInt(usableSections.size()); + selection = usableSections.get(index); + destination = allSections.get( random.nextInt(allSections.size()) ); + selection.reserveSectionLink(destination, random); + if (selection.capacity() == 0) + { + usableSections.remove(index); + } + } + + // Finally, make sure to process all reservations for section links. + for (SectionData section : allSections) + { + section.processReservedLinks(random); + } + } + else + { + // Only 1 section + selection = SectionData.createFromCore(cores.get(0).getLayoutNode()); + // Place entrance door in a random room + selection.createEntranceLink(random); + // Place 3 to 4 dungeon doors or fewer, based on capacity + count = Math.min(3 + random.nextInt(2), selection.capacity()); + for (; count > 0; count--) + { + selection.createDungeonLink(random); + } + } + } + + private static void linkMazeSections(ArrayList sections, Random random) + { + // This algorithm constructs links sections together using Dimensional Doors + // to create a random strongly connected graph. It takes into account capacity + // constraints as well. We assume that all sections have at least 1 door capacity. + final int EXTENSION_CHANCE = 2; + final int MAX_EXTENSION_CHANCE = 3; + + int index; + SectionData start; + SectionData current; + SectionData next; + + // Total spare capacity of the sections not in "remaining" + int capacity; + // Sections not in the graph + ArrayList remaining = (ArrayList) sections.clone(); + // Sections that are part of an incomplete cycle + ArrayList attached = new ArrayList(sections.size()); + // Sections that are part of a cycle and thus strongly connected + ArrayList connected = new ArrayList(sections.size()); + // Sections that are part of a cycle and have spare capacity - used to start new cycles + ArrayList starters = new ArrayList(sections.size()); + + // Shuffle remaining to achieve randomness + Collections.shuffle(remaining, random); + // Remove the starting node to serve as the base of our strongly connected graph + start = remaining.remove(remaining.size() - 1); + starters.add(start); + connected.add(start); + capacity = start.capacity(); + + // Repeat until all sections are connected + while (!remaining.isEmpty()) + { + // Select a section from which to start a new cycle + index = random.nextInt(starters.size()); + start = starters.get(index); + // Select the first new section in the cycle and link to it + current = remaining.remove(remaining.size() - 1); + attached.add(current); + start.reserveSectionLink(current, random); + // Add the current section's capacity to the total, but subtract two to account + // for the link just created and for the future link from this section to another + capacity += current.capacity() - 2; + // Remove the starting section from starters if it has exhausted its capacity + if (start.capacity() == 0) + { + starters.remove(index); + } + + // Continue attaching sections to the partial cycle while there are are still sections + // left to be added and we have no spare capacity. Or we could randomly decide to + // continue even with spare capacity. Spare capacity means we could start a new cycle + // safely and still achieve strong connectivity. Randomness here influences the kinds + // of graphs we can get. + while (!remaining.isEmpty() && (capacity == 0 || + random.nextInt(MAX_EXTENSION_CHANCE) < EXTENSION_CHANCE)) + { + next = remaining.remove(remaining.size() - 1); + attached.add(next); + current.reserveSectionLink(next, random); + // Account for this section's capacity, but subtract one for + // the future link that will connect this section to another + capacity += next.capacity() - 1; + current = next; + } + next = connected.get(random.nextInt(connected.size())); + current.reserveSectionLink(next, random); + for (SectionData section : attached) + { + connected.add(section); + if (section.capacity() > 0) + { + starters.add(section); } } } - return capacity; - } - - private static void createEntranceLink(ArrayList sources, int index) - { - RoomData entranceRoom = sources.get(index); - LinkPlan.createEntranceLink(entranceRoom); - if (entranceRoom.getRemainingDoorCapacity() == 0) - { - sources.remove(index); - } + + // Done! At this point, all sections are connected. } } diff --git a/src/main/java/StevenDimDoors/experimental/SectionData.java b/src/main/java/StevenDimDoors/experimental/SectionData.java new file mode 100644 index 0000000..22d240a --- /dev/null +++ b/src/main/java/StevenDimDoors/experimental/SectionData.java @@ -0,0 +1,192 @@ +package StevenDimDoors.experimental; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Random; +import java.util.Stack; + +public class SectionData +{ + // Specifies the chance of selecting a destination from protectedRooms + // rather than from destinationRooms (which will then become protected) + private static final int PROTECTED_DESTINATION_CHANCE = 4; + private static final int MAX_PROTECTED_DESTINATION_CHANCE = 5; + + private int capacity; + private ArrayList sourceRooms; + private ArrayList protectedRooms; + private ArrayList destinationRooms; + private ArrayList reservations; + + private SectionData(ArrayList sourceRooms, ArrayList destinationRooms, int capacity) + { + this.capacity = capacity; + this.sourceRooms = sourceRooms; + this.destinationRooms = destinationRooms; + this.protectedRooms = new ArrayList(); + this.reservations = new ArrayList(); + } + + public static SectionData createFromCore(IGraphNode core) + { + int capacity = 0; + ArrayList sourceRooms = new ArrayList(); + ArrayList destinationRooms = new ArrayList(); + + boolean hasHoles; + RoomData currentRoom; + IGraphNode current; + IGraphNode neighbor; + Stack> ordering = new Stack>(); + HashSet> visited = new HashSet>(); + + visited.add(core); + ordering.add(core); + while (!ordering.isEmpty()) + { + current = ordering.pop(); + hasHoles = false; + + for (IEdge edge : current.outbound()) + { + neighbor = edge.tail(); + if (visited.add(neighbor)) + { + ordering.add(neighbor); + } + } + for (IEdge edge : current.inbound()) + { + neighbor = edge.head(); + if (visited.add(neighbor)) + { + ordering.add(neighbor); + } + if (edge.data().axis() == DoorwayData.Y_AXIS) + { + hasHoles = true; + } + } + + if (!hasHoles) + { + currentRoom = current.data(); + destinationRooms.add(currentRoom); + if (currentRoom.estimateDoorCapacity() > 0) + { + capacity += currentRoom.estimateDoorCapacity(); + sourceRooms.add(currentRoom); + } + } + } + return new SectionData(sourceRooms, destinationRooms, capacity); + } + + public int capacity() + { + return capacity; + } + + public void createEntranceLink(Random random) + { + int index = random.nextInt(sourceRooms.size()); + RoomData room = sourceRooms.get(index); + LinkPlan.createEntranceLink(room); + if (room.getRemainingDoorCapacity() == 0) + { + sourceRooms.remove(index); + } + // It's okay to check containment in this list because + // the number of protected rooms is expected to be small + if (!protectedRooms.contains(room)) + { + protectedRooms.add(room); + } + capacity--; + } + + public void createDungeonLink(Random random) + { + int index = random.nextInt(sourceRooms.size()); + RoomData room = sourceRooms.get(index); + LinkPlan.createDungeonLink(room); + if (room.getRemainingDoorCapacity() == 0) + { + sourceRooms.remove(index); + } + // It's okay to check containment in this list because + // the number of protected rooms is expected to be small + if (!protectedRooms.contains(room)) + { + protectedRooms.add(room); + } + capacity--; + } + + public void reserveSectionLink(SectionData destination, Random random) + { + // This method "reserves" a link by decrementing the capacity of this + // section and assigning a source room to the link. However, assigning + // its destination in a particular section is deferred. Why? + + // We favor using source rooms as destinations to cut down the number + // of rooms that have to be marked as protected against decay effects. + // We defer assigning a destination until after all source rooms are + // known so that we have that information available. Otherwise, + // destination selection would be biased toward non-source rooms and + // rooms with dungeon doors, which are placed before section links. + + int index = random.nextInt(sourceRooms.size()); + RoomData room = sourceRooms.get(index); + destination.reserveDestination(LinkPlan.createInternalLink(room)); + if (room.getRemainingDoorCapacity() == 0) + { + sourceRooms.remove(index); + } + // It's okay to check containment in this list because + // the number of protected rooms is expected to be small + if (!protectedRooms.contains(room)) + { + protectedRooms.add(room); + } + capacity--; + } + + public void processReservedLinks(Random random) + { + for (LinkPlan link : reservations) + { + link.setDestination( getLinkDestination(random) ); + } + reservations.clear(); + } + + private void reserveDestination(LinkPlan link) + { + reservations.add(link); + } + + private RoomData getLinkDestination(Random random) + { + RoomData destination; + + // Choose whether to select a room that is already protected or select + // from all possible destination rooms. Note that some destination rooms + // may also be protected rooms already. + if (random.nextInt(MAX_PROTECTED_DESTINATION_CHANCE) < PROTECTED_DESTINATION_CHANCE) + { + destination = protectedRooms.get( random.nextInt(protectedRooms.size()) ); + } + else + { + destination = destinationRooms.get( random.nextInt(destinationRooms.size()) ); + // It's okay to check containment in this list because + // the number of protected rooms is expected to be small + if (!protectedRooms.contains(destination)) + { + protectedRooms.add(destination); + } + } + return destination; + } +} diff --git a/src/main/java/StevenDimDoors/mod_pocketDim/Point3D.java b/src/main/java/StevenDimDoors/mod_pocketDim/Point3D.java index f639ce1..627bdab 100644 --- a/src/main/java/StevenDimDoors/mod_pocketDim/Point3D.java +++ b/src/main/java/StevenDimDoors/mod_pocketDim/Point3D.java @@ -55,6 +55,13 @@ public class Point3D implements Serializable { { return this.z = z; } + + public void add(int x, int y, int z) + { + this.x += x; + this.y += y; + this.z += z; + } @Override public Point3D clone() diff --git a/src/main/java/StevenDimDoors/mod_pocketDim/world/PocketBuilder.java b/src/main/java/StevenDimDoors/mod_pocketDim/world/PocketBuilder.java index 913c282..462033a 100644 --- a/src/main/java/StevenDimDoors/mod_pocketDim/world/PocketBuilder.java +++ b/src/main/java/StevenDimDoors/mod_pocketDim/world/PocketBuilder.java @@ -477,7 +477,7 @@ public class PocketBuilder } */ - MazeBuilder.generate(world, x, y, z, random); + MazeBuilder.generate(world, x, y, z, random, properties); //Build the door int doorOrientation = BlockRotator.transformMetadata(BlockRotator.EAST_DOOR_METADATA, orientation - BlockRotator.EAST_DOOR_METADATA + 2, properties.DimensionalDoorID); From bce329c8fb539881a0a885d5fa028aabe91ca629 Mon Sep 17 00:00:00 2001 From: SenseiKiwi Date: Tue, 15 Apr 2014 01:35:14 -0400 Subject: [PATCH 08/11] Added Maze Decorators Added Decorators for use in mazes. I've added several decorators but all of them are stubs for now. --- .../experimental/MazeBuilder.java | 22 ++++++-- .../decorators/BaseDecorator.java | 15 ++++++ .../decorators/DecoratorFinder.java | 51 +++++++++++++++++++ .../decorators/DefaultDoorDecorator.java | 23 +++++++++ .../decorators/LinkDestinationDecorator.java | 23 +++++++++ .../decorators/TorchDecorator.java | 23 +++++++++ 6 files changed, 153 insertions(+), 4 deletions(-) create mode 100644 src/main/java/StevenDimDoors/experimental/decorators/BaseDecorator.java create mode 100644 src/main/java/StevenDimDoors/experimental/decorators/DecoratorFinder.java create mode 100644 src/main/java/StevenDimDoors/experimental/decorators/DefaultDoorDecorator.java create mode 100644 src/main/java/StevenDimDoors/experimental/decorators/LinkDestinationDecorator.java create mode 100644 src/main/java/StevenDimDoors/experimental/decorators/TorchDecorator.java diff --git a/src/main/java/StevenDimDoors/experimental/MazeBuilder.java b/src/main/java/StevenDimDoors/experimental/MazeBuilder.java index 760c399..592b2d0 100644 --- a/src/main/java/StevenDimDoors/experimental/MazeBuilder.java +++ b/src/main/java/StevenDimDoors/experimental/MazeBuilder.java @@ -8,12 +8,16 @@ 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.experimental.decorators.BaseDecorator; +import StevenDimDoors.experimental.decorators.DecoratorFinder; import StevenDimDoors.mod_pocketDim.Point3D; import StevenDimDoors.mod_pocketDim.config.DDProperties; public class MazeBuilder { private static final int POCKET_WALL_GAP = 4; + private static final int DECORATION_CHANCE = 1; + private static final int MAX_DECORATION_CHANCE = 4; private MazeBuilder() { } @@ -28,7 +32,7 @@ public class MazeBuilder buildRooms(design.getLayout(), world, offset); carveDoorways(design.getLayout(), world, offset, decay, random); applyRandomDestruction(design, world, offset, decay, random); - decorateRooms(design.getLayout(), world, offset); + decorateRooms(design.getLayout(), world, offset, random, properties); buildPocketWalls(design, world, offset, properties); } @@ -117,9 +121,11 @@ public class MazeBuilder } } - private static void decorateRooms(DirectedGraph layout, World world, Point3D offset) + private static void decorateRooms(DirectedGraph layout, + World world, Point3D offset, Random random, DDProperties properties) { RoomData room; + BaseDecorator decorator; PartitionNode partition; ArrayList links = new ArrayList(); @@ -129,8 +135,16 @@ public class MazeBuilder room = node.data(); partition = room.getPartitionNode(); links.addAll(room.getOutboundLinks()); - - // TODO: Add decorator code here! + // Protected rooms must be decorated because they have links. + // Otherwise, choose randomly whether to decorate. + if (room.isProtected() && random.nextInt(MAX_DECORATION_CHANCE) < DECORATION_CHANCE) + { + decorator = DecoratorFinder.find(room, random); + if (decorator != null) + { + decorator.decorate(room, random, properties); + } + } } // Iterate over all links plans and place links in the world for (LinkPlan link : links) diff --git a/src/main/java/StevenDimDoors/experimental/decorators/BaseDecorator.java b/src/main/java/StevenDimDoors/experimental/decorators/BaseDecorator.java new file mode 100644 index 0000000..05bd70f --- /dev/null +++ b/src/main/java/StevenDimDoors/experimental/decorators/BaseDecorator.java @@ -0,0 +1,15 @@ +package StevenDimDoors.experimental.decorators; + +import java.util.Random; + +import StevenDimDoors.experimental.RoomData; +import StevenDimDoors.mod_pocketDim.config.DDProperties; + +public abstract class BaseDecorator +{ + public BaseDecorator() { } + + public abstract boolean canDecorate(RoomData room); + + public abstract boolean decorate(RoomData room, Random random, DDProperties properties); +} diff --git a/src/main/java/StevenDimDoors/experimental/decorators/DecoratorFinder.java b/src/main/java/StevenDimDoors/experimental/decorators/DecoratorFinder.java new file mode 100644 index 0000000..ce925fe --- /dev/null +++ b/src/main/java/StevenDimDoors/experimental/decorators/DecoratorFinder.java @@ -0,0 +1,51 @@ +package StevenDimDoors.experimental.decorators; + +import java.util.ArrayList; +import java.util.Random; + +import StevenDimDoors.experimental.RoomData; + +public class DecoratorFinder +{ + private static ArrayList decorators = null; + + private DecoratorFinder() { } + + public static BaseDecorator find(RoomData room, Random random) + { + if (decorators == null) + { + load(); + } + + // Since there are only a few decorators right now, we just iterate + // over the list and check them all. If we add a lot, we'll need to + // switch to a more efficient approach. + ArrayList matches = new ArrayList(); + for (BaseDecorator decorator : decorators) + { + if (decorator.canDecorate(room)) + { + matches.add(decorator); + } + } + + if (matches.isEmpty()) + { + return null; + } + else + { + return matches.get( random.nextInt(matches.size()) ); + } + } + + private static void load() + { + // List all the decorators we have + decorators = new ArrayList(); + decorators.add(new LinkDestinationDecorator()); + decorators.add(new DefaultDoorDecorator()); + decorators.add(new TorchDecorator()); + } +} diff --git a/src/main/java/StevenDimDoors/experimental/decorators/DefaultDoorDecorator.java b/src/main/java/StevenDimDoors/experimental/decorators/DefaultDoorDecorator.java new file mode 100644 index 0000000..ffc4fa7 --- /dev/null +++ b/src/main/java/StevenDimDoors/experimental/decorators/DefaultDoorDecorator.java @@ -0,0 +1,23 @@ +package StevenDimDoors.experimental.decorators; + +import java.util.Random; + +import StevenDimDoors.experimental.RoomData; +import StevenDimDoors.mod_pocketDim.config.DDProperties; + +public class DefaultDoorDecorator extends BaseDecorator { + + @Override + public boolean canDecorate(RoomData room) { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean decorate(RoomData room, Random random, + DDProperties properties) { + // TODO Auto-generated method stub + return false; + } + +} diff --git a/src/main/java/StevenDimDoors/experimental/decorators/LinkDestinationDecorator.java b/src/main/java/StevenDimDoors/experimental/decorators/LinkDestinationDecorator.java new file mode 100644 index 0000000..17f5db4 --- /dev/null +++ b/src/main/java/StevenDimDoors/experimental/decorators/LinkDestinationDecorator.java @@ -0,0 +1,23 @@ +package StevenDimDoors.experimental.decorators; + +import java.util.Random; + +import StevenDimDoors.experimental.RoomData; +import StevenDimDoors.mod_pocketDim.config.DDProperties; + + +public class LinkDestinationDecorator extends BaseDecorator +{ + @Override + public boolean canDecorate(RoomData room) { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean decorate(RoomData room, Random random, + DDProperties properties) { + // TODO Auto-generated method stub + return false; + } +} diff --git a/src/main/java/StevenDimDoors/experimental/decorators/TorchDecorator.java b/src/main/java/StevenDimDoors/experimental/decorators/TorchDecorator.java new file mode 100644 index 0000000..6d736ac --- /dev/null +++ b/src/main/java/StevenDimDoors/experimental/decorators/TorchDecorator.java @@ -0,0 +1,23 @@ +package StevenDimDoors.experimental.decorators; + +import java.util.Random; + +import StevenDimDoors.experimental.RoomData; +import StevenDimDoors.mod_pocketDim.config.DDProperties; + +public class TorchDecorator extends BaseDecorator { + + @Override + public boolean canDecorate(RoomData room) { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean decorate(RoomData room, Random random, + DDProperties properties) { + // TODO Auto-generated method stub + return false; + } + +} From f867f16d1d60258ddbc752f2d41ab55c0f244218 Mon Sep 17 00:00:00 2001 From: SenseiKiwi Date: Tue, 15 Apr 2014 01:46:39 -0400 Subject: [PATCH 09/11] Fixed Maze Bug Fixed a minor bug that affected maze decorators. --- src/main/java/StevenDimDoors/experimental/MazeBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/StevenDimDoors/experimental/MazeBuilder.java b/src/main/java/StevenDimDoors/experimental/MazeBuilder.java index 592b2d0..96a7b6a 100644 --- a/src/main/java/StevenDimDoors/experimental/MazeBuilder.java +++ b/src/main/java/StevenDimDoors/experimental/MazeBuilder.java @@ -137,7 +137,7 @@ public class MazeBuilder links.addAll(room.getOutboundLinks()); // Protected rooms must be decorated because they have links. // Otherwise, choose randomly whether to decorate. - if (room.isProtected() && random.nextInt(MAX_DECORATION_CHANCE) < DECORATION_CHANCE) + if (room.isProtected() || random.nextInt(MAX_DECORATION_CHANCE) < DECORATION_CHANCE) { decorator = DecoratorFinder.find(room, random); if (decorator != null) From 77abcbb14811a5f0e569c89b91f0481f582ec3fc Mon Sep 17 00:00:00 2001 From: SenseiKiwi Date: Tue, 15 Apr 2014 05:14:38 -0400 Subject: [PATCH 10/11] Progress on Maze Generation * Increased the chance of decorating unprotected rooms from 1/4 to 1/3. * Made minor optimizations to the box building function. * Added some important arguments to BaseDecorator.decorate(). Also updated all decorators as a result of this change. * Implemented TorchDecorator so that mazes have light sources in them. --- .../experimental/MazeBuilder.java | 12 +-- .../decorators/BaseDecorator.java | 4 +- .../decorators/DefaultDoorDecorator.java | 20 ++--- .../decorators/LinkDestinationDecorator.java | 19 ++--- .../decorators/TorchDecorator.java | 81 ++++++++++++++++--- 5 files changed, 102 insertions(+), 34 deletions(-) diff --git a/src/main/java/StevenDimDoors/experimental/MazeBuilder.java b/src/main/java/StevenDimDoors/experimental/MazeBuilder.java index 96a7b6a..c863046 100644 --- a/src/main/java/StevenDimDoors/experimental/MazeBuilder.java +++ b/src/main/java/StevenDimDoors/experimental/MazeBuilder.java @@ -17,7 +17,7 @@ public class MazeBuilder { private static final int POCKET_WALL_GAP = 4; private static final int DECORATION_CHANCE = 1; - private static final int MAX_DECORATION_CHANCE = 4; + private static final int MAX_DECORATION_CHANCE = 3; private MazeBuilder() { } @@ -142,11 +142,11 @@ public class MazeBuilder decorator = DecoratorFinder.find(room, random); if (decorator != null) { - decorator.decorate(room, random, properties); + decorator.decorate(room, world, offset, random, properties); } } } - // Iterate over all links plans and place links in the world + // Iterate over all link plans and place links in the world for (LinkPlan link : links) { // TODO: Add link placement code here! @@ -323,15 +323,15 @@ public class MazeBuilder } for (x = minX; x <= maxX; x++) { - for (y = minY; y <= maxY; y++) + for (y = minY + 1; y < maxY; y++) { setBlockDirectly(world, x, y, minZ, blockID, metadata); setBlockDirectly(world, x, y, maxZ, blockID, metadata); } } - for (z = minZ; z <= maxZ; z++) + for (z = minZ + 1; z < maxZ; z++) { - for (y = minY; y <= maxY; y++) + for (y = minY + 1; y < maxY; y++) { setBlockDirectly(world, minX, y, z, blockID, metadata); setBlockDirectly(world, maxX, y, z, blockID, metadata); diff --git a/src/main/java/StevenDimDoors/experimental/decorators/BaseDecorator.java b/src/main/java/StevenDimDoors/experimental/decorators/BaseDecorator.java index 05bd70f..2ea4e8a 100644 --- a/src/main/java/StevenDimDoors/experimental/decorators/BaseDecorator.java +++ b/src/main/java/StevenDimDoors/experimental/decorators/BaseDecorator.java @@ -2,7 +2,9 @@ package StevenDimDoors.experimental.decorators; import java.util.Random; +import net.minecraft.world.World; import StevenDimDoors.experimental.RoomData; +import StevenDimDoors.mod_pocketDim.Point3D; import StevenDimDoors.mod_pocketDim.config.DDProperties; public abstract class BaseDecorator @@ -11,5 +13,5 @@ public abstract class BaseDecorator public abstract boolean canDecorate(RoomData room); - public abstract boolean decorate(RoomData room, Random random, DDProperties properties); + public abstract void decorate(RoomData room, World world, Point3D offset, Random random, DDProperties properties); } diff --git a/src/main/java/StevenDimDoors/experimental/decorators/DefaultDoorDecorator.java b/src/main/java/StevenDimDoors/experimental/decorators/DefaultDoorDecorator.java index ffc4fa7..99446da 100644 --- a/src/main/java/StevenDimDoors/experimental/decorators/DefaultDoorDecorator.java +++ b/src/main/java/StevenDimDoors/experimental/decorators/DefaultDoorDecorator.java @@ -2,22 +2,24 @@ package StevenDimDoors.experimental.decorators; import java.util.Random; +import net.minecraft.world.World; + import StevenDimDoors.experimental.RoomData; +import StevenDimDoors.mod_pocketDim.Point3D; import StevenDimDoors.mod_pocketDim.config.DDProperties; -public class DefaultDoorDecorator extends BaseDecorator { - +public class DefaultDoorDecorator extends BaseDecorator +{ @Override - public boolean canDecorate(RoomData room) { - // TODO Auto-generated method stub - return false; + public boolean canDecorate(RoomData room) + { + return !room.getOutboundLinks().isEmpty(); } @Override - public boolean decorate(RoomData room, Random random, - DDProperties properties) { - // TODO Auto-generated method stub - return false; + public void decorate(RoomData room, World world, Point3D offset, Random random, DDProperties properties) + { + } } diff --git a/src/main/java/StevenDimDoors/experimental/decorators/LinkDestinationDecorator.java b/src/main/java/StevenDimDoors/experimental/decorators/LinkDestinationDecorator.java index 17f5db4..7203a44 100644 --- a/src/main/java/StevenDimDoors/experimental/decorators/LinkDestinationDecorator.java +++ b/src/main/java/StevenDimDoors/experimental/decorators/LinkDestinationDecorator.java @@ -2,22 +2,23 @@ package StevenDimDoors.experimental.decorators; import java.util.Random; -import StevenDimDoors.experimental.RoomData; -import StevenDimDoors.mod_pocketDim.config.DDProperties; +import net.minecraft.world.World; +import StevenDimDoors.experimental.RoomData; +import StevenDimDoors.mod_pocketDim.Point3D; +import StevenDimDoors.mod_pocketDim.config.DDProperties; public class LinkDestinationDecorator extends BaseDecorator { @Override - public boolean canDecorate(RoomData room) { - // TODO Auto-generated method stub - return false; + public boolean canDecorate(RoomData room) + { + return room.getOutboundLinks().isEmpty() && !room.getInboundLinks().isEmpty(); } @Override - public boolean decorate(RoomData room, Random random, - DDProperties properties) { - // TODO Auto-generated method stub - return false; + public void decorate(RoomData room, World world, Point3D offset, Random random, DDProperties properties) + { + } } diff --git a/src/main/java/StevenDimDoors/experimental/decorators/TorchDecorator.java b/src/main/java/StevenDimDoors/experimental/decorators/TorchDecorator.java index 6d736ac..c200465 100644 --- a/src/main/java/StevenDimDoors/experimental/decorators/TorchDecorator.java +++ b/src/main/java/StevenDimDoors/experimental/decorators/TorchDecorator.java @@ -2,22 +2,85 @@ package StevenDimDoors.experimental.decorators; import java.util.Random; +import net.minecraft.block.Block; +import net.minecraft.util.MathHelper; +import net.minecraft.world.World; + +import StevenDimDoors.experimental.PartitionNode; import StevenDimDoors.experimental.RoomData; +import StevenDimDoors.mod_pocketDim.Point3D; import StevenDimDoors.mod_pocketDim.config.DDProperties; -public class TorchDecorator extends BaseDecorator { - +public class TorchDecorator extends BaseDecorator +{ @Override - public boolean canDecorate(RoomData room) { - // TODO Auto-generated method stub - return false; + public boolean canDecorate(RoomData room) + { + return !room.isProtected(); } @Override - public boolean decorate(RoomData room, Random random, - DDProperties properties) { - // TODO Auto-generated method stub - return false; + public void decorate(RoomData room, World world, Point3D offset, Random random, DDProperties properties) + { + // SenseiKiwi: Place a single random torch along the walls. + // We could do more complex arrangements but I feel that a single + // torches here and there will be a little unsettling. + // The walls might be broken by passages or decay, so this will + // require trial and error. + + final int MAX_ATTEMPTS = 5; + + int x; + int z; + int attempts = 0; + PartitionNode partition = room.getPartitionNode(); + int minX = partition.minCorner().getX() + offset.getX(); + int minZ = partition.minCorner().getZ() + offset.getZ(); + int maxX = partition.maxCorner().getX() + offset.getX(); + int maxZ = partition.maxCorner().getZ() + offset.getZ(); + int torchLevel = partition.minCorner().getY() + offset.getY() + 2; + + for (; attempts < MAX_ATTEMPTS; attempts++) + { + // Choose a random side of the room to place the torch. The sides are numbered arbitrarily here. + // Then choose a random position along the wall and check if there is a block there to place the + // torch against. We assume that all blocks are bricks and thus valid. + switch (random.nextInt(4)) + { + case 0: // Positive X side + z = MathHelper.getRandomIntegerInRange(random, minZ + 1, maxZ - 1); + if (!world.isAirBlock(maxX, torchLevel, z)) + { + world.setBlock(maxX - 1, torchLevel, z, Block.torchWood.blockID, 2, 0); + return; + } + break; + case 1: // Negative X side + z = MathHelper.getRandomIntegerInRange(random, minZ + 1, maxZ - 1); + if (!world.isAirBlock(minX, torchLevel, z)) + { + world.setBlock(minX + 1, torchLevel, z, Block.torchWood.blockID, 1, 0); + return; + } + break; + case 2: // Positive Z side + x = MathHelper.getRandomIntegerInRange(random, minX + 1, maxX - 1); + if (!world.isAirBlock(x, torchLevel, maxZ)) + { + world.setBlock(x, torchLevel, maxZ - 1, Block.torchWood.blockID, 4, 0); + return; + } + break; + case 3: // Negative Z side + x = MathHelper.getRandomIntegerInRange(random, minX + 1, maxX - 1); + if (!world.isAirBlock(x, torchLevel, minZ)) + { + world.setBlock(x, torchLevel, minZ + 1, Block.torchWood.blockID, 3, 0); + return; + } + break; + } + } } } From fa629db4fec3df329bb482d3f9d43bfd4b3f1a1c Mon Sep 17 00:00:00 2001 From: SenseiKiwi Date: Tue, 15 Apr 2014 07:40:44 -0400 Subject: [PATCH 11/11] Progress on Maze Generation * Implemented link creation based on link plans * Improvised an implementation of door placement in DefaultDoorDecorator for testing purposes. I'll provide a better version later. Known issues: 1. Doors with one-way links to other rooms will generate a return door at the destination. The return door doesn't actually lead back, it leads to a new pocket. Need to disable pair generation for doors in mazes. 2. Consider weighing sections by the door capacity to avoid putting a lot of doors into a small section. Also double-check that room selection within sections is unbiased. --- .../experimental/MazeBuilder.java | 37 ++++++++++++++++++- .../decorators/DefaultDoorDecorator.java | 26 ++++++++++++- .../decorators/LinkDestinationDecorator.java | 13 +++++++ .../StevenDimDoors/mod_pocketDim/Point3D.java | 7 ++++ 4 files changed, 80 insertions(+), 3 deletions(-) diff --git a/src/main/java/StevenDimDoors/experimental/MazeBuilder.java b/src/main/java/StevenDimDoors/experimental/MazeBuilder.java index c863046..f7a434c 100644 --- a/src/main/java/StevenDimDoors/experimental/MazeBuilder.java +++ b/src/main/java/StevenDimDoors/experimental/MazeBuilder.java @@ -12,6 +12,10 @@ import StevenDimDoors.experimental.decorators.BaseDecorator; import StevenDimDoors.experimental.decorators.DecoratorFinder; import StevenDimDoors.mod_pocketDim.Point3D; import StevenDimDoors.mod_pocketDim.config.DDProperties; +import StevenDimDoors.mod_pocketDim.core.DimLink; +import StevenDimDoors.mod_pocketDim.core.LinkTypes; +import StevenDimDoors.mod_pocketDim.core.NewDimData; +import StevenDimDoors.mod_pocketDim.core.PocketManager; public class MazeBuilder { @@ -146,10 +150,39 @@ public class MazeBuilder } } } + // Iterate over all link plans and place links in the world - for (LinkPlan link : links) + NewDimData dimension = PocketManager.getDimensionData(world); + for (LinkPlan plan : links) { - // TODO: Add link placement code here! + createLinkFromPlan(plan, dimension, world); + } + } + + private static void createLinkFromPlan(LinkPlan plan, NewDimData dimension, World world) + { + // TODO: Support entrances! Right now we'll treat them as dungeon doors for testing + + DimLink link; + Point3D source; + Point3D destination; + int orientation; + + source = plan.sourcePoint(); + orientation = world.getBlockMetadata(source.getX(), source.getY(), source.getZ()) & 3; + + // Check the link type and set the destination accordingly + if (plan.isInternal()) + { + // Create a link between sections + destination = plan.destinationPoint(); + link = dimension.createLink(source.getX(), source.getY(), source.getZ(), LinkTypes.DUNGEON, orientation); + dimension.setDestination(link, destination.getX(), destination.getY(), destination.getZ()); + } + else + { + // Create a dungeon link + dimension.createLink(source.getX(), source.getY(), source.getZ(), LinkTypes.DUNGEON, orientation); } } diff --git a/src/main/java/StevenDimDoors/experimental/decorators/DefaultDoorDecorator.java b/src/main/java/StevenDimDoors/experimental/decorators/DefaultDoorDecorator.java index 99446da..fd653ab 100644 --- a/src/main/java/StevenDimDoors/experimental/decorators/DefaultDoorDecorator.java +++ b/src/main/java/StevenDimDoors/experimental/decorators/DefaultDoorDecorator.java @@ -2,10 +2,12 @@ package StevenDimDoors.experimental.decorators; import java.util.Random; +import net.minecraft.item.ItemDoor; import net.minecraft.world.World; - +import StevenDimDoors.experimental.LinkPlan; import StevenDimDoors.experimental.RoomData; import StevenDimDoors.mod_pocketDim.Point3D; +import StevenDimDoors.mod_pocketDim.mod_pocketDim; import StevenDimDoors.mod_pocketDim.config.DDProperties; public class DefaultDoorDecorator extends BaseDecorator @@ -19,6 +21,28 @@ public class DefaultDoorDecorator extends BaseDecorator @Override public void decorate(RoomData room, World world, Point3D offset, Random random, DDProperties properties) { + // TODO: This is just an improvised implementation for testing + Point3D corner = room.getPartitionNode().minCorner().clone(); + corner.add(offset); + + int count = 0; + Point3D source = null; + for (LinkPlan plan : room.getOutboundLinks()) + { + source = new Point3D(corner.getX() + 2, corner.getY() + 2, corner.getZ() + count + 1); + ItemDoor.placeDoorBlock(world, source.getX(), source.getY() - 1, source.getZ(), 0, mod_pocketDim.dimensionalDoor); + plan.setSourcePoint(source); + count++; + } + + if (source == null) + { + throw new IllegalStateException("This should never happen because this decorator only applies if outbound links exist!"); + } + for (LinkPlan plan : room.getInboundLinks()) + { + plan.setDestinationPoint(source); + } } diff --git a/src/main/java/StevenDimDoors/experimental/decorators/LinkDestinationDecorator.java b/src/main/java/StevenDimDoors/experimental/decorators/LinkDestinationDecorator.java index 7203a44..0b5d21f 100644 --- a/src/main/java/StevenDimDoors/experimental/decorators/LinkDestinationDecorator.java +++ b/src/main/java/StevenDimDoors/experimental/decorators/LinkDestinationDecorator.java @@ -4,6 +4,8 @@ import java.util.Random; import net.minecraft.world.World; +import StevenDimDoors.experimental.LinkPlan; +import StevenDimDoors.experimental.PartitionNode; import StevenDimDoors.experimental.RoomData; import StevenDimDoors.mod_pocketDim.Point3D; import StevenDimDoors.mod_pocketDim.config.DDProperties; @@ -19,6 +21,17 @@ public class LinkDestinationDecorator extends BaseDecorator @Override public void decorate(RoomData room, World world, Point3D offset, Random random, DDProperties properties) { + // Set the center of the room as the destination for all inbound links + PartitionNode partition = room.getPartitionNode(); + Point3D destination = partition.minCorner().clone(); + destination.add( + offset.getX() + partition.width() / 2, + offset.getY() + 2, + offset.getZ() + partition.length() / 2); + for (LinkPlan plan : room.getInboundLinks()) + { + plan.setDestinationPoint(destination); + } } } diff --git a/src/main/java/StevenDimDoors/mod_pocketDim/Point3D.java b/src/main/java/StevenDimDoors/mod_pocketDim/Point3D.java index 627bdab..f03469a 100644 --- a/src/main/java/StevenDimDoors/mod_pocketDim/Point3D.java +++ b/src/main/java/StevenDimDoors/mod_pocketDim/Point3D.java @@ -62,6 +62,13 @@ public class Point3D implements Serializable { this.y += y; this.z += z; } + + public void add(Point3D other) + { + this.x += other.x; + this.y += other.y; + this.z += other.z; + } @Override public Point3D clone()