diff --git a/StevenDimDoors/mod_pocketDim/SchematicLoader.java b/StevenDimDoors/mod_pocketDim/SchematicLoader.java
index f5006f4d..7f4101c3 100644
--- a/StevenDimDoors/mod_pocketDim/SchematicLoader.java
+++ b/StevenDimDoors/mod_pocketDim/SchematicLoader.java
@@ -6,6 +6,8 @@ import java.util.Random;
 
 import net.minecraft.world.World;
 import StevenDimDoors.mod_pocketDim.dungeon.DungeonSchematic;
+import StevenDimDoors.mod_pocketDim.dungeon.pack.DungeonPack;
+import StevenDimDoors.mod_pocketDim.dungeon.pack.DungeonPackConfig;
 import StevenDimDoors.mod_pocketDim.helpers.DungeonHelper;
 import StevenDimDoors.mod_pocketDim.helpers.dimHelper;
 import StevenDimDoors.mod_pocketDim.schematic.InvalidSchematicException;
@@ -16,8 +18,6 @@ public class SchematicLoader
 	
 	public static boolean generateDungeonPocket(LinkData link, DDProperties properties)
 	{
-		//TODO: Phase this function out in the next update. ~SenseiKiwi
-		
 		if (link == null || properties == null)
 		{
 			return false;
@@ -46,7 +46,7 @@ public class SchematicLoader
 					final long localSeed = world.getSeed() ^ 0x2F50DB9B4A8057E4L ^ computeDestinationHash(link);
 					final Random random = new Random(localSeed);
 					
-					dungeonHelper.generateDungeonLink(link, dungeonHelper.RuinsPack, random);
+					dungeonHelper.generateDungeonLink(link, getDimDungeonPack(originDimID), random);
 				}
 				schematicPath = dimList.get(destDimID).dungeonGenerator.schematicPath;	
 				
@@ -98,8 +98,11 @@ public class SchematicLoader
 			{
 				dimHelper helperInstance = dimHelper.instance;
 				helperInstance.moveLinkDataDestination(link, link.destXCoord, fixedY, link.destZCoord, link.destDimID, true);
-			}			
-			dungeon.copyToWorld(world, new Point3D(link.destXCoord, link.destYCoord, link.destZCoord), link.linkOrientation, originDimID, destDimID);
+			}
+			DungeonPackConfig packConfig = getDimDungeonPack(destDimID).getConfig();
+			
+			dungeon.copyToWorld(world, new Point3D(link.destXCoord, link.destYCoord, link.destZCoord),
+					link.linkOrientation, originDimID, destDimID, packConfig.doDistortDoorCoordinates());
 			return true;
 		}
 		catch (Exception e)
@@ -109,6 +112,30 @@ public class SchematicLoader
 		}
 	}
 	
