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.
This commit is contained in:
@@ -1,52 +1,47 @@
|
|||||||
package StevenDimDoors.experimental;
|
package StevenDimDoors.experimental;
|
||||||
|
|
||||||
|
import StevenDimDoors.mod_pocketDim.Point3D;
|
||||||
|
|
||||||
public class LinkPlan
|
public class LinkPlan
|
||||||
{
|
{
|
||||||
private RoomData source;
|
private RoomData source;
|
||||||
private RoomData destination;
|
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)
|
private LinkPlan(RoomData source, boolean entrance, boolean internal)
|
||||||
{
|
|
||||||
this.source = source;
|
|
||||||
this.destination = destination;
|
|
||||||
this.entrance = entrance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static LinkPlan createInternalLink(RoomData source, RoomData destination)
|
|
||||||
{
|
{
|
||||||
if (source == null)
|
if (source == null)
|
||||||
{
|
{
|
||||||
throw new IllegalArgumentException("source cannot be null.");
|
throw new IllegalArgumentException("source cannot be null.");
|
||||||
}
|
}
|
||||||
if (destination == null)
|
this.source = source;
|
||||||
{
|
this.destination = null;
|
||||||
throw new IllegalArgumentException("destination cannot be null.");
|
this.sourcePoint = null;
|
||||||
}
|
this.destinationPoint = null;
|
||||||
LinkPlan plan = new LinkPlan(source, destination, false);
|
this.entrance = entrance;
|
||||||
|
this.internal = internal;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LinkPlan createInternalLink(RoomData source)
|
||||||
|
{
|
||||||
|
LinkPlan plan = new LinkPlan(source, false, true);
|
||||||
source.getOutboundLinks().add(plan);
|
source.getOutboundLinks().add(plan);
|
||||||
destination.getInboundLinks().add(plan);
|
|
||||||
return plan;
|
return plan;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LinkPlan createEntranceLink(RoomData source)
|
public static LinkPlan createEntranceLink(RoomData source)
|
||||||
{
|
{
|
||||||
if (source == null)
|
LinkPlan plan = new LinkPlan(source, true, false);
|
||||||
{
|
|
||||||
throw new IllegalArgumentException("source cannot be null.");
|
|
||||||
}
|
|
||||||
LinkPlan plan = new LinkPlan(source, null, true);
|
|
||||||
source.getOutboundLinks().add(plan);
|
source.getOutboundLinks().add(plan);
|
||||||
return plan;
|
return plan;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LinkPlan createDungeonLink(RoomData source)
|
public static LinkPlan createDungeonLink(RoomData source)
|
||||||
{
|
{
|
||||||
if (source == null)
|
LinkPlan plan = new LinkPlan(source, false, false);
|
||||||
{
|
|
||||||
throw new IllegalArgumentException("source cannot be null.");
|
|
||||||
}
|
|
||||||
LinkPlan plan = new LinkPlan(source, null, false);
|
|
||||||
source.getOutboundLinks().add(plan);
|
source.getOutboundLinks().add(plan);
|
||||||
return plan;
|
return plan;
|
||||||
}
|
}
|
||||||
@@ -68,7 +63,7 @@ public class LinkPlan
|
|||||||
|
|
||||||
public boolean isInternal()
|
public boolean isInternal()
|
||||||
{
|
{
|
||||||
return (destination != null);
|
return internal;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void remove()
|
public void remove()
|
||||||
@@ -84,4 +79,42 @@ public class LinkPlan
|
|||||||
destination = null;
|
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,18 +1,23 @@
|
|||||||
package StevenDimDoors.experimental;
|
package StevenDimDoors.experimental;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
import java.util.Stack;
|
||||||
|
|
||||||
import net.minecraft.block.Block;
|
import net.minecraft.block.Block;
|
||||||
import net.minecraft.world.World;
|
import net.minecraft.world.World;
|
||||||
import net.minecraft.world.chunk.Chunk;
|
import net.minecraft.world.chunk.Chunk;
|
||||||
import net.minecraft.world.chunk.storage.ExtendedBlockStorage;
|
import net.minecraft.world.chunk.storage.ExtendedBlockStorage;
|
||||||
import StevenDimDoors.mod_pocketDim.Point3D;
|
import StevenDimDoors.mod_pocketDim.Point3D;
|
||||||
|
import StevenDimDoors.mod_pocketDim.config.DDProperties;
|
||||||
|
|
||||||
public class MazeBuilder
|
public class MazeBuilder
|
||||||
{
|
{
|
||||||
|
private static final int POCKET_WALL_GAP = 4;
|
||||||
|
|
||||||
private MazeBuilder() { }
|
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
|
// 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);
|
buildRooms(design.getLayout(), world, offset);
|
||||||
carveDoorways(design.getLayout(), world, offset, decay, random);
|
carveDoorways(design.getLayout(), world, offset, decay, random);
|
||||||
|
|
||||||
placeDoors(design.getLayout(), world, offset);
|
|
||||||
|
|
||||||
applyRandomDestruction(design, world, offset, decay, random);
|
applyRandomDestruction(design, world, offset, decay, random);
|
||||||
|
decorateRooms(design.getLayout(), world, offset);
|
||||||
|
buildPocketWalls(design, world, offset, properties);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void applyRandomDestruction(MazeDesign design, World world,
|
private static void applyRandomDestruction(MazeDesign design, World world,
|
||||||
Point3D offset, SphereDecayOperation decay, Random random)
|
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<RoomData> 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<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())
|
||||||
|
{
|
||||||
|
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)
|
private static void buildRooms(DirectedGraph<RoomData, DoorwayData> layout, World world, Point3D offset)
|
||||||
@@ -43,19 +117,25 @@ public class MazeBuilder
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void placeDoors(DirectedGraph<RoomData, DoorwayData> layout, World world, Point3D offset)
|
private static void decorateRooms(DirectedGraph<RoomData, DoorwayData> layout, World world, Point3D offset)
|
||||||
{
|
{
|
||||||
|
RoomData room;
|
||||||
|
PartitionNode<RoomData> partition;
|
||||||
|
ArrayList<LinkPlan> links = new ArrayList<LinkPlan>();
|
||||||
|
|
||||||
|
// Iterate over all rooms and apply decorators
|
||||||
for (IGraphNode<RoomData, DoorwayData> node : layout.nodes())
|
for (IGraphNode<RoomData, DoorwayData> node : layout.nodes())
|
||||||
{
|
{
|
||||||
RoomData room = node.data();
|
room = node.data();
|
||||||
Point3D minCorner = room.getPartitionNode().minCorner();
|
partition = room.getPartitionNode();
|
||||||
if (!room.getOutboundLinks().isEmpty())
|
links.addAll(room.getOutboundLinks());
|
||||||
{
|
|
||||||
setBlockDirectly(world, offset.getX() + minCorner.getX(), offset.getY() + minCorner.getY() + 1,
|
// TODO: Add decorator code here!
|
||||||
offset.getZ() + minCorner.getZ(), Block.glowStone.blockID, 0);
|
}
|
||||||
setBlockDirectly(world, offset.getX() + minCorner.getX(), offset.getY() + minCorner.getY() + 2,
|
// Iterate over all links plans and place links in the world
|
||||||
offset.getZ() + minCorner.getZ(), Block.glowStone.blockID, 0);
|
for (LinkPlan link : links)
|
||||||
}
|
{
|
||||||
|
// TODO: Add link placement code here!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,12 +150,19 @@ public class MazeBuilder
|
|||||||
{
|
{
|
||||||
for (IEdge<RoomData, DoorwayData> passage : node.outbound())
|
for (IEdge<RoomData, DoorwayData> passage : node.outbound())
|
||||||
{
|
{
|
||||||
|
// Carve out the passage
|
||||||
doorway = passage.data();
|
doorway = passage.data();
|
||||||
axis = doorway.axis();
|
axis = doorway.axis();
|
||||||
lower = doorway.minCorner();
|
lower = doorway.minCorner();
|
||||||
carveDoorway(world, axis, offset.getX() + lower.getX(), offset.getY() + lower.getY(),
|
carveDoorway(world, axis, offset.getX() + lower.getX(), offset.getY() + lower.getY(),
|
||||||
offset.getZ() + lower.getZ(), doorway.width(), doorway.height(), doorway.length(),
|
offset.getZ() + lower.getZ(), doorway.width(), doorway.height(), doorway.length(),
|
||||||
decay, random);
|
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;
|
final int MIN_DOUBLE_DOOR_SPAN = 10;
|
||||||
|
|
||||||
int gap;
|
int gap;
|
||||||
|
int rx;
|
||||||
|
int rz;
|
||||||
switch (axis)
|
switch (axis)
|
||||||
{
|
{
|
||||||
case DoorwayData.X_AXIS:
|
case DoorwayData.X_AXIS:
|
||||||
@@ -150,9 +239,10 @@ public class MazeBuilder
|
|||||||
{
|
{
|
||||||
gap = 6;
|
gap = 6;
|
||||||
}
|
}
|
||||||
decay.apply(world,
|
rx = x + random.nextInt(width - gap - 1) + 1;
|
||||||
x + random.nextInt(width - gap - 1) + 1, y - 1,
|
rz = z + random.nextInt(length - gap - 1) + 1;
|
||||||
z + random.nextInt(length - gap - 1) + 1, gap, 4, gap);
|
carveHole(world, rx + gap / 2, y, rz + gap / 2);
|
||||||
|
decay.apply(world, rx, y - 1, rz, gap, 4, gap);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -184,6 +274,19 @@ public class MazeBuilder
|
|||||||
setBlockDirectly(world, x, y + 1, z, 0, 0);
|
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)
|
private static void buildBox(World world, Point3D offset, Point3D minCorner, Point3D maxCorner, int blockID, int metadata)
|
||||||
{
|
{
|
||||||
int minX = minCorner.getX() + offset.getX();
|
int minX = minCorner.getX() + offset.getX();
|
||||||
|
|||||||
@@ -4,16 +4,16 @@ import java.util.ArrayList;
|
|||||||
|
|
||||||
public class MazeDesign
|
public class MazeDesign
|
||||||
{
|
{
|
||||||
private PartitionNode root;
|
private PartitionNode<RoomData> root;
|
||||||
private DirectedGraph<RoomData, DoorwayData> layout;
|
private DirectedGraph<RoomData, DoorwayData> layout;
|
||||||
|
|
||||||
public MazeDesign(PartitionNode root, DirectedGraph<RoomData, DoorwayData> layout)
|
public MazeDesign(PartitionNode<RoomData> root, DirectedGraph<RoomData, DoorwayData> layout)
|
||||||
{
|
{
|
||||||
this.root = root;
|
this.root = root;
|
||||||
this.layout = layout;
|
this.layout = layout;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PartitionNode getRootPartition()
|
public PartitionNode<RoomData> getRootPartition()
|
||||||
{
|
{
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -589,93 +589,180 @@ public class MazeDesigner
|
|||||||
// 3. Place internal links connecting the different sections of the maze
|
// 3. Place internal links connecting the different sections of the maze
|
||||||
// 4. Place more internal links to confuse people
|
// 4. Place more internal links to confuse people
|
||||||
|
|
||||||
// We need to start by counting the door capacity of each section and
|
// We need to start by building up data for each section, such as their
|
||||||
// listing which rooms can have doors or destinations for each section.
|
// door capacities and the rooms available for placing doors.
|
||||||
int index;
|
int index;
|
||||||
int[] capacity = new int[cores.size()];
|
int count;
|
||||||
ArrayList<RoomData>[] sourceRooms = (ArrayList<RoomData>[]) Array.newInstance(cores.getClass(), cores.size());
|
SectionData selection;
|
||||||
ArrayList<RoomData>[] destinationRooms = (ArrayList<RoomData>[]) Array.newInstance(cores.getClass(), cores.size());
|
SectionData destination;
|
||||||
|
ArrayList<SectionData> allSections;
|
||||||
|
ArrayList<SectionData> usableSections;
|
||||||
|
|
||||||
for (index = 0; index < sourceRooms.length; index++)
|
// Check if there is only one section. Our concerns differ depending
|
||||||
{
|
// on whether there is one or more than one.
|
||||||
sourceRooms[index] = new ArrayList<RoomData>();
|
if (cores.size() > 1)
|
||||||
destinationRooms[index] = new ArrayList<RoomData>();
|
{
|
||||||
capacity[index] = listLinkRooms(cores.get(index).getLayoutNode(), sourceRooms[index], destinationRooms[index]);
|
// More than 1 section
|
||||||
}
|
allSections = new ArrayList<SectionData>(cores.size());
|
||||||
|
for (RoomData core : cores)
|
||||||
// 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<RoomData, DoorwayData> core,
|
|
||||||
ArrayList<RoomData> sourceRooms, ArrayList<RoomData> destinationRooms)
|
|
||||||
{
|
|
||||||
int capacity = 0;
|
|
||||||
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();
|
allSections.add( SectionData.createFromCore(core.getLayoutNode()) );
|
||||||
if (visited.add(neighbor))
|
|
||||||
{
|
|
||||||
ordering.add(neighbor);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
for (IEdge<RoomData, DoorwayData> edge : current.inbound())
|
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)
|
||||||
{
|
{
|
||||||
neighbor = edge.head();
|
usableSections.remove(index);
|
||||||
if (visited.add(neighbor))
|
}
|
||||||
|
|
||||||
|
// 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);
|
usableSections.remove(index);
|
||||||
}
|
|
||||||
if (edge.data().axis() == DoorwayData.Y_AXIS)
|
|
||||||
{
|
|
||||||
hasHoles = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
if (section.capacity() > 0)
|
||||||
destinationRooms.add(currentRoom);
|
|
||||||
if (currentRoom.estimateDoorCapacity() > 0)
|
|
||||||
{
|
{
|
||||||
capacity += currentRoom.estimateDoorCapacity();
|
usableSections.add(section);
|
||||||
sourceRooms.add(currentRoom);
|
}
|
||||||
|
}
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return capacity;
|
|
||||||
}
|
// Done! At this point, all sections are connected.
|
||||||
|
|
||||||
private static void createEntranceLink(ArrayList<RoomData> sources, int index)
|
|
||||||
{
|
|
||||||
RoomData entranceRoom = sources.get(index);
|
|
||||||
LinkPlan.createEntranceLink(entranceRoom);
|
|
||||||
if (entranceRoom.getRemainingDoorCapacity() == 0)
|
|
||||||
{
|
|
||||||
sources.remove(index);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -55,6 +55,13 @@ public class Point3D implements Serializable {
|
|||||||
{
|
{
|
||||||
return this.z = z;
|
return this.z = z;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void add(int x, int y, int z)
|
||||||
|
{
|
||||||
|
this.x += x;
|
||||||
|
this.y += y;
|
||||||
|
this.z += z;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Point3D clone()
|
public Point3D clone()
|
||||||
|
|||||||
@@ -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
|
//Build the door
|
||||||
int doorOrientation = BlockRotator.transformMetadata(BlockRotator.EAST_DOOR_METADATA, orientation - BlockRotator.EAST_DOOR_METADATA + 2, properties.DimensionalDoorID);
|
int doorOrientation = BlockRotator.transformMetadata(BlockRotator.EAST_DOOR_METADATA, orientation - BlockRotator.EAST_DOOR_METADATA + 2, properties.DimensionalDoorID);
|
||||||
|
|||||||
Reference in New Issue
Block a user