Mazes #155
@@ -165,8 +165,8 @@ public class DirectedGraph<U, V>
|
||||
{
|
||||
Edge<U, V> innerEdge = (Edge<U, V>) 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.");
|
||||
|
||||
120
src/main/java/StevenDimDoors/experimental/LinkPlan.java
Normal file
120
src/main/java/StevenDimDoors/experimental/LinkPlan.java
Normal file
@@ -0,0 +1,120 @@
|
||||
package StevenDimDoors.experimental;
|
||||
|
||||
import StevenDimDoors.mod_pocketDim.Point3D;
|
||||
|
||||
public class LinkPlan
|
||||
{
|
||||
private RoomData source;
|
||||
private RoomData destination;
|
||||
private Point3D sourcePoint;
|
||||
private Point3D destinationPoint;
|
||||
private final boolean entrance;
|
||||
private final boolean internal;
|
||||
|
||||
private LinkPlan(RoomData source, boolean entrance, boolean internal)
|
||||
{
|
||||
if (source == null)
|
||||
{
|
||||
throw new IllegalArgumentException("source cannot be null.");
|
||||
}
|
||||
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);
|
||||
return plan;
|
||||
}
|
||||
|
||||
public static LinkPlan createEntranceLink(RoomData source)
|
||||
{
|
||||
LinkPlan plan = new LinkPlan(source, true, false);
|
||||
source.getOutboundLinks().add(plan);
|
||||
return plan;
|
||||
}
|
||||
|
||||
public static LinkPlan createDungeonLink(RoomData source)
|
||||
{
|
||||
LinkPlan plan = new LinkPlan(source, false, 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 internal;
|
||||
}
|
||||
|
||||
public void remove()
|
||||
{
|
||||
if (source != null)
|
||||
{
|
||||
source.getOutboundLinks().remove(this);
|
||||
source = null;
|
||||
}
|
||||
if (destination != null)
|
||||
{
|
||||
destination.getInboundLinks().remove(this);
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,63 +1,215 @@
|
||||
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.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
|
||||
{
|
||||
private static final int POCKET_WALL_GAP = 4;
|
||||
private static final int DECORATION_CHANCE = 1;
|
||||
private static final int MAX_DECORATION_CHANCE = 3;
|
||||
|
||||
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
|
||||
|
||||
MazeDesign design = MazeDesigner.generate(random);
|
||||
Point3D offset = new Point3D(x - design.width() / 2, y - design.height() - 1, z - design.length() / 2);
|
||||
SphereDecayOperation decay = new SphereDecayOperation(random, 0, 0, Block.stoneBrick.blockID, 2);
|
||||
|
||||
buildRooms(design.getRoomGraph(), world, offset);
|
||||
carveDoorways(design.getRoomGraph(), world, offset, decay, random);
|
||||
|
||||
//placeDoors(design, world, offset);
|
||||
|
||||
buildRooms(design.getLayout(), world, offset);
|
||||
carveDoorways(design.getLayout(), world, offset, decay, random);
|
||||
applyRandomDestruction(design, world, offset, decay, random);
|
||||
decorateRooms(design.getLayout(), world, offset, random, properties);
|
||||
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 void buildRooms(DirectedGraph<PartitionNode, DoorwayData> roomGraph, World world, Point3D offset)
|
||||
private static boolean markDecayArea(int x, int y, int z, int DECAY_BOX_SIZE, PartitionNode<RoomData> root)
|
||||
{
|
||||
for (IGraphNode<PartitionNode, DoorwayData> node : roomGraph.nodes())
|
||||
// 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<RoomData> partition;
|
||||
ArrayList<RoomData> targets = new ArrayList<RoomData>();
|
||||
Stack<PartitionNode<RoomData>> nodes = new Stack<PartitionNode<RoomData>>();
|
||||
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())
|
||||
{
|
||||
PartitionNode room = node.data();
|
||||
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<RoomData, DoorwayData> layout, World world, Point3D offset)
|
||||
{
|
||||
for (IGraphNode<RoomData, DoorwayData> node : layout.nodes())
|
||||
{
|
||||
PartitionNode room = node.data().getPartitionNode();
|
||||
buildBox(world, offset, room.minCorner(), room.maxCorner(), Block.stoneBrick.blockID, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private static void carveDoorways(DirectedGraph<PartitionNode, DoorwayData> roomGraph, World world,
|
||||
private static void decorateRooms(DirectedGraph<RoomData, DoorwayData> layout,
|
||||
World world, Point3D offset, Random random, DDProperties properties)
|
||||
{
|
||||
RoomData room;
|
||||
BaseDecorator decorator;
|
||||
PartitionNode<RoomData> partition;
|
||||
ArrayList<LinkPlan> links = new ArrayList<LinkPlan>();
|
||||
|
||||
// Iterate over all rooms and apply decorators
|
||||
for (IGraphNode<RoomData, DoorwayData> node : layout.nodes())
|
||||
{
|
||||
room = node.data();
|
||||
partition = room.getPartitionNode();
|
||||
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)
|
||||
{
|
||||
decorator = DecoratorFinder.find(room, random);
|
||||
if (decorator != null)
|
||||
{
|
||||
decorator.decorate(room, world, offset, random, properties);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate over all link plans and place links in the world
|
||||
NewDimData dimension = PocketManager.getDimensionData(world);
|
||||
for (LinkPlan plan : links)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
private static void carveDoorways(DirectedGraph<RoomData, DoorwayData> layout, World world,
|
||||
Point3D offset, SphereDecayOperation decay, Random random)
|
||||
{
|
||||
char axis;
|
||||
Point3D lower;
|
||||
DoorwayData doorway;
|
||||
|
||||
for (IGraphNode<PartitionNode, DoorwayData> node : roomGraph.nodes())
|
||||
for (IGraphNode<RoomData, DoorwayData> node : layout.nodes())
|
||||
{
|
||||
for (IEdge<PartitionNode, DoorwayData> passage : node.outbound())
|
||||
for (IEdge<RoomData, DoorwayData> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,6 +220,8 @@ public class MazeBuilder
|
||||
final int MIN_DOUBLE_DOOR_SPAN = 10;
|
||||
|
||||
int gap;
|
||||
int rx;
|
||||
int rz;
|
||||
switch (axis)
|
||||
{
|
||||
case DoorwayData.X_AXIS:
|
||||
@@ -132,9 +286,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
|
||||
{
|
||||
@@ -166,6 +321,18 @@ 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)
|
||||
{
|
||||
@@ -189,15 +356,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);
|
||||
|
||||
@@ -4,37 +4,23 @@ import java.util.ArrayList;
|
||||
|
||||
public class MazeDesign
|
||||
{
|
||||
private PartitionNode root;
|
||||
private DirectedGraph<PartitionNode, DoorwayData> rooms;
|
||||
private ArrayList<IGraphNode<PartitionNode, DoorwayData>> cores;
|
||||
private ArrayList<BoundingBox> protectedAreas;
|
||||
private PartitionNode<RoomData> root;
|
||||
private DirectedGraph<RoomData, DoorwayData> layout;
|
||||
|
||||
public MazeDesign(PartitionNode root, DirectedGraph<PartitionNode, DoorwayData> rooms,
|
||||
ArrayList<IGraphNode<PartitionNode, DoorwayData>> cores)
|
||||
public MazeDesign(PartitionNode<RoomData> root, DirectedGraph<RoomData, DoorwayData> layout)
|
||||
{
|
||||
this.root = root;
|
||||
this.rooms = rooms;
|
||||
this.cores = cores;
|
||||
this.layout = layout;
|
||||
}
|
||||
|
||||
public PartitionNode getRootPartition()
|
||||
public PartitionNode<RoomData> getRootPartition()
|
||||
{
|
||||
return root;
|
||||
}
|
||||
|
||||
public DirectedGraph<PartitionNode, DoorwayData> getRoomGraph()
|
||||
public DirectedGraph<RoomData, DoorwayData> getLayout()
|
||||
{
|
||||
return rooms;
|
||||
}
|
||||
|
||||
public ArrayList<IGraphNode<PartitionNode, DoorwayData>> getCoreNodes()
|
||||
{
|
||||
return cores;
|
||||
}
|
||||
|
||||
public ArrayList<BoundingBox> getProtectedAreas()
|
||||
{
|
||||
return protectedAreas;
|
||||
return layout;
|
||||
}
|
||||
|
||||
public int width()
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package StevenDimDoors.experimental;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Queue;
|
||||
import java.util.Random;
|
||||
@@ -25,54 +26,46 @@ 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<RoomData> 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<PartitionNode> partitions = new ArrayList<PartitionNode>(1 << SPLIT_COUNT);
|
||||
listRoomPartitions(root, partitions);
|
||||
// Attach rooms to all the leaf nodes of the partition tree
|
||||
ArrayList<RoomData> rooms = new ArrayList<RoomData>(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<PartitionNode, DoorwayData> rooms = createRoomGraph(root, partitions, random);
|
||||
DirectedGraph<RoomData, DoorwayData> layout = createRoomGraph(root, rooms, random);
|
||||
|
||||
// Cut out random subgraphs from the adjacency graph
|
||||
ArrayList<IGraphNode<PartitionNode, DoorwayData>> cores = createMazeSections(rooms, random);
|
||||
ArrayList<RoomData> cores = createMazeSections(layout, random);
|
||||
|
||||
// Remove unnecessary passages through floors/ceilings and some from the walls
|
||||
for (IGraphNode<PartitionNode, DoorwayData> core : cores)
|
||||
for (RoomData core : cores)
|
||||
{
|
||||
pruneDoorways(core, rooms, random);
|
||||
pruneDoorways(core.getLayoutNode(), layout, random);
|
||||
}
|
||||
|
||||
return new MazeDesign(root, rooms, cores);
|
||||
// Set up the placement of dimensional doors within the maze
|
||||
createMazeLinks(layout, cores, random);
|
||||
|
||||
return new MazeDesign(root, layout);
|
||||
}
|
||||
|
||||
private static void listRoomPartitions(PartitionNode node, ArrayList<PartitionNode> partitions)
|
||||
private static void attachRooms(PartitionNode<RoomData> node, ArrayList<RoomData> partitions)
|
||||
{
|
||||
if (node.isLeaf())
|
||||
{
|
||||
partitions.add(node);
|
||||
partitions.add(new RoomData(node));
|
||||
}
|
||||
else
|
||||
{
|
||||
listRoomPartitions(node.leftChild(), partitions);
|
||||
listRoomPartitions(node.rightChild(), partitions);
|
||||
}
|
||||
}
|
||||
|
||||
private static void removeRoomPartitions(PartitionNode node)
|
||||
{
|
||||
// Remove a node and any of its ancestors that become leaf nodes
|
||||
PartitionNode parent;
|
||||
PartitionNode current;
|
||||
|
||||
current = node;
|
||||
while (current != null && current.isLeaf())
|
||||
{
|
||||
parent = current.parent();
|
||||
current.remove();
|
||||
current = parent;
|
||||
attachRooms(node.leftChild(), partitions);
|
||||
attachRooms(node.rightChild(), partitions);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,35 +133,25 @@ public class MazeDesigner
|
||||
}
|
||||
}
|
||||
|
||||
private static DirectedGraph<PartitionNode, DoorwayData> createRoomGraph(PartitionNode root, ArrayList<PartitionNode> partitions, Random random)
|
||||
private static DirectedGraph<RoomData, DoorwayData> createRoomGraph(PartitionNode<RoomData> root, ArrayList<RoomData> rooms, Random random)
|
||||
{
|
||||
DirectedGraph<PartitionNode, DoorwayData> roomGraph = new DirectedGraph<PartitionNode, DoorwayData>();
|
||||
HashMap<PartitionNode, IGraphNode<PartitionNode, DoorwayData>> roomsToGraph = new HashMap<PartitionNode, IGraphNode<PartitionNode, DoorwayData>>(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<RoomData, DoorwayData> layout = new DirectedGraph<RoomData, DoorwayData>();
|
||||
|
||||
// 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<PartitionNode, DoorwayData> node : roomGraph.nodes())
|
||||
for (IGraphNode<RoomData, DoorwayData> node : layout.nodes())
|
||||
{
|
||||
findDoorways(node, root, roomsToGraph, roomGraph);
|
||||
findDoorways(node.data(), root, layout);
|
||||
}
|
||||
return layout;
|
||||
}
|
||||
|
||||
return roomGraph;
|
||||
}
|
||||
|
||||
private static void findDoorways(IGraphNode<PartitionNode, DoorwayData> roomNode, PartitionNode root,
|
||||
HashMap<PartitionNode, IGraphNode<PartitionNode, DoorwayData>> roomsToGraph,
|
||||
DirectedGraph<PartitionNode, DoorwayData> roomGraph)
|
||||
private static void findDoorways(RoomData room, PartitionNode<RoomData> root,
|
||||
DirectedGraph<RoomData, DoorwayData> 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 +169,7 @@ public class MazeDesigner
|
||||
// there will always be a way to walk from any room to any other room.
|
||||
|
||||
boolean[][] detected;
|
||||
PartitionNode adjacent;
|
||||
PartitionNode<RoomData> adjacent;
|
||||
|
||||
int a, b, c;
|
||||
int p, q, r;
|
||||
@@ -195,11 +178,10 @@ public class MazeDesigner
|
||||
Point3D otherMin;
|
||||
Point3D otherMax;
|
||||
DoorwayData doorway;
|
||||
IGraphNode<PartitionNode, DoorwayData> 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 +191,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 +229,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 +276,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 +323,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,108 +338,130 @@ public class MazeDesigner
|
||||
//Done!
|
||||
}
|
||||
|
||||
private static ArrayList<IGraphNode<PartitionNode, DoorwayData>> createMazeSections(DirectedGraph<PartitionNode, DoorwayData> roomGraph, Random random)
|
||||
private static ArrayList<RoomData> createMazeSections(DirectedGraph<RoomData, DoorwayData> layout, Random random)
|
||||
{
|
||||
// The randomness of the sections generated here hinges on
|
||||
// the nodes in the graph being in a random order. We assume
|
||||
// that was handled in a previous step!
|
||||
|
||||
final int MAX_DISTANCE = 2;
|
||||
// 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 + random.nextInt(2);
|
||||
final int MIN_SECTION_ROOMS = 5;
|
||||
final int MIN_SECTION_CAPACITY = 2;
|
||||
|
||||
int distance;
|
||||
IGraphNode<PartitionNode, DoorwayData> current;
|
||||
IGraphNode<PartitionNode, DoorwayData> neighbor;
|
||||
int capacity;
|
||||
RoomData room;
|
||||
RoomData neighbor;
|
||||
boolean hasHoles;
|
||||
IGraphNode<RoomData, DoorwayData> roomNode;
|
||||
|
||||
ArrayList<IGraphNode<PartitionNode, DoorwayData>> cores = new ArrayList<IGraphNode<PartitionNode, DoorwayData>>();
|
||||
ArrayList<IGraphNode<PartitionNode, DoorwayData>> removals = new ArrayList<IGraphNode<PartitionNode, DoorwayData>>();
|
||||
ArrayList<IGraphNode<PartitionNode, DoorwayData>> section = new ArrayList<IGraphNode<PartitionNode, DoorwayData>>();
|
||||
ArrayList<RoomData> cores = new ArrayList<RoomData>();
|
||||
ArrayList<RoomData> section = new ArrayList<RoomData>();
|
||||
ArrayList<IGraphNode<RoomData, DoorwayData>> nodes = new ArrayList<IGraphNode<RoomData, DoorwayData>>(layout.nodeCount());
|
||||
|
||||
Queue<IGraphNode<PartitionNode, DoorwayData>> ordering = new LinkedList<IGraphNode<PartitionNode, DoorwayData>>();
|
||||
HashMap<IGraphNode<PartitionNode, DoorwayData>, Integer> distances = new HashMap<IGraphNode<PartitionNode, DoorwayData>, Integer>();
|
||||
Queue<RoomData> ordering = new LinkedList<RoomData>();
|
||||
|
||||
// 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<RoomData, DoorwayData> node : layout.nodes())
|
||||
{
|
||||
nodes.add(node);
|
||||
}
|
||||
|
||||
// Repeatedly generate sections until all nodes have been visited
|
||||
for (IGraphNode<PartitionNode, DoorwayData> node : roomGraph.nodes())
|
||||
for (IGraphNode<RoomData, DoorwayData> node : nodes)
|
||||
{
|
||||
// If this node hasn't been visited, then use it as the core of a new section
|
||||
// Otherwise, ignore it, since it was already processed
|
||||
if (!distances.containsKey(node))
|
||||
// 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. Also make sure to check that room
|
||||
// isn't null, which happens if the room was removed previously.
|
||||
room = node.data();
|
||||
if (room != null && room.getDistance() < 0)
|
||||
{
|
||||
// Perform a breadth-first search to tag surrounding nodes with distances
|
||||
distances.put(node, 0);
|
||||
ordering.add(node);
|
||||
ordering.add(room);
|
||||
room.setDistance(0);
|
||||
section.clear();
|
||||
capacity = 0;
|
||||
|
||||
while (!ordering.isEmpty())
|
||||
while (room != null && (room.getDistance() <= MAX_DISTANCE || capacity < 2))
|
||||
{
|
||||
current = ordering.remove();
|
||||
distance = distances.get(current) + 1;
|
||||
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<RoomData, DoorwayData> edge : roomNode.inbound())
|
||||
{
|
||||
section.add(current);
|
||||
|
||||
// Visit neighboring nodes and assign them distances, if they don't
|
||||
// have a distance assigned already
|
||||
for (IEdge<PartitionNode, DoorwayData> edge : current.inbound())
|
||||
neighbor = edge.head().data();
|
||||
if (neighbor.getDistance() < 0)
|
||||
{
|
||||
neighbor = edge.head();
|
||||
if (!distances.containsKey(neighbor))
|
||||
neighbor.setDistance(distance);
|
||||
ordering.add(neighbor);
|
||||
}
|
||||
if (edge.data().axis() == DoorwayData.Y_AXIS)
|
||||
{
|
||||
distances.put(neighbor, distance);
|
||||
hasHoles = true;
|
||||
}
|
||||
}
|
||||
for (IEdge<RoomData, DoorwayData> edge : roomNode.outbound())
|
||||
{
|
||||
neighbor = edge.tail().data();
|
||||
if (neighbor.getDistance() < 0)
|
||||
{
|
||||
neighbor.setDistance(distance);
|
||||
ordering.add(neighbor);
|
||||
}
|
||||
}
|
||||
for (IEdge<PartitionNode, DoorwayData> edge : current.outbound())
|
||||
|
||||
// Count this room's door capacity if it has no floor holes
|
||||
if (!hasHoles)
|
||||
{
|
||||
neighbor = edge.tail();
|
||||
if (!distances.containsKey(neighbor))
|
||||
{
|
||||
distances.put(neighbor, distance);
|
||||
ordering.add(neighbor);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
removals.add(current);
|
||||
break;
|
||||
}
|
||||
capacity += room.estimateDoorCapacity();
|
||||
}
|
||||
|
||||
// List nodes that have a distance of exactly MAX_DISTANCE + 1
|
||||
// Those are precisely the nodes that remain in the queue
|
||||
// We can't remove them immediately because that could break
|
||||
// the iterator for the graph.
|
||||
room = ordering.peek();
|
||||
}
|
||||
|
||||
// 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.
|
||||
while (!ordering.isEmpty())
|
||||
{
|
||||
removals.add(ordering.remove());
|
||||
ordering.remove().remove();
|
||||
}
|
||||
|
||||
// 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);
|
||||
cores.add(node.data());
|
||||
}
|
||||
else
|
||||
{
|
||||
removals.addAll(section);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove all the nodes that were listed for removal
|
||||
// Also remove unused partitions from the partition tree
|
||||
for (IGraphNode<PartitionNode, DoorwayData> node : removals)
|
||||
// Discard the whole section
|
||||
for (RoomData target : section)
|
||||
{
|
||||
removeRoomPartitions(node.data());
|
||||
roomGraph.removeNode(node);
|
||||
target.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return cores;
|
||||
}
|
||||
|
||||
private static void pruneDoorways(IGraphNode<PartitionNode, DoorwayData> core,
|
||||
DirectedGraph<PartitionNode, DoorwayData> rooms, Random random)
|
||||
private static void pruneDoorways(IGraphNode<RoomData, DoorwayData> core,
|
||||
DirectedGraph<RoomData, DoorwayData> 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 +479,12 @@ public class MazeDesigner
|
||||
// idea applies for the other doorways, plus some randomness.
|
||||
|
||||
// First, list all nodes in the subgraph
|
||||
IGraphNode<PartitionNode, DoorwayData> current;
|
||||
IGraphNode<PartitionNode, DoorwayData> neighbor;
|
||||
IGraphNode<RoomData, DoorwayData> current;
|
||||
IGraphNode<RoomData, DoorwayData> neighbor;
|
||||
|
||||
Stack<IGraphNode<PartitionNode, DoorwayData>> ordering = new Stack<IGraphNode<PartitionNode, DoorwayData>>();
|
||||
ArrayList<IGraphNode<PartitionNode, DoorwayData>> subgraph = new ArrayList<IGraphNode<PartitionNode, DoorwayData>>(64);
|
||||
DisjointSet<IGraphNode<PartitionNode, DoorwayData>> components = new DisjointSet<IGraphNode<PartitionNode, DoorwayData>>(128);
|
||||
Stack<IGraphNode<RoomData, DoorwayData>> ordering = new Stack<IGraphNode<RoomData, DoorwayData>>();
|
||||
ArrayList<IGraphNode<RoomData, DoorwayData>> subgraph = new ArrayList<IGraphNode<RoomData, DoorwayData>>(64);
|
||||
DisjointSet<IGraphNode<RoomData, DoorwayData>> components = new DisjointSet<IGraphNode<RoomData, DoorwayData>>(128);
|
||||
|
||||
ordering.add(core);
|
||||
components.makeSet(core);
|
||||
@@ -492,7 +493,7 @@ public class MazeDesigner
|
||||
current = ordering.pop();
|
||||
subgraph.add(current);
|
||||
|
||||
for (IEdge<PartitionNode, DoorwayData> edge : current.inbound())
|
||||
for (IEdge<RoomData, DoorwayData> edge : current.inbound())
|
||||
{
|
||||
neighbor = edge.head();
|
||||
if (components.makeSet(neighbor))
|
||||
@@ -500,7 +501,7 @@ public class MazeDesigner
|
||||
ordering.add(neighbor);
|
||||
}
|
||||
}
|
||||
for (IEdge<PartitionNode, DoorwayData> edge : current.outbound())
|
||||
for (IEdge<RoomData, DoorwayData> edge : current.outbound())
|
||||
{
|
||||
neighbor = edge.tail();
|
||||
if (components.makeSet(neighbor))
|
||||
@@ -510,15 +511,17 @@ 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<IEdge<PartitionNode, DoorwayData>> targets =
|
||||
new ArrayList<IEdge<PartitionNode, DoorwayData>>();
|
||||
// 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.
|
||||
ArrayList<IEdge<RoomData, DoorwayData>> targets =
|
||||
new ArrayList<IEdge<RoomData, DoorwayData>>();
|
||||
|
||||
for (IGraphNode<PartitionNode, DoorwayData> room : subgraph)
|
||||
for (IGraphNode<RoomData, DoorwayData> room : subgraph)
|
||||
{
|
||||
for (IEdge<PartitionNode, DoorwayData> passage : room.outbound())
|
||||
for (IEdge<RoomData, DoorwayData> passage : room.outbound())
|
||||
{
|
||||
if (passage.data().axis() != DoorwayData.Y_AXIS)
|
||||
{
|
||||
@@ -535,11 +538,12 @@ public class MazeDesigner
|
||||
Collections.shuffle(targets, random);
|
||||
|
||||
// Merge sets together and remove unnecessary doorways
|
||||
for (IEdge<PartitionNode, DoorwayData> passage : targets)
|
||||
for (IEdge<RoomData, DoorwayData> passage : targets)
|
||||
{
|
||||
if (!components.mergeSets(passage.head(), passage.tail()))
|
||||
{
|
||||
rooms.removeEdge(passage);
|
||||
layout.removeEdge(passage);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -548,13 +552,13 @@ public class MazeDesigner
|
||||
components.clear();
|
||||
targets.clear();
|
||||
|
||||
for (IGraphNode<PartitionNode, DoorwayData> room : subgraph)
|
||||
for (IGraphNode<RoomData, DoorwayData> room : subgraph)
|
||||
{
|
||||
components.makeSet(room);
|
||||
}
|
||||
for (IGraphNode<PartitionNode, DoorwayData> room : subgraph)
|
||||
for (IGraphNode<RoomData, DoorwayData> room : subgraph)
|
||||
{
|
||||
for (IEdge<PartitionNode, DoorwayData> passage : room.outbound())
|
||||
for (IEdge<RoomData, DoorwayData> passage : room.outbound())
|
||||
{
|
||||
if (passage.data().axis() == DoorwayData.Y_AXIS)
|
||||
{
|
||||
@@ -567,13 +571,198 @@ public class MazeDesigner
|
||||
}
|
||||
}
|
||||
Collections.shuffle(targets, random);
|
||||
for (IEdge<PartitionNode, DoorwayData> passage : targets)
|
||||
for (IEdge<RoomData, DoorwayData> passage : targets)
|
||||
{
|
||||
if (!components.mergeSets(passage.head(), passage.tail()) && random.nextBoolean())
|
||||
{
|
||||
rooms.removeEdge(passage);
|
||||
layout.removeEdge(passage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void createMazeLinks(DirectedGraph<RoomData, DoorwayData> layout,
|
||||
ArrayList<RoomData> cores, Random random)
|
||||
{
|
||||
// We have 4 objectives here...
|
||||
// 1. Place the entrance to the maze
|
||||
// 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 building up data for each section, such as their
|
||||
// door capacities and the rooms available for placing doors.
|
||||
int index;
|
||||
int count;
|
||||
SectionData selection;
|
||||
SectionData destination;
|
||||
ArrayList<SectionData> allSections;
|
||||
ArrayList<SectionData> usableSections;
|
||||
|
||||
// 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<SectionData>(cores.size());
|
||||
for (RoomData core : cores)
|
||||
{
|
||||
allSections.add( SectionData.createFromCore(core.getLayoutNode()) );
|
||||
}
|
||||
usableSections = (ArrayList<SectionData>) 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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
usableSections.remove(index);
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
if (section.capacity() > 0)
|
||||
{
|
||||
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<SectionData> 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<SectionData> remaining = (ArrayList<SectionData>) sections.clone();
|
||||
// Sections that are part of an incomplete cycle
|
||||
ArrayList<SectionData> attached = new ArrayList<SectionData>(sections.size());
|
||||
// Sections that are part of a cycle and thus strongly connected
|
||||
ArrayList<SectionData> connected = new ArrayList<SectionData>(sections.size());
|
||||
// Sections that are part of a cycle and have spare capacity - used to start new cycles
|
||||
ArrayList<SectionData> starters = new ArrayList<SectionData>(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Done! At this point, all sections are connected.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,12 @@ package StevenDimDoors.experimental;
|
||||
|
||||
import StevenDimDoors.mod_pocketDim.Point3D;
|
||||
|
||||
public class PartitionNode extends BoundingBox
|
||||
public class PartitionNode<T> 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;
|
||||
}
|
||||
}
|
||||
|
||||
133
src/main/java/StevenDimDoors/experimental/RoomData.java
Normal file
133
src/main/java/StevenDimDoors/experimental/RoomData.java
Normal file
@@ -0,0 +1,133 @@
|
||||
package StevenDimDoors.experimental;
|
||||
|
||||
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<LinkPlan> inboundLinks;
|
||||
private ArrayList<LinkPlan> outboundLinks;
|
||||
private DirectedGraph<RoomData, DoorwayData> layout;
|
||||
private IGraphNode<RoomData, DoorwayData> layoutNode;
|
||||
|
||||
public RoomData(PartitionNode partitionNode)
|
||||
{
|
||||
this.partitionNode = partitionNode;
|
||||
this.inboundLinks = new ArrayList<LinkPlan>();
|
||||
this.outboundLinks = new ArrayList<LinkPlan>();
|
||||
this.layoutNode = null;
|
||||
this.layout = null;
|
||||
this.distance = -1;
|
||||
this.capacity = -1;
|
||||
this.decayed = false;
|
||||
partitionNode.setData(this);
|
||||
}
|
||||
|
||||
public PartitionNode getPartitionNode()
|
||||
{
|
||||
return this.partitionNode;
|
||||
}
|
||||
|
||||
public IGraphNode<RoomData, DoorwayData> getLayoutNode()
|
||||
{
|
||||
return this.layoutNode;
|
||||
}
|
||||
|
||||
public void addToLayout(DirectedGraph<RoomData, DoorwayData> layout)
|
||||
{
|
||||
this.layout = layout;
|
||||
this.layoutNode = layout.addNode(this);
|
||||
}
|
||||
|
||||
public boolean isDecayed()
|
||||
{
|
||||
return decayed;
|
||||
}
|
||||
|
||||
public void setDecayed(boolean value)
|
||||
{
|
||||
this.decayed = value;
|
||||
}
|
||||
|
||||
public ArrayList<LinkPlan> getInboundLinks()
|
||||
{
|
||||
return this.inboundLinks;
|
||||
}
|
||||
|
||||
public ArrayList<LinkPlan> getOutboundLinks()
|
||||
{
|
||||
return this.outboundLinks;
|
||||
}
|
||||
|
||||
public int getDistance()
|
||||
{
|
||||
return distance;
|
||||
}
|
||||
|
||||
public void setDistance(int value)
|
||||
{
|
||||
distance = value;
|
||||
}
|
||||
|
||||
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;
|
||||
inboundLinks = null;
|
||||
outboundLinks = null;
|
||||
}
|
||||
|
||||
public int estimateDoorCapacity()
|
||||
{
|
||||
if (capacity >= 0)
|
||||
return capacity;
|
||||
|
||||
int cellsX = (partitionNode.width() - 3) / 2;
|
||||
int cellsZ = (partitionNode.length() - 3) / 2;
|
||||
capacity = Math.min(cellsX * cellsZ, 3);
|
||||
return capacity;
|
||||
}
|
||||
|
||||
public int getRemainingDoorCapacity()
|
||||
{
|
||||
return (estimateDoorCapacity() - outboundLinks.size());
|
||||
}
|
||||
|
||||
public boolean isProtected()
|
||||
{
|
||||
return !inboundLinks.isEmpty() || !outboundLinks.isEmpty();
|
||||
}
|
||||
}
|
||||
192
src/main/java/StevenDimDoors/experimental/SectionData.java
Normal file
192
src/main/java/StevenDimDoors/experimental/SectionData.java
Normal file
@@ -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<RoomData> sourceRooms;
|
||||
private ArrayList<RoomData> protectedRooms;
|
||||
private ArrayList<RoomData> destinationRooms;
|
||||
private ArrayList<LinkPlan> reservations;
|
||||
|
||||
private SectionData(ArrayList<RoomData> sourceRooms, ArrayList<RoomData> destinationRooms, int capacity)
|
||||
{
|
||||
this.capacity = capacity;
|
||||
this.sourceRooms = sourceRooms;
|
||||
this.destinationRooms = destinationRooms;
|
||||
this.protectedRooms = new ArrayList<RoomData>();
|
||||
this.reservations = new ArrayList<LinkPlan>();
|
||||
}
|
||||
|
||||
public static SectionData createFromCore(IGraphNode<RoomData, DoorwayData> core)
|
||||
{
|
||||
int capacity = 0;
|
||||
ArrayList<RoomData> sourceRooms = new ArrayList<RoomData>();
|
||||
ArrayList<RoomData> destinationRooms = new ArrayList<RoomData>();
|
||||
|
||||
boolean hasHoles;
|
||||
RoomData currentRoom;
|
||||
IGraphNode<RoomData, DoorwayData> current;
|
||||
IGraphNode<RoomData, DoorwayData> neighbor;
|
||||
Stack<IGraphNode<RoomData, DoorwayData>> ordering = new Stack<IGraphNode<RoomData, DoorwayData>>();
|
||||
HashSet<IGraphNode<RoomData, DoorwayData>> visited = new HashSet<IGraphNode<RoomData, DoorwayData>>();
|
||||
|
||||
visited.add(core);
|
||||
ordering.add(core);
|
||||
while (!ordering.isEmpty())
|
||||
{
|
||||
current = ordering.pop();
|
||||
hasHoles = false;
|
||||
|
||||
for (IEdge<RoomData, DoorwayData> edge : current.outbound())
|
||||
{
|
||||
neighbor = edge.tail();
|
||||
if (visited.add(neighbor))
|
||||
{
|
||||
ordering.add(neighbor);
|
||||
}
|
||||
}
|
||||
for (IEdge<RoomData, DoorwayData> 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
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
|
||||
{
|
||||
public BaseDecorator() { }
|
||||
|
||||
public abstract boolean canDecorate(RoomData room);
|
||||
|
||||
public abstract void decorate(RoomData room, World world, Point3D offset, Random random, DDProperties properties);
|
||||
}
|
||||
@@ -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<BaseDecorator> 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<BaseDecorator> matches = new ArrayList<BaseDecorator>();
|
||||
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<BaseDecorator>();
|
||||
decorators.add(new LinkDestinationDecorator());
|
||||
decorators.add(new DefaultDoorDecorator());
|
||||
decorators.add(new TorchDecorator());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
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
|
||||
{
|
||||
@Override
|
||||
public boolean canDecorate(RoomData room)
|
||||
{
|
||||
return !room.getOutboundLinks().isEmpty();
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package StevenDimDoors.experimental.decorators;
|
||||
|
||||
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;
|
||||
|
||||
public class LinkDestinationDecorator extends BaseDecorator
|
||||
{
|
||||
@Override
|
||||
public boolean canDecorate(RoomData room)
|
||||
{
|
||||
return room.getOutboundLinks().isEmpty() && !room.getInboundLinks().isEmpty();
|
||||
}
|
||||
|
||||
@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<RoomData> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
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
|
||||
{
|
||||
@Override
|
||||
public boolean canDecorate(RoomData room)
|
||||
{
|
||||
return !room.isProtected();
|
||||
}
|
||||
|
||||
@Override
|
||||
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<RoomData> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -56,6 +56,20 @@ 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;
|
||||
}
|
||||
|
||||
public void add(Point3D other)
|
||||
{
|
||||
this.x += other.x;
|
||||
this.y += other.y;
|
||||
this.z += other.z;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Point3D clone()
|
||||
{
|
||||
|
||||
@@ -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, properties);
|
||||
|
||||
//Build the door
|
||||
int doorOrientation = BlockRotator.transformMetadata(BlockRotator.EAST_DOOR_METADATA, orientation - BlockRotator.EAST_DOOR_METADATA + 2, properties.DimensionalDoorID);
|
||||
|
||||
Reference in New Issue
Block a user