+	private static DungeonPack getDimDungeonPack(int dimensionID)
+	{
+		//FIXME: This function is a workaround to our current dungeon data limitations. Modify later.
+		//The upcoming save format change and code overhaul will make this obsolete.
+		
+		DungeonPack pack;
+		DungeonGenerator generator = dimHelper.dimList.get(dimensionID).dungeonGenerator;
+		if (generator != null)
+		{
+			pack = generator.getDungeonType().Owner;
+			
+			//Make sure the pack isn't null. This can happen for dungeons with the special UNKNOWN type.
+			if (pack == null)
+			{
+				pack = DungeonHelper.instance().RuinsPack;
+			}
+		}
+		else
+		{
+			pack = DungeonHelper.instance().RuinsPack;
+		}
+		return pack;
+	}
+
 	private static int adjustDestinationY(World world, int y, DungeonSchematic dungeon)
 	{
 		//The goal here is to guarantee that the dungeon fits within the vertical bounds
@@ -141,7 +168,7 @@ public class SchematicLoader
 	
 	private static DungeonSchematic checkSourceAndLoad(String schematicPath) throws FileNotFoundException, InvalidSchematicException
 	{
-		//TODO: Change this code once we introduce an isInternal flag in dungeon data
+		//FIXME: Change this code once we introduce an isInternal flag in dungeon data
 		DungeonSchematic dungeon;
 		if ((new File(schematicPath)).exists())
 		{
diff --git a/StevenDimDoors/mod_pocketDim/commands/CommandExportDungeon.java b/StevenDimDoors/mod_pocketDim/commands/CommandExportDungeon.java
index 194d5ce1..f2ee7dab 100644
--- a/StevenDimDoors/mod_pocketDim/commands/CommandExportDungeon.java
+++ b/StevenDimDoors/mod_pocketDim/commands/CommandExportDungeon.java
@@ -105,7 +105,7 @@ public class CommandExportDungeon extends DDCommandBase
 				try
 				{
 					int weight = Integer.parseInt(command[3]);
-					if (weight >= 0 && weight <= DungeonHelper.MAX_DUNGEON_WEIGHT)
+					if (weight >= DungeonHelper.MIN_DUNGEON_WEIGHT && weight <= DungeonHelper.MAX_DUNGEON_WEIGHT)
 					{
 						return exportDungeon(sender, join(command, "_", 0, 4));
 					}
@@ -114,7 +114,8 @@ public class CommandExportDungeon extends DDCommandBase
 			}
 			
 			//If we've reached this point, then we must have an invalid weight.
-			return new DDCommandResult("Invalid dungeon weight. Please specify a weight between 0 and " + DungeonHelper.MAX_DUNGEON_WEIGHT + ", inclusive.");
+			return new DDCommandResult("Invalid dungeon weight. Please specify a weight between "
+					+ DungeonHelper.MIN_DUNGEON_WEIGHT + " and " + DungeonHelper.MAX_DUNGEON_WEIGHT + ", inclusive.");
 		}
 		
 		return DDCommandResult.SUCCESS;
diff --git a/StevenDimDoors/mod_pocketDim/dungeon/DungeonSchematic.java b/StevenDimDoors/mod_pocketDim/dungeon/DungeonSchematic.java
index 8792eaea..cae2fb49 100644
--- a/StevenDimDoors/mod_pocketDim/dungeon/DungeonSchematic.java
+++ b/StevenDimDoors/mod_pocketDim/dungeon/DungeonSchematic.java
@@ -167,7 +167,7 @@ public class DungeonSchematic extends Schematic {
 		return new DungeonSchematic(Schematic.copyFromWorld(world, x, y, z, width, height, length, doCompactBounds));
 	}
 
-	public void copyToWorld(World world, Point3D pocketCenter, int dungeonOrientation, int originDimID, int destDimID)
+	public void copyToWorld(World world, Point3D pocketCenter, int dungeonOrientation, int originDimID, int destDimID, boolean doDistortCoordinates)
 	{
 		//TODO: This function is an improvised solution so we can get the release moving. In the future,
 		//we should generalize block tranformations and implement support for them at the level of Schematic,
@@ -222,17 +222,17 @@ public class DungeonSchematic extends Schematic {
 			world.setBlockTileEntity(pocketPoint.getX(), pocketPoint.getY(), pocketPoint.getZ(), TileEntity.createAndLoadEntity(tileTag));
 		}
 		
-		setUpDungeon(world, pocketCenter, turnAngle, originDimID, destDimID);
+		setUpDungeon(world, pocketCenter, turnAngle, originDimID, destDimID, doDistortCoordinates);
 	}
 	
-	private void setUpDungeon(World world, Point3D pocketCenter, int turnAngle, int originDimID, int destDimID)
+	private void setUpDungeon(World world, Point3D pocketCenter, int turnAngle, int originDimID, int destDimID, boolean doDistortCoordinates)
 	{
 		//The following Random initialization code is based on code from ChunkProviderGenerate.
 		//It makes our generation depend on the world seed.
 		Random random = new Random(world.getSeed());
         long factorA = random.nextLong() / 2L * 2L + 1L;
         long factorB = random.nextLong() / 2L * 2L + 1L;
-        random.setSeed((pocketCenter.getX() >> 4) * factorA + (pocketCenter.getZ() >> 4) * factorB ^ world.getSeed());
+        random.setSeed(pocketCenter.getX() * factorB + pocketCenter.getZ() * factorA ^ world.getSeed());
 		
         //Transform dungeon corners
         Point3D minCorner = new Point3D(0, 0, 0);
@@ -249,7 +249,7 @@ public class DungeonSchematic extends Schematic {
 		//Set up link data for dimensional doors
 		for (Point3D location : dimensionalDoorLocations)
 		{
-			setUpDimensionalDoorLink(world, location, entranceDoorLocation, turnAngle, pocketCenter, originDimID, destDimID, random);
+			setUpDimensionalDoorLink(world, location, entranceDoorLocation, turnAngle, pocketCenter, originDimID, destDimID, doDistortCoordinates, random);
 		}
 		
 		//Set up link data for exit door
@@ -374,11 +374,22 @@ public class DungeonSchematic extends Schematic {
 		}
 	}
 	
-	private static void setUpDimensionalDoorLink(World world, Point3D point, Point3D entrance, int rotation, Point3D pocketCenter, int originDimID, int destDimID, Random random)
+	private static void setUpDimensionalDoorLink(World world, Point3D point, Point3D entrance, int rotation, Point3D pocketCenter, int originDimID, int destDimID, boolean applyNoise, Random random)
 	{
 		int depth = dimHelper.instance.getDimDepth(originDimID) + 1;
-		int forwardNoise = MathHelper.getRandomIntegerInRange(random, -50 * depth, 150 * depth);
-		int sidewaysNoise = MathHelper.getRandomIntegerInRange(random, -10 * depth, 10 * depth);
+		int forwardNoise;
+		int sidewaysNoise;
+		
+		if (applyNoise)
+		{
+			forwardNoise = MathHelper.getRandomIntegerInRange(random, -50 * depth, 150 * depth);
+			sidewaysNoise = MathHelper.getRandomIntegerInRange(random, -10 * depth, 10 * depth);
+		}
+		else
+		{
+			forwardNoise = 0;
+			sidewaysNoise = 0;
+		}
 		
 		//Transform doorLocation to the pocket coordinate system
 		Point3D location = point.clone();
diff --git a/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonChainRule.java b/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonChainRule.java
index 17554dcd..eacfa9c6 100644
--- a/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonChainRule.java
+++ b/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonChainRule.java
@@ -11,9 +11,32 @@ public class DungeonChainRule
 	private final ArrayList<WeightedContainer<DungeonType>> products;
 	
 	public DungeonChainRule(DungeonChainRuleDefinition source, HashMap<String, DungeonType> nameToTypeMapping)
-	{
+	{	
 		ArrayList<String> conditionNames = source.getCondition();
 		ArrayList<WeightedContainer<String>> productNames = source.getProducts();
+		
+		//Validate the data, just in case
+		if (conditionNames == null)
+		{
+			throw new NullPointerException("source cannot have null conditions");
+		}
+		if (productNames == null)
+		{
+			throw new NullPointerException("source cannot have null products");
+		}
+		if (productNames.isEmpty())
+		{
+			throw new IllegalArgumentException("products cannot be an empty list");
+		}
+		for (WeightedContainer<String> product : productNames)
+		{
+			//Check for weights less than 1. Those could cause Minecraft's random selection algorithm to throw an exception.
+			//At the very least, they're useless values.
+			if (product.itemWeight < 1)
+			{
+				throw new IllegalArgumentException("products cannot contain items with weights less than 1");
+			}
+		}
 
 		//Obtain the IDs of dungeon types in reverse order. Reverse order makes comparing against chain histories easy.
 		condition = new int[conditionNames.size()];
diff --git a/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonChainRuleDefinition.java b/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonChainRuleDefinition.java
index f06cdedc..17011e6d 100644
--- a/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonChainRuleDefinition.java
+++ b/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonChainRuleDefinition.java
@@ -11,6 +11,25 @@ public class DungeonChainRuleDefinition
 	
 	public DungeonChainRuleDefinition(ArrayList<String> conditions, ArrayList<WeightedContainer<String>> products)
 	{
+		//Validate the arguments, just in case
+		if (conditions == null)
+		{
+			throw new NullPointerException("conditions cannot be null");
+		}
+		if (products.isEmpty())
+		{
+			throw new IllegalArgumentException("products cannot be an empty list");
+		}
+		for (WeightedContainer<String> product : products)
+		{
+			//Check for weights less than 1. Those could cause Minecraft's random selection algorithm to throw an exception.
+			//At the very least, they're useless values.
+			if (product.itemWeight < 1)
+			{
+				throw new IllegalArgumentException("products cannot contain items with weights less than 1");
+			}
+		}
+		
 		this.conditions = conditions;
 		this.products = products;
 	}
diff --git a/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonPack.java b/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonPack.java
index 40bc2c77..f9439f27 100644
--- a/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonPack.java
+++ b/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonPack.java
@@ -21,6 +21,8 @@ public class DungeonPack
 	//The ID numbers would be a problem since it couldn't have a valid number, since it wasn't initialized by the pack instance.
 	//FIXME: Do not release this code as an update without dealing with disowned types!
 	
+	private static final int MAX_HISTORY_LENGTH = 1337;
+	
 	private final String name;
 	private final HashMap<String, DungeonType> nameToTypeMapping;
 	private final ArrayList<ArrayList<DungeonGenerator>> groupedDungeons;
@@ -78,6 +80,11 @@ public class DungeonPack
 		return name;
 	}
 
+	public DungeonPackConfig getConfig()
+	{
+		return config.clone();
+	}
+
 	public boolean isEmpty()
 	{
 		return allDungeons.isEmpty();
@@ -128,7 +135,7 @@ public class DungeonPack
 		//of the longest rule we have. Getting any more data would be useless. This optimization could be significant
 		//for dungeon packs that can extend arbitrarily deep. We should probably set a reasonable limit anyway.
 		
-		int maxSearchLength = config.allowDuplicatesInChain() ? maxRuleLength : 1337;
+		int maxSearchLength = config.allowDuplicatesInChain() ? maxRuleLength : MAX_HISTORY_LENGTH;
 		ArrayList<DungeonGenerator> history = DungeonHelper.getDungeonChainHistory(
 				dimHelper.instance.getDimData(inbound.locDimID), this, maxSearchLength);
 		return getNextDungeon(history, random);
diff --git a/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonPackConfig.java b/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonPackConfig.java
index 842c94ce..00745450 100644
--- a/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonPackConfig.java
+++ b/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonPackConfig.java
@@ -18,14 +18,14 @@ public class DungeonPackConfig
 	@SuppressWarnings("unchecked")
 	private DungeonPackConfig(DungeonPackConfig source)
 	{
-		this.name = source.name;
-		this.typeNames = (ArrayList<String>) source.typeNames.clone();
+		this.name = (source.name != null) ? source.name : null;
+		this.typeNames = (source.typeNames != null) ? (ArrayList<String>) source.typeNames.clone() : null;
 		this.allowDuplicatesInChain = source.allowDuplicatesInChain;
 		this.allowPackChangeIn = source.allowPackChangeIn;
 		this.allowPackChangeOut = source.allowPackChangeOut;
 		this.distortDoorCoordinates = source.distortDoorCoordinates;
 		this.packWeight = source.packWeight;
-		this.rules = (ArrayList<DungeonChainRuleDefinition>) source.rules.clone();
+		this.rules = (source.rules != null) ? (ArrayList<DungeonChainRuleDefinition>) source.rules.clone() : null;
 	}
 	
 	public void validate()
@@ -114,7 +114,7 @@ public class DungeonPackConfig
 		this.packWeight = packWeight;
 	}
 	
-	public boolean getDistortDoorCoordinates()
+	public boolean doDistortDoorCoordinates()
 	{
 		return distortDoorCoordinates;
 	}
diff --git a/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonPackConfigReader.java b/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonPackConfigReader.java
index 8aac9674..a4aae616 100644
--- a/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonPackConfigReader.java
+++ b/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonPackConfigReader.java
@@ -31,9 +31,13 @@ public class DungeonPackConfigReader extends BaseConfigurationProcessor<DungeonP
 	private final int CONFIG_VERSION = 1;
 	private final int LOOKAHEAD_LIMIT = 1024;
 	private final int MAX_PRODUCT_WEIGHT = 10000;
+	private final int MIN_PRODUCT_WEIGHT = 1;
 	private final int DEFAULT_PRODUCT_WEIGHT = 100;
 	private final int MAX_DUNGEON_PACK_WEIGHT = 10000;
+	private final int MIN_DUNGEON_PACK_WEIGHT = 1;
 	private final int DEFAULT_DUNGEON_PACK_WEIGHT = 100;
+	private final int MAX_CONDITION_LENGTH = 20;
+	private final int MAX_PRODUCT_COUNT = MAX_CONDITION_LENGTH;
 	private final String COMMENT_MARKER = "##";
 
 	private final Pattern DUNGEON_TYPE_PATTERN = Pattern.compile("[A-Za-z0-9_\\-]{1,20}");
@@ -237,8 +241,8 @@ public class DungeonPackConfigReader extends BaseConfigurationProcessor<DungeonP
 			{
 				try
 				{
-					String name = settingParts[0];
-					String value = settingParts[1];
+					String name = settingParts[0].trim();
+					String value = settingParts[1].trim();
 					if (name.equalsIgnoreCase("AllowDuplicatesInChain"))
 					{
 						config.setAllowDuplicatesInChain(parseBoolean(value));
@@ -258,7 +262,7 @@ public class DungeonPackConfigReader extends BaseConfigurationProcessor<DungeonP
 					else if (name.equalsIgnoreCase("PackWeight"))
 					{
 						int weight = Integer.parseInt(value);
-						if (weight >= 0 && weight <= MAX_DUNGEON_PACK_WEIGHT)
+						if (weight >= MIN_DUNGEON_PACK_WEIGHT && weight <= MAX_DUNGEON_PACK_WEIGHT)
 						{
 							config.setPackWeight(weight);
 						}
@@ -267,6 +271,10 @@ public class DungeonPackConfigReader extends BaseConfigurationProcessor<DungeonP
 							valid = false;
 						}
 					}
+					else
+					{
+						valid = false;
+					}
 				}
 				catch (Exception e)
 				{
@@ -318,6 +326,11 @@ public class DungeonPackConfigReader extends BaseConfigurationProcessor<DungeonP
 				{
 					throw new ConfigurationProcessingException("The dungeon pack config has a rule condition with an unknown dungeon type: " + typeName);
 				}
+
+				if (condition.size() > MAX_CONDITION_LENGTH)
+				{
+					throw new ConfigurationProcessingException("The dungeon pack config has a rule condition that is too long: " + definition);
+				}
 			}
 
 			for (String product : WHITESPACE_SPLITTER.split(ruleProduct))
@@ -337,7 +350,7 @@ public class DungeonPackConfigReader extends BaseConfigurationProcessor<DungeonP
 					if (productParts.length > 1)
 					{
 						weight = Ints.tryParse(productParts[1]);
-						if (weight == null  || (weight > MAX_PRODUCT_WEIGHT) || (weight < 0))
+						if (weight == null  || (weight > MAX_PRODUCT_WEIGHT) || (weight < MIN_PRODUCT_WEIGHT))
 						{
 							throw new ConfigurationProcessingException("The dungeon pack config has a rule with an invalid product weight: " + product);
 						}
@@ -352,7 +365,17 @@ public class DungeonPackConfigReader extends BaseConfigurationProcessor<DungeonP
 				{
 					throw new ConfigurationProcessingException("The dungeon pack config has an unknown dungeon type in a rule: " + typeName);
 				}
+
+				if (products.size() > MAX_PRODUCT_COUNT)
+				{
+					throw new ConfigurationProcessingException("The dungeon pack config has a rule with too many products: " + definition);
+				}
 			}
+			if (products.isEmpty())
+			{
+				throw new ConfigurationProcessingException("The dungeon pack config has a rule with no products: " + definition);
+			}
+			
 			config.getRules().add( new DungeonChainRuleDefinition(condition, products) );
 		}
 	}
diff --git a/StevenDimDoors/mod_pocketDim/helpers/DungeonHelper.java b/StevenDimDoors/mod_pocketDim/helpers/DungeonHelper.java
index 4a4b9a49..81e06e40 100644
--- a/StevenDimDoors/mod_pocketDim/helpers/DungeonHelper.java
+++ b/StevenDimDoors/mod_pocketDim/helpers/DungeonHelper.java
@@ -2,11 +2,13 @@ package StevenDimDoors.mod_pocketDim.helpers;
 
 import java.io.BufferedReader;
 import java.io.File;
+import java.io.FileNotFoundException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
@@ -14,6 +16,7 @@ import java.util.Queue;
 import java.util.Random;
 import java.util.regex.Pattern;
 
+import net.minecraft.util.WeightedRandom;
 import net.minecraft.world.World;
 import StevenDimDoors.mod_pocketDim.DDProperties;
 import StevenDimDoors.mod_pocketDim.DimData;
@@ -27,6 +30,7 @@ import StevenDimDoors.mod_pocketDim.dungeon.pack.DungeonPackConfigReader;
 import StevenDimDoors.mod_pocketDim.dungeon.pack.DungeonType;
 import StevenDimDoors.mod_pocketDim.items.itemDimDoor;
 import StevenDimDoors.mod_pocketDim.util.ConfigurationProcessingException;
+import StevenDimDoors.mod_pocketDim.util.WeightedContainer;
 
 public class DungeonHelper
 {
@@ -42,8 +46,18 @@ public class DungeonHelper
 	private static final String BUNDLED_DUNGEONS_LIST_PATH = "/schematics/schematics.txt";
 	private static final String DUNGEON_CREATION_GUIDE_SOURCE_PATH = "/mods/DimDoors/text/How_to_add_dungeons.txt";
 
+	private static final String RUINS_PACK_PATH = "/schematics/ruins/rules.txt";
+	
 	public static final String SCHEMATIC_FILE_EXTENSION = ".schematic";
+	
+	
+	private static final int MIN_PACK_SWITCH_CHANCE = 0;
+	private static final int PACK_SWITCH_CHANCE_PER_LEVEL = 1;
+	private static final int MAX_PACK_SWITCH_CHANCE = 500;
+	private static final int START_PACK_SWITCH_CHANCE = MAX_PACK_SWITCH_CHANCE / 9;
+	
 	private static final int DEFAULT_DUNGEON_WEIGHT = 100;
+	public static final int MIN_DUNGEON_WEIGHT = 1; //Prevents MC's random selection algorithm from throwing an exception
 	public static final int MAX_DUNGEON_WEIGHT = 10000; //Used to prevent overflows and math breaking down
 	private static final int MAX_EXPORT_RADIUS = 50;
 	public static final short MAX_DUNGEON_WIDTH = 2 * MAX_EXPORT_RADIUS + 1;
@@ -54,6 +68,8 @@ public class DungeonHelper
 	private ArrayList<DungeonGenerator> registeredDungeons = new ArrayList<DungeonGenerator>();
  
 	public DungeonPack RuinsPack;
+	private HashMap<String, DungeonPack> dungeonPackMapping = new HashMap<String, DungeonPack>();
+	private ArrayList<DungeonPack> dungeonPackList = new ArrayList<DungeonPack>();
 	
 	private DungeonGenerator defaultUp;
 	private DungeonGenerator defaultDown;
@@ -101,31 +117,45 @@ public class DungeonHelper
 			copyfile.copyFile(DUNGEON_CREATION_GUIDE_SOURCE_PATH, file.getAbsolutePath() + "/How_to_add_dungeons.txt");
 		}
 		
-		RuinsPack = new DungeonPack(createRuinsConfig());
+		//TODO: Write up a dungeon pack loading function that loads the whole pack and infers its name from the path
+		DungeonPackConfigReader reader = new DungeonPackConfigReader();
+		RuinsPack = new DungeonPack(loadDungeonPackConfig(reader, RUINS_PACK_PATH, "ruins", true));
+		dungeonPackMapping.put("ruins", RuinsPack);
+		dungeonPackList.add(RuinsPack);
 		
 		registerBundledDungeons();
 		registerCustomDungeons(properties.CustomSchematicDirectory);
 	}
 	
-	private static DungeonPackConfig createRuinsConfig()
+	private static DungeonPackConfig loadDungeonPackConfig(DungeonPackConfigReader reader, String configPath, String name, boolean isInternal)
 	{
-		//This is a temporarily function for testing dungeon packs.
-		//It'll be removed later when we read dungeon configurations from files.
-				
-		DungeonPackConfig config;
 		try
 		{
-			config = (new DungeonPackConfigReader()).readFromResource("/schematics/ruins/rules.txt");
-			config.setName("ruins");
+			DungeonPackConfig config;
+			if (isInternal)
+			{
+				config = reader.readFromResource(configPath);
+			}
+			else
+			{
+				config = reader.readFromFile(configPath);
+			}
+			config.setName(name);
 			return config;
 		}
 		catch (ConfigurationProcessingException e)
 		{
-			//FIXME TEMPORARY DEBUG PRINT, DO SOMETHING BETTER HERE
-			System.err.println("OH GOD SOMETHING WENT WRONG WITH THE DEFAULT DUNGEON PACK CONFIG");
-			e.printStackTrace();
-			return null;
+			System.err.println(e.getMessage());
+			if (e.getCause() != null)
+			{
+				System.err.println(e.getCause());
+			}
 		}
+		catch (FileNotFoundException e)
+		{
+			System.err.println("Could not find a dungeon pack config file: " + configPath);
+		}
+		return null;
 	}
 	
 	public List<DungeonGenerator> getRegisteredDungeons()
@@ -202,7 +232,7 @@ public class DungeonHelper
 			try
 			{
 				int weight = Integer.parseInt(dungeonData[3]);
-				if (weight < 0 || weight > MAX_DUNGEON_WEIGHT)
+				if (weight < MIN_DUNGEON_WEIGHT || weight > MAX_DUNGEON_WEIGHT)
 					return false;
 			}
 			catch (NumberFormatException e)
@@ -336,10 +366,36 @@ public class DungeonHelper
 	public void generateDungeonLink(LinkData inbound, DungeonPack pack, Random random)
 	{
 		DungeonGenerator selection;
+		DungeonPackConfig config;
+		DungeonPack selectedPack;
 		
 		try
-		{			
-			selection = pack.getNextDungeon(inbound, random);
+		{
+			config = pack.getConfig();
+			selectedPack = pack;
+			
+			//Do we want to switch to another dungeon pack?
+			if (config.allowPackChangeOut())
+			{
+				//Calculate the chance of switching to a different pack type
+				int packSwitchChance;
+				if (dimHelper.dimList.get(inbound.locDimID).depth == 0)
+				{
+					packSwitchChance = START_PACK_SWITCH_CHANCE;
+				}
+				else
+				{
+					packSwitchChance = MIN_PACK_SWITCH_CHANCE + (getPackDepth(inbound, pack) - 1) * PACK_SWITCH_CHANCE_PER_LEVEL;
+				}
+
+				//Decide randomly whether to switch packs or not
+				if (random.nextInt(MAX_PACK_SWITCH_CHANCE) < packSwitchChance)
+				{
+					//Find another pack
+					selectedPack = getRandomDungeonPack(pack, random);
+				}
+			}
+			selection = selectedPack.getNextDungeon(inbound, random);
 		}
 		catch (Exception e)
 		{
@@ -358,6 +414,29 @@ public class DungeonHelper
 		dimHelper.instance.getDimData(inbound.destDimID).dungeonGenerator = selection;
 	}
 
+	@SuppressWarnings("unchecked")
+	private DungeonPack getRandomDungeonPack(DungeonPack current, Random random)
+	{
+		DungeonPack selection = current;
+		ArrayList<WeightedContainer<DungeonPack>> packs = new ArrayList<WeightedContainer<DungeonPack>>(dungeonPackList.size());
+
+		//Load up a list of weighted items with any usable dungeon packs that is not the current pack
+		for (DungeonPack pack : dungeonPackList)
+		{
+			DungeonPackConfig config = pack.getConfig();
+			if (pack != current && config.allowPackChangeIn() && !pack.isEmpty())
+			{
+				packs.add(new WeightedContainer<DungeonPack>(pack, config.getPackWeight()));
+			}
+		}
+		if (!packs.isEmpty())
+		{
+			//Pick a random dungeon pack
+			selection = ((WeightedContainer<DungeonPack>) WeightedRandom.getRandomItem(random, packs)).getData();
+		}
+		return selection;
+	}
+
 	public Collection<String> getDungeonNames() {
 
 		//Use a HashSet to guarantee that all dungeon names will be distinct.
@@ -426,6 +505,38 @@ public class DungeonHelper
 		return history;
 	}
 	
+	private static int getPackDepth(LinkData inbound, DungeonPack pack)
+	{
+		//TODO: I've improved this code for the time being. However, searching across links is a flawed approach. A player could
+		//manipulate the output of this function by setting up links to mislead our algorithm or by removing links.
+		//Dimensions MUST have built-in records of their parent dimensions in the future. ~SenseiKiwi
+		//Dimensions should also just keep track of pack depth internally.
+		
+		int packDepth = 1;
+		DimData tailDim = dimHelper.dimList.get(inbound.destDimID);
+		boolean found;
+		
+		do
+		{
+			found = false;
+			for (LinkData link : tailDim.getLinksInDim())
+			{
+				DimData neighbor = dimHelper.instance.getDimData(link.destDimID);
+				if (neighbor.depth == tailDim.depth - 1 && neighbor.dungeonGenerator != null &&
+						neighbor.dungeonGenerator.getDungeonType().Owner == pack)
+				{
+					tailDim = neighbor;
+					found = true;
+					packDepth++;
+					break;
+				}
+			}
+		}
+		while (found);
+		
+		return packDepth;
+	}
+	
 	public static ArrayList<DungeonGenerator> getFlatDungeonTree(DimData dimData, int maxSize)
 	{
 		//TODO: I've improved this code for the time being. However, searching across links is a flawed approach. A player could