diff --git a/StevenDimDoors/mod_pocketDim/DungeonGenerator.java b/StevenDimDoors/mod_pocketDim/DungeonGenerator.java index 6c6ea160..0500e76e 100644 --- a/StevenDimDoors/mod_pocketDim/DungeonGenerator.java +++ b/StevenDimDoors/mod_pocketDim/DungeonGenerator.java @@ -4,13 +4,11 @@ import java.io.File; import java.io.Serializable; import java.util.ArrayList; import java.util.HashMap; -import java.util.Random; +import StevenDimDoors.mod_pocketDim.dungeon.pack.DungeonPack; import StevenDimDoors.mod_pocketDim.dungeon.pack.DungeonType; import StevenDimDoors.mod_pocketDim.helpers.DungeonHelper; -import net.minecraft.world.World; - public class DungeonGenerator implements Serializable { //This static field is hax so that I don't have to add an instance field to DungeonGenerator to support DungeonType. @@ -48,7 +46,13 @@ public class DungeonGenerator implements Serializable { File file = new File(schematicPath); String typeName = file.getName().split("_")[0]; - type = DungeonHelper.instance().RuinsPack.getType(typeName); + String packName = file.getParentFile().getName(); + DungeonPack pack = DungeonHelper.instance().getDungeonPack(packName); + if (pack == null) + { + pack = DungeonHelper.instance().getDungeonPack("ruins"); + } + type = pack.getType(typeName); } catch (Exception e) { } if (type == null) diff --git a/StevenDimDoors/mod_pocketDim/SchematicLoader.java b/StevenDimDoors/mod_pocketDim/SchematicLoader.java index f5006f4d..630b21ff 100644 --- a/StevenDimDoors/mod_pocketDim/SchematicLoader.java +++ b/StevenDimDoors/mod_pocketDim/SchematicLoader.java @@ -6,6 +6,7 @@ import java.util.Random; import net.minecraft.world.World; import StevenDimDoors.mod_pocketDim.dungeon.DungeonSchematic; +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 +17,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,10 +45,9 @@ 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, dungeonHelper.getDimDungeonPack(originDimID), random); } - schematicPath = dimList.get(destDimID).dungeonGenerator.schematicPath; - + schematicPath = dimList.get(destDimID).dungeonGenerator.schematicPath; } else { @@ -98,8 +96,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 = dungeonHelper.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) @@ -108,7 +109,7 @@ public class SchematicLoader return false; } } - + 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 +142,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..8968d11c 100644 --- a/StevenDimDoors/mod_pocketDim/commands/CommandExportDungeon.java +++ b/StevenDimDoors/mod_pocketDim/commands/CommandExportDungeon.java @@ -81,7 +81,7 @@ public class CommandExportDungeon extends DDCommandBase //TODO: This validation should be in DungeonHelper or in another class. We should move it //during the save file format rewrite. ~SenseiKiwi - if (!dungeonHelper.validateDungeonType(command[0])) + if (!dungeonHelper.validateDungeonType(command[0], dungeonHelper.getDungeonPack("ruins"))) { return new DDCommandResult("Error: Invalid dungeon type. Please use one of the existing types."); } @@ -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; @@ -132,7 +133,7 @@ public class CommandExportDungeon extends DDCommandBase if (dungeonHelper.exportDungeon(player.worldObj, x, y, z, exportPath)) { player.sendChatToPlayer("Saved dungeon schematic in " + exportPath); - dungeonHelper.registerDungeon(exportPath, false, true); + dungeonHelper.registerDungeon(exportPath, dungeonHelper.getDungeonPack("ruins"), false, true); return DDCommandResult.SUCCESS; } else 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> products; public DungeonChainRule(DungeonChainRuleDefinition source, HashMap nameToTypeMapping) - { + { ArrayList conditionNames = source.getCondition(); ArrayList> 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 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 conditions, ArrayList> 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 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 181425e1..3e82c28f 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 nameToTypeMapping; private final ArrayList> 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 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) source.typeNames.clone(); + this.name = (source.name != null) ? source.name : null; + this.typeNames = (source.typeNames != null) ? (ArrayList) 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) source.rules.clone(); + this.rules = (source.rules != null) ? (ArrayList) 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= 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 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 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 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 0050a9f0..71516240 100644 --- a/StevenDimDoors/mod_pocketDim/helpers/DungeonHelper.java +++ b/StevenDimDoors/mod_pocketDim/helpers/DungeonHelper.java @@ -2,11 +2,14 @@ package StevenDimDoors.mod_pocketDim.helpers; import java.io.BufferedReader; import java.io.File; +import java.io.FileFilter; +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 +17,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,24 +31,56 @@ 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 { + //TODO: File-handling functionality should be spun off to a helper class later + private static class DirectoryFilter implements FileFilter + { + @Override + public boolean accept(File file) + { + return file.isDirectory(); + } + } + + private static class SchematicFileFilter implements FileFilter + { + @Override + public boolean accept(File file) + { + return file.isFile() && file.getName().endsWith(SCHEMATIC_FILE_EXTENSION); + } + } + private static DungeonHelper instance = null; private static DDProperties properties = null; public static final Pattern SCHEMATIC_NAME_PATTERN = Pattern.compile("[A-Za-z0-9_\\-]+"); public static final Pattern DUNGEON_NAME_PATTERN = Pattern.compile("[A-Za-z0-9\\-]+"); + public static final String SCHEMATIC_FILE_EXTENSION = ".schematic"; + private static final String DEFAULT_UP_SCHEMATIC_PATH = "/schematics/core/simpleStairsUp.schematic"; private static final String DEFAULT_DOWN_SCHEMATIC_PATH = "/schematics/core/simpleStairsDown.schematic"; private static final String DEFAULT_ERROR_SCHEMATIC_PATH = "/schematics/core/somethingBroke.schematic"; - 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"; - - public static final String SCHEMATIC_FILE_EXTENSION = ".schematic"; + private static final String RUINS_PACK_PATH = "/schematics/ruins"; + private static final String BUNDLED_RUINS_LIST_PATH = "/schematics/ruins.txt"; + private static final String STANDARD_CONFIG_FILE_NAME = "rules.txt"; + + private static final int NETHER_DIMENSION_ID = -1; + + 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; public static final short MAX_DUNGEON_HEIGHT = MAX_DUNGEON_WIDTH; @@ -53,7 +89,9 @@ public class DungeonHelper private ArrayList untaggedDungeons = new ArrayList(); private ArrayList registeredDungeons = new ArrayList(); - public DungeonPack RuinsPack; + private DungeonPack RuinsPack; + private HashMap dungeonPackMapping = new HashMap(); + private ArrayList dungeonPackList = new ArrayList(); private DungeonGenerator defaultUp; private DungeonGenerator defaultDown; @@ -101,30 +139,82 @@ public class DungeonHelper copyfile.copyFile(DUNGEON_CREATION_GUIDE_SOURCE_PATH, file.getAbsolutePath() + "/How_to_add_dungeons.txt"); } - RuinsPack = new DungeonPack(createRuinsConfig()); - - registerBundledDungeons(); - registerCustomDungeons(properties.CustomSchematicDirectory); + DungeonPackConfigReader reader = new DungeonPackConfigReader(); + registerBundledDungeons(reader); + registerCustomDungeons(properties.CustomSchematicDirectory, reader); } - private static DungeonPackConfig createRuinsConfig() + private static DungeonPackConfig loadDungeonPackConfig(String configPath, String name, boolean isInternal, DungeonPackConfigReader reader) { - //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; + } + + private void registerDungeonPack(String directory, Iterable schematics, boolean isInternal, boolean verbose, DungeonPackConfigReader reader) + { + //First determine the pack's name and validate it + File packDirectory = new File(directory); + String name = packDirectory.getName().toUpperCase(); + //TODO: ADD VALIDATION HERE? + + //Check for naming conflicts + //That could happen if a user has a custom pack with a name that conflicts with a bundled pack, + //or if a user is running Linux and has two directories with names differing only by capitalization. + + DungeonPack pack = dungeonPackMapping.get(name); + if (pack == null) + { + //Load the pack's configuration file + String configPath = directory + File.separator + STANDARD_CONFIG_FILE_NAME; + DungeonPackConfig config = loadDungeonPackConfig(configPath, name, isInternal, reader); + if (config == null) + { + System.err.println("Could not load config file: " + configPath); + return; + } + + //Register the pack + pack = new DungeonPack(config); + dungeonPackMapping.put(name, pack); + dungeonPackList.add(pack); + } + else + { + //Show a warning that there is a naming conflict but keep going. People can use this to extend + //our built-in packs with custom schematics without tampering with our mod's JAR file. + System.err.println("A dungeon pack has the same name as another pack that has already been loaded: " + directory); + System.err.println("We will try to load its schematics but will not check its config file."); + } + + //Register the dungeons! ^_^ + for (String schematicPath : schematics) + { + registerDungeon(schematicPath, pack, isInternal, verbose); } } @@ -153,6 +243,44 @@ public class DungeonHelper return defaultDown; } + public DungeonPack getDungeonPack(String name) + { + //TODO: This function might be obsolete after the new save format is implemented. + return dungeonPackMapping.get(name.toUpperCase()); + } + + public 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 = RuinsPack; + } + } + else + { + if (dimensionID == NETHER_DIMENSION_ID) + { + //TODO: Change this to the nether-side pack later ^_^ + pack = RuinsPack; + } + else + { + pack = RuinsPack; + } + } + return pack; + } + public LinkData createCustomDungeonDoor(World world, int x, int y, int z) { //Create a link above the specified position. Link to a new pocket dimension. @@ -165,13 +293,13 @@ public class DungeonHelper return link; } - public boolean validateDungeonType(String type) + public boolean validateDungeonType(String type, DungeonPack pack) { //Check if the dungeon type is valid - return RuinsPack.isKnownType(type); + return pack.isKnownType(type); } - public boolean validateSchematicName(String name) + public boolean validateSchematicName(String name, DungeonPack pack) { String[] dungeonData; @@ -185,7 +313,7 @@ public class DungeonHelper return false; //Check if the dungeon type is valid - if (!validateDungeonType(dungeonData[0])) + if (!validateDungeonType(dungeonData[0], pack)) return false; //Check if the name is valid @@ -202,7 +330,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) @@ -214,7 +342,7 @@ public class DungeonHelper return true; } - public void registerDungeon(String schematicPath, boolean isInternal, boolean verbose) + public void registerDungeon(String schematicPath, DungeonPack pack, boolean isInternal, boolean verbose) { //We use schematicPath as the real path for internal files (inside our JAR) because it seems //that File tries to interpret it as a local drive path and mangles it. @@ -223,19 +351,19 @@ public class DungeonHelper String path = isInternal ? schematicPath : schematicFile.getAbsolutePath(); try { - if (validateSchematicName(name)) + if (validateSchematicName(name, pack)) { //Strip off the file extension while splitting the file name String[] dungeonData = name.substring(0, name.length() - SCHEMATIC_FILE_EXTENSION.length()).split("_"); - DungeonType dungeonType = RuinsPack.getType(dungeonData[0]); + DungeonType dungeonType = pack.getType(dungeonData[0]); boolean isOpen = dungeonData[2].equalsIgnoreCase("open"); int weight = (dungeonData.length == 4) ? Integer.parseInt(dungeonData[3]) : DEFAULT_DUNGEON_WEIGHT; //Add this custom dungeon to the list corresponding to its type DungeonGenerator generator = new DungeonGenerator(weight, path, isOpen, dungeonType); - RuinsPack.addDungeon(generator); + pack.addDungeon(generator); registeredDungeons.add(generator); if (verbose) { @@ -246,37 +374,82 @@ public class DungeonHelper { if (verbose) { - System.out.println("Could not parse dungeon filename, not adding dungeon to generation lists"); + System.out.println("The following dungeon name is invalid for its given pack. It will not be generated naturally: " + schematicPath); } untaggedDungeons.add(new DungeonGenerator(DEFAULT_DUNGEON_WEIGHT, path, true, DungeonType.UNKNOWN_TYPE)); System.out.println("Registered untagged dungeon: " + name); } } - catch(Exception e) + catch (Exception e) { System.err.println("Failed to register dungeon: " + name); e.printStackTrace(); } } - private void registerCustomDungeons(String path) + private void registerCustomDungeons(String path, DungeonPackConfigReader reader) { + File[] schematics; + File[] packDirectories; + File[] packFiles; + ArrayList packFilePaths; File directory = new File(path); - File[] schematicNames = directory.listFiles(); + SchematicFileFilter schematicFileFilter = new SchematicFileFilter(); - if (schematicNames != null) + //Check that the Ruins pack has been loaded + if (RuinsPack == null) { - for (File schematicFile: schematicNames) + throw new IllegalStateException("Cannot register custom dungeons without first loading the Ruins dungeon pack."); + } + + //Load stray dungeons directly in the custom dungeons folder + schematics = directory.listFiles(schematicFileFilter); + if (schematics != null) + { + for (File schematicFile : schematics) { - if (schematicFile.getName().endsWith(SCHEMATIC_FILE_EXTENSION)) + registerDungeon(schematicFile.getPath(), RuinsPack, false, true); + } + } + else + { + System.err.println("Could not retrieve the list of schematics stored in the custom dungeons directory!"); + } + schematics = null; //Release memory + + //Load the custom dungeon packs + packDirectories = directory.listFiles(new DirectoryFilter()); + if (packDirectories != null) + { + //Loop through each directory, which is assumed to be a dungeon pack + for (File packDirectory : packDirectories) + { + //List the schematics within the dungeon pack directory + packFiles = packDirectory.listFiles(schematicFileFilter); + if (packFiles != null) { - registerDungeon(schematicFile.getPath(), false, true); + //Copy the pack files' paths into an ArrayList for use with registerDungeonPack() + packFilePaths = new ArrayList(packFiles.length); + for (File packFile : packFiles) + { + packFilePaths.add(packFile.getPath()); + } + + registerDungeonPack(packDirectory.getAbsolutePath(), packFilePaths, false, true, reader); + } + else + { + System.err.println("Could not retrieve the list of schematics in a dungeon pack: " + packDirectory.getPath()); } } } + else + { + System.err.println("Could not retrieve the list of dungeon pack directories in the custom dungeons directory!"); + } } - private void registerBundledDungeons() + private void registerBundledDungeons(DungeonPackConfigReader reader) { //Register the core schematics //These are used for debugging and in case of unusual errors while loading dungeons @@ -285,31 +458,46 @@ public class DungeonHelper defaultError = new DungeonGenerator(DEFAULT_DUNGEON_WEIGHT, DEFAULT_ERROR_SCHEMATIC_PATH, true, DungeonType.UNKNOWN_TYPE); //Open the list of dungeons packaged with our mod and register their schematics - InputStream listStream = this.getClass().getResourceAsStream(BUNDLED_DUNGEONS_LIST_PATH); + registerBundledPack(BUNDLED_RUINS_LIST_PATH, RUINS_PACK_PATH, "Ruins", reader); + RuinsPack = getDungeonPack("Ruins"); + + System.out.println("Finished registering bundled dungeon packs"); + } + + private void registerBundledPack(String listPath, String packPath, String name, DungeonPackConfigReader reader) + { + System.out.println("Registering bundled dungeon pack: " + name); + + InputStream listStream = this.getClass().getResourceAsStream(listPath); if (listStream == null) { - System.err.println("Unable to open list of bundled dungeon schematics."); + System.err.println("Unable to open list of bundled dungeon schematics for " + name); return; } try { + //Read the list of schematics that come with a bundled pack BufferedReader listReader = new BufferedReader(new InputStreamReader(listStream)); + ArrayList schematics = new ArrayList(); String schematicPath = listReader.readLine(); while (schematicPath != null) { schematicPath = schematicPath.trim(); if (!schematicPath.isEmpty()) { - registerDungeon(schematicPath, true, false); + schematics.add(schematicPath); } schematicPath = listReader.readLine(); } listReader.close(); + + //Register the pack + registerDungeonPack(packPath, schematics, true, false, reader); } catch (Exception e) { - System.err.println("An exception occurred while reading the list of bundled dungeon schematics."); + System.err.println("An exception occurred while reading the list of bundled dungeon schematics for " + name); e.printStackTrace(); } } @@ -336,10 +524,38 @@ 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; + + //Are we allowed 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); + } + } + + //Pick the next dungeon + selection = selectedPack.getNextDungeon(inbound, random); } catch (Exception e) { @@ -358,6 +574,29 @@ public class DungeonHelper dimHelper.instance.getDimData(inbound.destDimID).dungeonGenerator = selection; } + @SuppressWarnings("unchecked") + private DungeonPack getRandomDungeonPack(DungeonPack current, Random random) + { + DungeonPack selection = current; + ArrayList> packs = new ArrayList>(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(pack, config.getPackWeight())); + } + } + if (!packs.isEmpty()) + { + //Pick a random dungeon pack + selection = ((WeightedContainer) WeightedRandom.getRandomItem(random, packs)).getData(); + } + return selection; + } + public Collection getDungeonNames() { //Use a HashSet to guarantee that all dungeon names will be distinct. @@ -426,6 +665,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 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 diff --git a/schematics/schematics.txt b/schematics/ruins.txt similarity index 100% rename from schematics/schematics.txt rename to schematics/ruins.txt