From e96fc027478fe7f710fd387483c14b3a78d4c936 Mon Sep 17 00:00:00 2001 From: SenseiKiwi Date: Sun, 4 Aug 2013 19:27:34 -0400 Subject: [PATCH 01/10] Progress on Implementing Dungeon Packs Started implementing our support for dungeon packs. The code is not usable yet and the mod is not functional at this stage. A few additional changes should make it testable. A significant obstacle to implementing dungeon packs easily is that DungeonGenerator doesn't have some necessary information and shouldn't be modified. Even if it is modified, old serialized instances wouldn't have the new fields initialized. I'm having to create workarounds until we implement the new save format. DungeonPack handles all of the logic of selecting a dungeon and verifying whether its type is valid. It relies on DungeonChainRule and OptimizedRule to check which dungeons should be generated next given a list of the dungeons in a chain. DungeonType maps types in packs to ID numbers and provides a reference to the pack that owns the type. DungeonPackConfig will carry config information to be passed to the DungeonPack constructor. --- .../mod_pocketDim/DungeonGenerator.java | 5 +- .../mod_pocketDim/SchematicLoader.java | 28 +- .../dungeon/pack/DungeonChainRule.java | 13 + .../dungeon/pack/DungeonPack.java | 247 ++++++++++++++++++ .../dungeon/pack/DungeonPackConfig.java | 49 ++++ .../dungeon/pack/DungeonType.java | 45 ++++ .../dungeon/pack/OptimizedRule.java | 25 ++ .../mod_pocketDim/helpers/DungeonHelper.java | 206 ++------------- .../mod_pocketDim/helpers/dimHelper.java | 12 +- 9 files changed, 431 insertions(+), 199 deletions(-) create mode 100644 StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonChainRule.java create mode 100644 StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonPack.java create mode 100644 StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonPackConfig.java create mode 100644 StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonType.java create mode 100644 StevenDimDoors/mod_pocketDim/dungeon/pack/OptimizedRule.java diff --git a/StevenDimDoors/mod_pocketDim/DungeonGenerator.java b/StevenDimDoors/mod_pocketDim/DungeonGenerator.java index a0eb9f9..ee575f2 100644 --- a/StevenDimDoors/mod_pocketDim/DungeonGenerator.java +++ b/StevenDimDoors/mod_pocketDim/DungeonGenerator.java @@ -12,16 +12,13 @@ public class DungeonGenerator implements Serializable public int weight; public String schematicPath; public ArrayList sideRifts = new ArrayList(); - public LinkData exitLink; - public static Random rand = new Random(); + public LinkData exitLink; public boolean isOpen; public int sideDoorsSoFar=0; public int exitDoorsSoFar=0; public int deadEndsSoFar=0; - - public DungeonGenerator(int weight, String schematicPath, Boolean isOpen) { this.weight=weight; diff --git a/StevenDimDoors/mod_pocketDim/SchematicLoader.java b/StevenDimDoors/mod_pocketDim/SchematicLoader.java index 279a297..22d46e1 100644 --- a/StevenDimDoors/mod_pocketDim/SchematicLoader.java +++ b/StevenDimDoors/mod_pocketDim/SchematicLoader.java @@ -2,6 +2,7 @@ package StevenDimDoors.mod_pocketDim; import java.io.File; import java.io.FileNotFoundException; import java.util.HashMap; +import java.util.Random; import net.minecraft.world.World; import StevenDimDoors.mod_pocketDim.dungeon.DungeonSchematic; @@ -27,14 +28,32 @@ public class SchematicLoader int originDimID = link.locDimID; int destDimID = link.destDimID; HashMap dimList = dimHelper.dimList; + World world; if (dimList.containsKey(destDimID)) { + dimList.get(destDimID).hasBeenFilled = true; + if (dimHelper.getWorld(destDimID) == null) + { + dimHelper.initDimension(destDimID); + } + world = dimHelper.getWorld(destDimID); + if (dimList.get(destDimID).dungeonGenerator == null) { - DungeonHelper.instance().generateDungeonLink(link); + //The following 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((link.destXCoord >> 4) * factorA + (link.destZCoord >> 4) * factorB ^ world.getSeed()); + + //TODO: FIX THIS LINE OR SADNESS WILL FOLLOW. Add a reference to the dungeon pack. + //DungeonHelper.instance().generateDungeonLink(link, ???, random); } schematicPath = dimList.get(destDimID).dungeonGenerator.schematicPath; + } else { @@ -77,13 +96,6 @@ public class SchematicLoader dungeon.applyImportFilters(properties); } - dimList.get(destDimID).hasBeenFilled = true; - if (dimHelper.getWorld(destDimID) == null) - { - dimHelper.initDimension(destDimID); - } - World world = dimHelper.getWorld(destDimID); - //Adjust the height at which the dungeon is placed to prevent vertical clipping int fixedY = adjustDestinationY(world, link.destYCoord, dungeon); if (fixedY != link.destYCoord) diff --git a/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonChainRule.java b/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonChainRule.java new file mode 100644 index 0000000..5258561 --- /dev/null +++ b/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonChainRule.java @@ -0,0 +1,13 @@ +package StevenDimDoors.mod_pocketDim.dungeon.pack; + +import java.util.HashMap; + +public class DungeonChainRule +{ + + public OptimizedRule optimize(HashMap nameToTypeMapping) { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonPack.java b/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonPack.java new file mode 100644 index 0000000..9bb714e --- /dev/null +++ b/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonPack.java @@ -0,0 +1,247 @@ +package StevenDimDoors.mod_pocketDim.dungeon.pack; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Random; + +import net.minecraft.util.WeightedRandom; +import StevenDimDoors.mod_pocketDim.DungeonGenerator; +import StevenDimDoors.mod_pocketDim.LinkData; +import StevenDimDoors.mod_pocketDim.helpers.DungeonHelper; +import StevenDimDoors.mod_pocketDim.helpers.dimHelper; +import StevenDimDoors.mod_pocketDim.util.WeightedContainer; + +public class DungeonPack +{ + //Why final? I just felt like it, honestly. ~SenseiKiwi + + private static final DungeonType WILDCARD_TYPE = new DungeonType(null, "?", 0); + + private final String name; + private final HashMap nameToTypeMapping; + private final ArrayList> groupedDungeons; + private final ArrayList allDungeons; + private final DungeonPackConfig config; + private final int maxRuleLength; + private final ArrayList rules; + + public DungeonPack(DungeonPackConfig config) + { + config.validate(); + this.config = config.clone(); //Store a clone of the config so that the caller can't change it externally later + this.name = config.getName(); + + int index; + int maxLength = 0; + int typeCount = config.getTypeNames().size(); + this.allDungeons = new ArrayList(); + this.nameToTypeMapping = new HashMap(typeCount); + this.groupedDungeons = new ArrayList>(typeCount); + + this.groupedDungeons.add(allDungeons); //Make sure the list of all dungeons is placed at index 0 + this.nameToTypeMapping.put(WILDCARD_TYPE.Name, WILDCARD_TYPE); + + index = 1; + for (String typeName : config.getTypeNames()) + { + String standardName = typeName.toUpperCase(); + this.nameToTypeMapping.put(standardName, new DungeonType(this, standardName, index)); + this.groupedDungeons.add(new ArrayList()); + index++; + } + + //Construct optimized rules from config rules + ArrayList chainRules = config.getRules(); + this.rules = new ArrayList(chainRules.size()); + for (DungeonChainRule rule : chainRules) + { + OptimizedRule optimized = rule.optimize(nameToTypeMapping); + this.rules.add(optimized); + if (maxLength < optimized.length()) + { + maxLength = optimized.length(); + } + } + this.maxRuleLength = maxLength; + + //Remove the reference to the non-optimized rules to free up memory - we won't need them here + this.config.setRules(null); + } + + public String getName() + { + return name; + } + + public boolean isEmpty() + { + return allDungeons.isEmpty(); + } + + public DungeonType getType(String typeName) + { + DungeonType result = nameToTypeMapping.get(typeName.toUpperCase()); + if (result != WILDCARD_TYPE) + { + return result; + } + else + { + return null; + } + } + + public boolean isKnownType(String typeName) + { + return (this.getType(typeName) != null); + } + + public DungeonGenerator getNextDungeon(LinkData inbound, Random random) + { + if (allDungeons.isEmpty()) + { + return null; + } + + //Retrieve a list of the previous dungeons in this chain. Restrict the length of the + //search to the length of the longest rule. Getting more data is useless. + dimHelper helper = dimHelper.instance; + + //TODO: Add dungeon pack parameter! We can't use dungeon types from other packs. + ArrayList history = DungeonHelper.getDungeonChainHistory(helper.getDimData(inbound.locDimID), maxRuleLength); + return getNextDungeon(history, random); + } + + private DungeonGenerator getNextDungeon(ArrayList history, Random random) + { + //Extract the dungeon types that have been used from history and convert them into an array of IDs + int index; + int[] typeHistory = new int[history.size()]; + HashSet excludedDungeons = null; + for (index = 0; index < typeHistory.length; index++) + { + typeHistory[index] = getDungeonType(history.get(index)).ID; + } + + for (OptimizedRule rule : rules) + { + if (rule.evaluate(typeHistory)) + { + //Pick a random dungeon type to be generated next based on the rule's products + ArrayList> products = rule.products(); + DungeonType nextType; + do + { + nextType = getRandomDungeonType(random, products, groupedDungeons); + if (nextType != null) + { + //Initialize the set of excluded dungeons if needed + if (excludedDungeons == null && config.allowDuplicatesInChain()) + { + //TODO: Finish implementing this! + } + + //List which dungeons are allowed + ArrayList candidates; + ArrayList group = groupedDungeons.get(nextType.ID); + if (excludedDungeons != null) + { + candidates = new ArrayList(group.size()); + for (DungeonGenerator dungeon : group) + { + if (!excludedDungeons.contains(dungeon)) + { + candidates.add(dungeon); + } + } + } + else + { + candidates = group; + } + if (!candidates.isEmpty()) + { + return getRandomDungeon(random, candidates); + } + } + } + while (nextType != null); + } + } + + //None of the rules were applicable. Simply return a random dungeon. + return getRandomDungeon(random); + } + + private DungeonType getDungeonType(DungeonGenerator generator) + { + //This function is a workaround for DungeonGenerator not having a dungeon type or pack field. + //I really don't want to go messing around with that serializable type. + //TODO: Remove this function once we transition to using the new save format. ~SenseiKiwi + + //TODO: Finish implementing this! + return null; + } + + public DungeonGenerator getRandomDungeon(Random random) + { + if (!allDungeons.isEmpty()) + { + return getRandomDungeon(random, allDungeons); + } + else + { + return null; + } + } + + private static DungeonType getRandomDungeonType(Random random, Collection> types, + ArrayList> groupedDungeons) + { + //TODO: Make this faster? This algorithm runs in quadratic time in the worst case because of the random-selection + //process and the removal search. Should be okay for normal use, though. ~SenseiKiwi + + //Pick a random dungeon type based on weights. Repeat this process until a non-empty group is found or all groups are checked. + while (!types.isEmpty()) + { + //Pick a random dungeon type + @SuppressWarnings("unchecked") + WeightedContainer resultContainer = (WeightedContainer) WeightedRandom.getRandomItem(random, types); + + //Check if there are any dungeons of that type + DungeonType selectedType = resultContainer.getData(); + if (!groupedDungeons.get(selectedType.ID).isEmpty()) + { + //Choose this type + return selectedType; + } + else + { + //We can't use this type because there are no dungeons of this type + //Remove it from the list of types and try again + types.remove(resultContainer); + } + } + + //We have run out of types to try + return null; + } + + private static DungeonGenerator getRandomDungeon(Random random, Collection dungeons) + { + //Use Minecraft's WeightedRandom to select our dungeon. =D + ArrayList> weights = + new ArrayList>(dungeons.size()); + for (DungeonGenerator dungeon : dungeons) + { + weights.add(new WeightedContainer(dungeon, dungeon.weight)); + } + + @SuppressWarnings("unchecked") + WeightedContainer resultContainer = (WeightedContainer) WeightedRandom.getRandomItem(random, weights); + return (resultContainer != null) ? resultContainer.getData() : null; + } +} diff --git a/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonPackConfig.java b/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonPackConfig.java new file mode 100644 index 0000000..8655d3c --- /dev/null +++ b/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonPackConfig.java @@ -0,0 +1,49 @@ +package StevenDimDoors.mod_pocketDim.dungeon.pack; + +import java.util.ArrayList; +import java.util.List; + +public class DungeonPackConfig +{ + public DungeonPackConfig() { } + + private DungeonPackConfig(DungeonPackConfig source) + { + + } + + public void validate() + { + + } + + public DungeonPackConfig clone() + { + return new DungeonPackConfig(this); + } + + public String getName() + { + return null; + } + + public List getTypeNames() + { + return null; + } + + public boolean allowDuplicatesInChain() + { + return false; + } + + public void setRules(Object object) { + // TODO Auto-generated method stub + + } + + public ArrayList getRules() { + // TODO Auto-generated method stub + return null; + } +} diff --git a/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonType.java b/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonType.java new file mode 100644 index 0000000..e15b683 --- /dev/null +++ b/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonType.java @@ -0,0 +1,45 @@ +package StevenDimDoors.mod_pocketDim.dungeon.pack; + +public class DungeonType implements Comparable +{ + public final DungeonPack Owner; + public final String Name; + public final int ID; + + public DungeonType(DungeonPack owner, String name, int id) + { + Owner = owner; + Name = name; + this.ID = id; + } + + @Override + public int compareTo(DungeonType other) + { + return this.ID - other.ID; + } + + @Override + public boolean equals(Object other) + { + return equals((DungeonType) other); + } + + public boolean equals(DungeonType other) + { + if (this == other) + return true; + + if (this == null || other == null) + return false; + + return (this.ID == other.ID); + } + + @Override + public int hashCode() + { + final int prime = 2039; + return prime * ID; + } +} diff --git a/StevenDimDoors/mod_pocketDim/dungeon/pack/OptimizedRule.java b/StevenDimDoors/mod_pocketDim/dungeon/pack/OptimizedRule.java new file mode 100644 index 0000000..c69ccd7 --- /dev/null +++ b/StevenDimDoors/mod_pocketDim/dungeon/pack/OptimizedRule.java @@ -0,0 +1,25 @@ +package StevenDimDoors.mod_pocketDim.dungeon.pack; + +import java.util.ArrayList; + +import StevenDimDoors.mod_pocketDim.util.WeightedContainer; + +public class OptimizedRule +{ + + public int length() { + // TODO Auto-generated method stub + return 0; + } + + public boolean evaluate(int[] typeHistory) { + // TODO Auto-generated method stub + return false; + } + + public ArrayList> products() { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/StevenDimDoors/mod_pocketDim/helpers/DungeonHelper.java b/StevenDimDoors/mod_pocketDim/helpers/DungeonHelper.java index a99d503..406ade1 100644 --- a/StevenDimDoors/mod_pocketDim/helpers/DungeonHelper.java +++ b/StevenDimDoors/mod_pocketDim/helpers/DungeonHelper.java @@ -13,7 +13,6 @@ import java.util.List; 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; @@ -21,8 +20,8 @@ import StevenDimDoors.mod_pocketDim.DungeonGenerator; import StevenDimDoors.mod_pocketDim.LinkData; import StevenDimDoors.mod_pocketDim.mod_pocketDim; import StevenDimDoors.mod_pocketDim.dungeon.DungeonSchematic; +import StevenDimDoors.mod_pocketDim.dungeon.pack.DungeonPack; import StevenDimDoors.mod_pocketDim.items.itemDimDoor; -import StevenDimDoors.mod_pocketDim.util.WeightedContainer; public class DungeonHelper { @@ -65,8 +64,6 @@ public class DungeonHelper MAZE_DUNGEON_TYPE }; - private Random rand = new Random(); - private ArrayList untaggedDungeons = new ArrayList(); private ArrayList registeredDungeons = new ArrayList(); @@ -354,157 +351,29 @@ public class DungeonHelper } } - public void generateDungeonLink(LinkData incoming) + public void generateDungeonLink(LinkData inbound, DungeonPack pack, Random random) { - DungeonGenerator dungeon; - int depth = dimHelper.instance.getDimDepth(incoming.locDimID); - int depthWeight = rand.nextInt(depth + 2) + rand.nextInt(depth + 2) - 2; - - int count = 10; - boolean flag = true; + DungeonGenerator selection; + try - { - - if (incoming.destYCoord > 15) - { - do - { - count--; - flag = true; - //Select a dungeon at random, taking into account its weight - dungeon = getRandomDungeon(rand, registeredDungeons); - - if (depth <= 1) - { - if(rand.nextBoolean()) - { - dungeon = complexHalls.get(rand.nextInt(complexHalls.size())); - - } - else if(rand.nextBoolean()) - { - dungeon = hubs.get(rand.nextInt(hubs.size())); - - } - else if(rand.nextBoolean()) - { - dungeon = hubs.get(rand.nextInt(hubs.size())); - - } - else if(deadEnds.contains(dungeon)||exits.contains(dungeon)) - { - flag=false; - } - } - else if (depth <= 3 && (deadEnds.contains(dungeon) || exits.contains(dungeon) || rand.nextBoolean())) - { - if(rand.nextBoolean()) - { - dungeon = hubs.get(rand.nextInt(hubs.size())); - - } - else if(rand.nextBoolean()) - { - dungeon = mazes.get(rand.nextInt(mazes.size())); - } - else if(rand.nextBoolean()) - { - dungeon = pistonTraps.get(rand.nextInt(pistonTraps.size())); - - } - else - { - flag = false; - } - } - else if (rand.nextInt(3) == 0 && !complexHalls.contains(dungeon)) - { - if (rand.nextInt(3) == 0) - { - dungeon = simpleHalls.get(rand.nextInt(simpleHalls.size())); - } - else if(rand.nextBoolean()) - { - dungeon = pistonTraps.get(rand.nextInt(pistonTraps.size())); - } - else if (depth < 4) - { - dungeon = hubs.get(rand.nextInt(hubs.size())); - } - } - else if (depthWeight - depthWeight / 2 > depth -4 && (deadEnds.contains(dungeon) || exits.contains(dungeon))) - { - if(rand.nextBoolean()) - { - dungeon = simpleHalls.get(rand.nextInt(simpleHalls.size())); - } - else if(rand.nextBoolean()) - { - dungeon = complexHalls.get(rand.nextInt(complexHalls.size())); - } - else if(rand.nextBoolean()) - { - dungeon = pistonTraps.get(rand.nextInt(pistonTraps.size())); - } - else - { - flag = false; - } - } - else if (depthWeight > 7 && hubs.contains(dungeon)) - { - if(rand.nextInt(12)+5 10 && hubs.contains(dungeon)) - { - flag = false; - } - - if(getDungeonDataInChain(dimHelper.instance.getDimData(incoming.locDimID)).contains(dungeon)) - { - flag=false; - } - } - while (!flag && count > 0); - } - else - { - dungeon = defaultUp; - } + { + selection = pack.getNextDungeon(inbound, random); } catch (Exception e) { + System.err.println("An exception occurred while selecting a dungeon:"); e.printStackTrace(); - if (registeredDungeons.size() > 0) + + if (!pack.isEmpty()) { - //Select a random dungeon - dungeon = getRandomDungeon(rand, registeredDungeons); + selection = pack.getRandomDungeon(random); } else { - return; + selection = defaultError; } } - dimHelper.instance.getDimData(incoming.destDimID).dungeonGenerator = dungeon; - //dimHelper.instance.getDimData(incoming.destDimID).dungeonGenerator = defaultUp; + dimHelper.instance.getDimData(inbound.destDimID).dungeonGenerator = selection; } public Collection getDungeonNames() { @@ -539,50 +408,33 @@ public class DungeonHelper return names; } - private static DungeonGenerator getRandomDungeon(Random random, Collection dungeons) + public static ArrayList getDungeonChainHistory(DimData dimData, int maxSize) { - //Use Minecraft's WeightedRandom to select our dungeon. =D - ArrayList> weights = - new ArrayList>(dungeons.size()); - for (DungeonGenerator dungeon : dungeons) - { - weights.add(new WeightedContainer(dungeon, dungeon.weight)); - } + //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 - @SuppressWarnings("unchecked") - WeightedContainer resultContainer = (WeightedContainer) WeightedRandom.getRandomItem(random, weights); - return (resultContainer != null) ? resultContainer.getData() : null; - } - public static ArrayList getDungeonDataInChain(DimData dimData) - { - DimData startingDim = dimHelper.instance.getDimData(dimHelper.instance.getLinkDataFromCoords(dimData.exitDimLink.destXCoord, dimData.exitDimLink.destYCoord, dimData.exitDimLink.destZCoord, dimData.exitDimLink.destDimID).destDimID); - - return getDungeonDataBelow(startingDim); - } - private static ArrayList getDungeonDataBelow(DimData dimData) - { - ArrayList dungeonData = new ArrayList(); - if(dimData.dungeonGenerator!=null) + dimHelper helper = dimHelper.instance; + ArrayList history = new ArrayList(); + DimData tailDim = helper.getDimData(helper.getLinkDataFromCoords(dimData.exitDimLink.destXCoord, dimData.exitDimLink.destYCoord, dimData.exitDimLink.destZCoord, dimData.exitDimLink.destDimID).destDimID); + + for (int count = 0; count < maxSize && tailDim.dungeonGenerator != null; count++) { - dungeonData.add(dimData.dungeonGenerator); + history.add(tailDim.dungeonGenerator); - for(LinkData link : dimData.getLinksInDim()) + if (count + 1 < maxSize) { - if(dimHelper.dimList.containsKey(link.destDimID)) + for (LinkData link : tailDim.getLinksInDim()) { - if(dimHelper.instance.getDimData(link.destDimID).dungeonGenerator!=null&&dimHelper.instance.getDimDepth(link.destDimID)==dimData.depth+1) + DimData nextDim = dimHelper.instance.getDimData(link.destDimID); + if (helper.getDimDepth(link.destDimID) == tailDim.depth + 1) { - for(DungeonGenerator dungeonGen :getDungeonDataBelow(dimHelper.instance.getDimData(link.destDimID)) ) - { - if(!dungeonData.contains(dungeonGen)) - { - dungeonData.add(dungeonGen); - } - } + tailDim = nextDim; + break; } } } } - return dungeonData; + return history; } } \ No newline at end of file diff --git a/StevenDimDoors/mod_pocketDim/helpers/dimHelper.java b/StevenDimDoors/mod_pocketDim/helpers/dimHelper.java index 9f82048..7e2cf21 100644 --- a/StevenDimDoors/mod_pocketDim/helpers/dimHelper.java +++ b/StevenDimDoors/mod_pocketDim/helpers/dimHelper.java @@ -1320,17 +1320,9 @@ public class dimHelper extends DimensionManager { return dimHelper.instance.getDimData(world.provider.dimensionId); } + public DimData getDimData(int dimID) { - if(dimHelper.dimList.containsKey(dimID)) - { - return dimHelper.dimList.get(dimID); - } - else - { - return null; - } + return dimHelper.dimList.get(dimID); } - - } From 101e9e4ce6c71689d5984b4cd9ca1582849b9b21 Mon Sep 17 00:00:00 2001 From: SenseiKiwi Date: Mon, 5 Aug 2013 09:48:49 -0400 Subject: [PATCH 02/10] Progress on Implementing Dungeon Packs Completed enough of the implementation and integration to compile DD. Some portions of the code are only for testing and will be removed later. The configuration for default dungeons is hardcoded - we can parse config files once we're certain that dungeon chains work. At the moment, dungeons generate but it doesn't seem like the rules we set are being followed properly. Renamed OptimizedRule to DungeonChainRule, and renamed the old DungeonChainRule to DungeonChainRuleDefinition, to match the role of each class better. Added some hax to DungeonGenerator to get packs integrated - the implementation will be much cleaner once the new save format is done. --- .../mod_pocketDim/DungeonGenerator.java | 60 ++++++- .../mod_pocketDim/SchematicLoader.java | 6 +- .../commands/CommandExportDungeon.java | 4 +- .../dungeon/pack/DungeonChainRule.java | 59 ++++++- .../dungeon/pack/DungeonPack.java | 83 ++++++---- .../dungeon/pack/DungeonPackConfig.java | 55 +++++-- .../dungeon/pack/DungeonType.java | 3 + .../dungeon/pack/OptimizedRule.java | 25 --- .../mod_pocketDim/helpers/DungeonHelper.java | 154 ++++++++++-------- .../mod_pocketDim/util/WeightedContainer.java | 6 +- 10 files changed, 298 insertions(+), 157 deletions(-) delete mode 100644 StevenDimDoors/mod_pocketDim/dungeon/pack/OptimizedRule.java diff --git a/StevenDimDoors/mod_pocketDim/DungeonGenerator.java b/StevenDimDoors/mod_pocketDim/DungeonGenerator.java index ee575f2..6c6ea16 100644 --- a/StevenDimDoors/mod_pocketDim/DungeonGenerator.java +++ b/StevenDimDoors/mod_pocketDim/DungeonGenerator.java @@ -1,14 +1,22 @@ package StevenDimDoors.mod_pocketDim; +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.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. + //Otherwise it would have to be serializable and all sorts of problems would arise. + private static final HashMap dungeonTypes = new HashMap(); + public int weight; public String schematicPath; public ArrayList sideRifts = new ArrayList(); @@ -19,10 +27,54 @@ public class DungeonGenerator implements Serializable public int exitDoorsSoFar=0; public int deadEndsSoFar=0; - public DungeonGenerator(int weight, String schematicPath, Boolean isOpen) + public DungeonGenerator(int weight, String schematicPath, boolean isOpen, DungeonType dungeonType) { - this.weight=weight; - this.schematicPath=schematicPath; - this.isOpen=isOpen; + this.weight = weight; + this.schematicPath = schematicPath; + this.isOpen = isOpen; + + dungeonTypes.put(this, dungeonType); //Hax... + } + + public DungeonType getDungeonType() + { + DungeonType type = dungeonTypes.get(this); + if (type == null) + { + //Infer the dungeon's type from its file name + //There is minimal risk of us applying this to untagged dungeons and this'll be phased out + //when we get the new save format. + try + { + File file = new File(schematicPath); + String typeName = file.getName().split("_")[0]; + type = DungeonHelper.instance().RuinsPack.getType(typeName); + } + catch (Exception e) { } + if (type == null) + { + type = DungeonType.UNKNOWN_TYPE; + } + dungeonTypes.put(this, type); + } + return type; + } + + @Override + public int hashCode() + { + return (schematicPath != null) ? schematicPath.hashCode() : 0; + } + + @Override + public boolean equals(Object other) + { + return equals((DungeonGenerator) other); + } + + public boolean equals(DungeonGenerator other) + { + return ((this.schematicPath != null && this.schematicPath.equals(other.schematicPath)) || + (this.schematicPath == other.schematicPath)); } } \ No newline at end of file diff --git a/StevenDimDoors/mod_pocketDim/SchematicLoader.java b/StevenDimDoors/mod_pocketDim/SchematicLoader.java index 22d46e1..00d1b6a 100644 --- a/StevenDimDoors/mod_pocketDim/SchematicLoader.java +++ b/StevenDimDoors/mod_pocketDim/SchematicLoader.java @@ -28,6 +28,7 @@ public class SchematicLoader int originDimID = link.locDimID; int destDimID = link.destDimID; HashMap dimList = dimHelper.dimList; + DungeonHelper dungeonHelper = DungeonHelper.instance(); World world; if (dimList.containsKey(destDimID)) @@ -49,8 +50,7 @@ public class SchematicLoader long factorB = random.nextLong() / 2L * 2L + 1L; random.setSeed((link.destXCoord >> 4) * factorA + (link.destZCoord >> 4) * factorB ^ world.getSeed()); - //TODO: FIX THIS LINE OR SADNESS WILL FOLLOW. Add a reference to the dungeon pack. - //DungeonHelper.instance().generateDungeonLink(link, ???, random); + dungeonHelper.generateDungeonLink(link, dungeonHelper.RuinsPack, random); } schematicPath = dimList.get(destDimID).dungeonGenerator.schematicPath; @@ -90,7 +90,7 @@ public class SchematicLoader //TODO: In the future, remove this dungeon from the generation lists altogether. //That will have to wait until our code is updated to support that more easily. System.err.println("The dungeon will not be loaded."); - DungeonGenerator defaultError = DungeonHelper.instance().getDefaultErrorDungeon(); + DungeonGenerator defaultError = dungeonHelper.getDefaultErrorDungeon(); dimList.get(destDimID).dungeonGenerator = defaultError; dungeon = checkSourceAndLoad(defaultError.schematicPath); dungeon.applyImportFilters(properties); diff --git a/StevenDimDoors/mod_pocketDim/commands/CommandExportDungeon.java b/StevenDimDoors/mod_pocketDim/commands/CommandExportDungeon.java index ff833b3..194d5ce 100644 --- a/StevenDimDoors/mod_pocketDim/commands/CommandExportDungeon.java +++ b/StevenDimDoors/mod_pocketDim/commands/CommandExportDungeon.java @@ -54,7 +54,7 @@ public class CommandExportDungeon extends DDCommandBase if (command[1].equalsIgnoreCase("override")) { //Check that the schematic name is a legal name - if (DungeonHelper.SchematicNamePattern.matcher(command[0]).matches()) + if (DungeonHelper.SCHEMATIC_NAME_PATTERN.matcher(command[0]).matches()) { //Export the schematic return exportDungeon(sender, command[0]); @@ -85,7 +85,7 @@ public class CommandExportDungeon extends DDCommandBase { return new DDCommandResult("Error: Invalid dungeon type. Please use one of the existing types."); } - if (!DungeonHelper.DungeonNamePattern.matcher(command[1]).matches()) + if (!DungeonHelper.DUNGEON_NAME_PATTERN.matcher(command[1]).matches()) { return new DDCommandResult("Error: Invalid dungeon name. Please use only letters, numbers, and dashes."); } diff --git a/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonChainRule.java b/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonChainRule.java index 5258561..472f20b 100644 --- a/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonChainRule.java +++ b/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonChainRule.java @@ -1,13 +1,66 @@ package StevenDimDoors.mod_pocketDim.dungeon.pack; +import java.util.ArrayList; import java.util.HashMap; +import StevenDimDoors.mod_pocketDim.util.WeightedContainer; + public class DungeonChainRule { + private final int[] condition; + private final ArrayList> products; + + public DungeonChainRule(DungeonChainRuleDefinition source, HashMap nameToTypeMapping) + { + ArrayList conditionNames = source.getCondition(); + ArrayList> productNames = source.getProducts(); - public OptimizedRule optimize(HashMap nameToTypeMapping) { - // TODO Auto-generated method stub - return null; + condition = new int[conditionNames.size()]; + for (int k = 0; k < condition.length; k++) + { + condition[k] = nameToTypeMapping.get(conditionNames.get(k)).ID; + } + products = new ArrayList>(); + for (WeightedContainer product : productNames) + { + products.add(new WeightedContainer(nameToTypeMapping.get(product.getData()), product.itemWeight )); + } + } + + public int length() + { + return condition.length; } + public boolean evaluate(int[] typeHistory) + { + if (typeHistory.length >= condition.length) + { + for (int k = 0; k < condition.length; k++) + { + if (condition[k] != 0 && typeHistory[k] != condition[k]) + { + return false; + } + } + return true; + } + else + { + return false; + } + } + + public ArrayList> products() + { + //Create a deep copy of the internal list of products. That way, if the list is modified externally, + //it won't affect the reference copy inside this rule. + ArrayList> copy = new ArrayList>(products.size()); + for (WeightedContainer container : products) + { + copy.add(container.clone()); + } + + return copy; + } } diff --git a/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonPack.java b/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonPack.java index 9bb714e..9db777d 100644 --- a/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonPack.java +++ b/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonPack.java @@ -4,7 +4,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; -import java.util.List; import java.util.Random; import net.minecraft.util.WeightedRandom; @@ -16,9 +15,11 @@ import StevenDimDoors.mod_pocketDim.util.WeightedContainer; public class DungeonPack { - //Why final? I just felt like it, honestly. ~SenseiKiwi - - private static final DungeonType WILDCARD_TYPE = new DungeonType(null, "?", 0); + //There is no precaution against having a dungeon type removed from a config file after dungeons of that type + //have been generated. That would likely cause one or two problems. It's hard to guard against when I don't know + //what the save format will be like completely. How should this class behave if it finds a "disowned" type? + //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 final String name; private final HashMap nameToTypeMapping; @@ -26,7 +27,7 @@ public class DungeonPack private final ArrayList allDungeons; private final DungeonPackConfig config; private final int maxRuleLength; - private final ArrayList rules; + private final ArrayList rules; public DungeonPack(DungeonPackConfig config) { @@ -42,7 +43,7 @@ public class DungeonPack this.groupedDungeons = new ArrayList>(typeCount); this.groupedDungeons.add(allDungeons); //Make sure the list of all dungeons is placed at index 0 - this.nameToTypeMapping.put(WILDCARD_TYPE.Name, WILDCARD_TYPE); + this.nameToTypeMapping.put(DungeonType.WILDCARD_TYPE.Name, DungeonType.WILDCARD_TYPE); index = 1; for (String typeName : config.getTypeNames()) @@ -53,22 +54,23 @@ public class DungeonPack index++; } - //Construct optimized rules from config rules - ArrayList chainRules = config.getRules(); - this.rules = new ArrayList(chainRules.size()); - for (DungeonChainRule rule : chainRules) + //Construct optimized rules from definitions + ArrayList definitions = config.getRules(); + this.rules = new ArrayList(definitions.size()); + for (DungeonChainRuleDefinition definition : definitions) { - OptimizedRule optimized = rule.optimize(nameToTypeMapping); - this.rules.add(optimized); - if (maxLength < optimized.length()) + DungeonChainRule rule = new DungeonChainRule(definition, nameToTypeMapping); + this.rules.add(rule); + if (maxLength < rule.length()) { - maxLength = optimized.length(); + maxLength = rule.length(); } } this.maxRuleLength = maxLength; - //Remove the reference to the non-optimized rules to free up memory - we won't need them here + //Remove unnecessary references to save a little memory - we won't need them here this.config.setRules(null); + this.config.setTypeNames(null); } public String getName() @@ -84,7 +86,7 @@ public class DungeonPack public DungeonType getType(String typeName) { DungeonType result = nameToTypeMapping.get(typeName.toUpperCase()); - if (result != WILDCARD_TYPE) + if (result.Owner == this) //Filter out the wildcard dungeon type { return result; } @@ -99,6 +101,21 @@ public class DungeonPack return (this.getType(typeName) != null); } + public void addDungeon(DungeonGenerator generator) + { + //Make sure this dungeon really belongs in this pack + DungeonType type = generator.getDungeonType(); + if (type.Owner == this) + { + allDungeons.add(generator); + groupedDungeons.get(type.ID).add(generator); + } + else + { + throw new IllegalArgumentException("The dungeon type of generator must belong to this instance of DungeonPack."); + } + } + public DungeonGenerator getNextDungeon(LinkData inbound, Random random) { if (allDungeons.isEmpty()) @@ -106,12 +123,14 @@ public class DungeonPack return null; } - //Retrieve a list of the previous dungeons in this chain. Restrict the length of the - //search to the length of the longest rule. Getting more data is useless. - dimHelper helper = dimHelper.instance; + //Retrieve a list of the previous dungeons in this chain. + //If we're not going to check for duplicates in chains, restrict the length of the history to the length + //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. - //TODO: Add dungeon pack parameter! We can't use dungeon types from other packs. - ArrayList history = DungeonHelper.getDungeonChainHistory(helper.getDimData(inbound.locDimID), maxRuleLength); + int maxSearchLength = config.allowDuplicatesInChain() ? maxRuleLength : 1337; + ArrayList history = DungeonHelper.getDungeonChainHistory( + dimHelper.instance.getDimData(inbound.locDimID), this, maxSearchLength); return getNextDungeon(history, random); } @@ -123,10 +142,10 @@ public class DungeonPack HashSet excludedDungeons = null; for (index = 0; index < typeHistory.length; index++) { - typeHistory[index] = getDungeonType(history.get(index)).ID; + typeHistory[index] = history.get(index).getDungeonType().ID; } - for (OptimizedRule rule : rules) + for (DungeonChainRule rule : rules) { if (rule.evaluate(typeHistory)) { @@ -139,9 +158,9 @@ public class DungeonPack if (nextType != null) { //Initialize the set of excluded dungeons if needed - if (excludedDungeons == null && config.allowDuplicatesInChain()) + if (excludedDungeons == null && !config.allowDuplicatesInChain()) { - //TODO: Finish implementing this! + excludedDungeons = new HashSet(history); } //List which dungeons are allowed @@ -166,6 +185,8 @@ public class DungeonPack { return getRandomDungeon(random, candidates); } + //If we've reached this point, then a dungeon was not selected. Discard the type and try again. + products.remove(nextType); } } while (nextType != null); @@ -175,16 +196,6 @@ public class DungeonPack //None of the rules were applicable. Simply return a random dungeon. return getRandomDungeon(random); } - - private DungeonType getDungeonType(DungeonGenerator generator) - { - //This function is a workaround for DungeonGenerator not having a dungeon type or pack field. - //I really don't want to go messing around with that serializable type. - //TODO: Remove this function once we transition to using the new save format. ~SenseiKiwi - - //TODO: Finish implementing this! - return null; - } public DungeonGenerator getRandomDungeon(Random random) { @@ -202,7 +213,7 @@ public class DungeonPack ArrayList> groupedDungeons) { //TODO: Make this faster? This algorithm runs in quadratic time in the worst case because of the random-selection - //process and the removal search. Should be okay for normal use, though. ~SenseiKiwi + //process and the removal search. Might be okay for normal use, though. ~SenseiKiwi //Pick a random dungeon type based on weights. Repeat this process until a non-empty group is found or all groups are checked. while (!types.isEmpty()) diff --git a/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonPackConfig.java b/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonPackConfig.java index 8655d3c..c99dd91 100644 --- a/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonPackConfig.java +++ b/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonPackConfig.java @@ -1,22 +1,36 @@ package StevenDimDoors.mod_pocketDim.dungeon.pack; import java.util.ArrayList; -import java.util.List; public class DungeonPackConfig { + private String name; + private ArrayList typeNames; + private boolean allowDuplicatesInChain; + private ArrayList rules; + public DungeonPackConfig() { } + @SuppressWarnings("unchecked") private DungeonPackConfig(DungeonPackConfig source) { - + this.name = source.name; + this.typeNames = (ArrayList) source.typeNames.clone(); + this.allowDuplicatesInChain = source.allowDuplicatesInChain; + this.rules = (ArrayList) source.rules.clone(); } public void validate() { - + if (this.name == null) + throw new NullPointerException("name cannot be null"); + if (this.typeNames == null) + throw new NullPointerException("typeNames cannot be null"); + if (this.rules == null) + throw new NullPointerException("rules cannot be null"); } + @Override public DungeonPackConfig clone() { return new DungeonPackConfig(this); @@ -24,26 +38,41 @@ public class DungeonPackConfig public String getName() { - return null; + return name; + } + + public void setName(String name) + { + this.name = name; } - public List getTypeNames() + public ArrayList getTypeNames() { - return null; + return typeNames; + } + + public void setTypeNames(ArrayList typeNames) + { + this.typeNames = typeNames; } public boolean allowDuplicatesInChain() { - return false; + return allowDuplicatesInChain; + } + + public void setAllowDuplicatesInChain(boolean value) + { + allowDuplicatesInChain = value; } - public void setRules(Object object) { - // TODO Auto-generated method stub - + public void setRules(ArrayList rules) + { + this.rules = rules; } - public ArrayList getRules() { - // TODO Auto-generated method stub - return null; + public ArrayList getRules() + { + return rules; } } diff --git a/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonType.java b/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonType.java index e15b683..50e5921 100644 --- a/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonType.java +++ b/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonType.java @@ -2,6 +2,9 @@ package StevenDimDoors.mod_pocketDim.dungeon.pack; public class DungeonType implements Comparable { + public static final DungeonType WILDCARD_TYPE = new DungeonType(null, "?", 0); + public static final DungeonType UNKNOWN_TYPE = new DungeonType(null, "!", -1); + public final DungeonPack Owner; public final String Name; public final int ID; diff --git a/StevenDimDoors/mod_pocketDim/dungeon/pack/OptimizedRule.java b/StevenDimDoors/mod_pocketDim/dungeon/pack/OptimizedRule.java deleted file mode 100644 index c69ccd7..0000000 --- a/StevenDimDoors/mod_pocketDim/dungeon/pack/OptimizedRule.java +++ /dev/null @@ -1,25 +0,0 @@ -package StevenDimDoors.mod_pocketDim.dungeon.pack; - -import java.util.ArrayList; - -import StevenDimDoors.mod_pocketDim.util.WeightedContainer; - -public class OptimizedRule -{ - - public int length() { - // TODO Auto-generated method stub - return 0; - } - - public boolean evaluate(int[] typeHistory) { - // TODO Auto-generated method stub - return false; - } - - public ArrayList> products() { - // TODO Auto-generated method stub - return null; - } - -} diff --git a/StevenDimDoors/mod_pocketDim/helpers/DungeonHelper.java b/StevenDimDoors/mod_pocketDim/helpers/DungeonHelper.java index 406ade1..c364297 100644 --- a/StevenDimDoors/mod_pocketDim/helpers/DungeonHelper.java +++ b/StevenDimDoors/mod_pocketDim/helpers/DungeonHelper.java @@ -5,9 +5,9 @@ import java.io.File; import java.io.InputStream; import java.io.InputStreamReader; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Random; @@ -20,15 +20,20 @@ import StevenDimDoors.mod_pocketDim.DungeonGenerator; import StevenDimDoors.mod_pocketDim.LinkData; import StevenDimDoors.mod_pocketDim.mod_pocketDim; import StevenDimDoors.mod_pocketDim.dungeon.DungeonSchematic; +import StevenDimDoors.mod_pocketDim.dungeon.pack.DungeonChainRuleDefinition; import StevenDimDoors.mod_pocketDim.dungeon.pack.DungeonPack; +import StevenDimDoors.mod_pocketDim.dungeon.pack.DungeonPackConfig; +import StevenDimDoors.mod_pocketDim.dungeon.pack.DungeonType; import StevenDimDoors.mod_pocketDim.items.itemDimDoor; +import StevenDimDoors.mod_pocketDim.util.WeightedContainer; public class DungeonHelper { private static DungeonHelper instance = null; private static DDProperties properties = null; - public static final Pattern SchematicNamePattern = Pattern.compile("[A-Za-z0-9_\\-]+"); - public static final Pattern DungeonNamePattern = Pattern.compile("[A-Za-z0-9\\-]+"); + + 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\\-]+"); private static final String DEFAULT_UP_SCHEMATIC_PATH = "/schematics/core/simpleStairsUp.schematic"; private static final String DEFAULT_DOWN_SCHEMATIC_PATH = "/schematics/core/simpleStairsDown.schematic"; @@ -44,70 +49,22 @@ public class DungeonHelper public static final short MAX_DUNGEON_HEIGHT = MAX_DUNGEON_WIDTH; public static final short MAX_DUNGEON_LENGTH = MAX_DUNGEON_WIDTH; - private static final String HUB_DUNGEON_TYPE = "Hub"; - private static final String TRAP_DUNGEON_TYPE = "Trap"; - private static final String SIMPLE_HALL_DUNGEON_TYPE = "SimpleHall"; - private static final String COMPLEX_HALL_DUNGEON_TYPE = "ComplexHall"; - private static final String EXIT_DUNGEON_TYPE = "Exit"; - private static final String DEAD_END_DUNGEON_TYPE = "DeadEnd"; - private static final String MAZE_DUNGEON_TYPE = "Maze"; - - //The list of dungeon types will be kept as an array for now. If we allow new - //dungeon types in the future, then this can be changed to an ArrayList. - private static final String[] DUNGEON_TYPES = new String[] { - HUB_DUNGEON_TYPE, - TRAP_DUNGEON_TYPE, - SIMPLE_HALL_DUNGEON_TYPE, - COMPLEX_HALL_DUNGEON_TYPE, - EXIT_DUNGEON_TYPE, - DEAD_END_DUNGEON_TYPE, - MAZE_DUNGEON_TYPE - }; - private ArrayList untaggedDungeons = new ArrayList(); private ArrayList registeredDungeons = new ArrayList(); - - private ArrayList simpleHalls = new ArrayList(); - private ArrayList complexHalls = new ArrayList(); - private ArrayList deadEnds = new ArrayList(); - private ArrayList hubs = new ArrayList(); - private ArrayList mazes = new ArrayList(); - private ArrayList pistonTraps = new ArrayList(); - private ArrayList exits = new ArrayList(); + public DungeonPack RuinsPack; + private DungeonGenerator defaultUp; private DungeonGenerator defaultDown; private DungeonGenerator defaultError; - private HashSet dungeonTypeChecker; - private HashMap> dungeonTypeMapping; - private DungeonHelper() { - //Load the dungeon type checker with the list of all types in lowercase. - //Capitalization matters for matching in a hash set. - dungeonTypeChecker = new HashSet(); - for (String dungeonType : DUNGEON_TYPES) - { - dungeonTypeChecker.add(dungeonType.toLowerCase()); - } - - //Add all the basic dungeon types to dungeonTypeMapping - //Dungeon type names must be passed in lowercase to make matching easier. - dungeonTypeMapping = new HashMap>(); - dungeonTypeMapping.put(SIMPLE_HALL_DUNGEON_TYPE.toLowerCase(), simpleHalls); - dungeonTypeMapping.put(COMPLEX_HALL_DUNGEON_TYPE.toLowerCase(), complexHalls); - dungeonTypeMapping.put(HUB_DUNGEON_TYPE.toLowerCase(), hubs); - dungeonTypeMapping.put(EXIT_DUNGEON_TYPE.toLowerCase(), exits); - dungeonTypeMapping.put(DEAD_END_DUNGEON_TYPE.toLowerCase(), deadEnds); - dungeonTypeMapping.put(MAZE_DUNGEON_TYPE.toLowerCase(), mazes); - dungeonTypeMapping.put(TRAP_DUNGEON_TYPE.toLowerCase(), pistonTraps); - //Load our reference to the DDProperties singleton if (properties == null) properties = DDProperties.instance(); - registerCustomDungeons(); + registerDungeons(); } public static DungeonHelper initialize() @@ -135,15 +92,67 @@ public class DungeonHelper return instance; } - private void registerCustomDungeons() + private void registerDungeons() { File file = new File(properties.CustomSchematicDirectory); if (file.exists() || file.mkdir()) { copyfile.copyFile(DUNGEON_CREATION_GUIDE_SOURCE_PATH, file.getAbsolutePath() + "/How_to_add_dungeons.txt"); } + + RuinsPack = new DungeonPack(createRuinsConfig()); + registerBundledDungeons(); - importCustomDungeons(properties.CustomSchematicDirectory); + registerCustomDungeons(properties.CustomSchematicDirectory); + } + + private static DungeonPackConfig createRuinsConfig() + { + //This is a temporarily function for testing dungeon packs. + //It'll be removed later when we read dungeon configurations from files. + + ArrayList rules = new ArrayList(); + rules.add(parseDefinitionUnsafe("? ? ? -> DeadEnd Exit")); + rules.add(parseDefinitionUnsafe("Trap -> ?")); + rules.add(parseDefinitionUnsafe("Hub -> Trap")); + rules.add(parseDefinitionUnsafe("? -> Hub")); + rules.add(parseDefinitionUnsafe("-> ComplexHall#40 Hub#30 Trap#10 SimpleHall#10 Maze#10")); + + String[] typeNames = "Hub Trap Maze Exit DeadEnd SimpleHall ComplexHall".toUpperCase().split(" "); + + DungeonPackConfig config = new DungeonPackConfig(); + config.setName("ruins"); + config.setAllowDuplicatesInChain(false); + config.setRules(rules); + config.setTypeNames(new ArrayList(Arrays.asList(typeNames))); + return config; + } + + private static DungeonChainRuleDefinition parseDefinitionUnsafe(String definition) + { + //This is an improvised parsing function for rule definitions. Only for testing!!! + definition = definition.toUpperCase(); + String[] parts = definition.split("->"); + ArrayList condition = new ArrayList(); + ArrayList> products = new ArrayList>(); + + for (String conditionPart : parts[0].split(" ")) + { + if (!conditionPart.isEmpty()) + condition.add(conditionPart); + } + + for (String product : parts[1].split(" ")) + { + if (!product.isEmpty()) + { + String[] productParts = product.split("#"); + String productType = productParts[0]; + int weight = (productParts.length > 1) ? Integer.parseInt(productParts[1]) : 100; + products.add(new WeightedContainer(productType, weight)); + } + } + return new DungeonChainRuleDefinition(condition, products); } public List getRegisteredDungeons() @@ -186,7 +195,7 @@ public class DungeonHelper public boolean validateDungeonType(String type) { //Check if the dungeon type is valid - return dungeonTypeChecker.contains(type.toLowerCase()); + return RuinsPack.isKnownType(type); } public boolean validateSchematicName(String name) @@ -203,11 +212,11 @@ public class DungeonHelper return false; //Check if the dungeon type is valid - if (!dungeonTypeChecker.contains(dungeonData[0].toLowerCase())) + if (!validateDungeonType(dungeonData[0])) return false; //Check if the name is valid - if (!SchematicNamePattern.matcher(dungeonData[1]).matches()) + if (!SCHEMATIC_NAME_PATTERN.matcher(dungeonData[1]).matches()) return false; //Check if the open/closed flag is present @@ -246,14 +255,14 @@ public class DungeonHelper //Strip off the file extension while splitting the file name String[] dungeonData = name.substring(0, name.length() - SCHEMATIC_FILE_EXTENSION.length()).split("_"); - String dungeonType = dungeonData[0].toLowerCase(); + DungeonType dungeonType = RuinsPack.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); + DungeonGenerator generator = new DungeonGenerator(weight, path, isOpen, dungeonType); - dungeonTypeMapping.get(dungeonType).add(generator); + RuinsPack.addDungeon(generator); registeredDungeons.add(generator); if (verbose) { @@ -266,7 +275,7 @@ public class DungeonHelper { System.out.println("Could not parse dungeon filename, not adding dungeon to generation lists"); } - untaggedDungeons.add(new DungeonGenerator(DEFAULT_DUNGEON_WEIGHT, path, true)); + untaggedDungeons.add(new DungeonGenerator(DEFAULT_DUNGEON_WEIGHT, path, true, DungeonType.UNKNOWN_TYPE)); System.out.println("Registered untagged dungeon: " + name); } } @@ -277,7 +286,7 @@ public class DungeonHelper } } - private void importCustomDungeons(String path) + private void registerCustomDungeons(String path) { File directory = new File(path); File[] schematicNames = directory.listFiles(); @@ -298,9 +307,9 @@ public class DungeonHelper { //Register the core schematics //These are used for debugging and in case of unusual errors while loading dungeons - defaultUp = new DungeonGenerator(DEFAULT_DUNGEON_WEIGHT, DEFAULT_UP_SCHEMATIC_PATH, true); - defaultDown = new DungeonGenerator(DEFAULT_DUNGEON_WEIGHT, DEFAULT_DOWN_SCHEMATIC_PATH, true); - defaultError = new DungeonGenerator(DEFAULT_DUNGEON_WEIGHT, DEFAULT_ERROR_SCHEMATIC_PATH, true); + defaultUp = new DungeonGenerator(DEFAULT_DUNGEON_WEIGHT, DEFAULT_UP_SCHEMATIC_PATH, true, DungeonType.UNKNOWN_TYPE); + defaultDown = new DungeonGenerator(DEFAULT_DUNGEON_WEIGHT, DEFAULT_DOWN_SCHEMATIC_PATH, true, DungeonType.UNKNOWN_TYPE); + 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); @@ -408,7 +417,7 @@ public class DungeonHelper return names; } - public static ArrayList getDungeonChainHistory(DimData dimData, int maxSize) + public static ArrayList getDungeonChainHistory(DimData dimData, DungeonPack pack, int maxSize) { //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. @@ -418,10 +427,15 @@ public class DungeonHelper ArrayList history = new ArrayList(); DimData tailDim = helper.getDimData(helper.getLinkDataFromCoords(dimData.exitDimLink.destXCoord, dimData.exitDimLink.destYCoord, dimData.exitDimLink.destZCoord, dimData.exitDimLink.destDimID).destDimID); - for (int count = 0; count < maxSize && tailDim.dungeonGenerator != null; count++) + for (int count = 0; count < maxSize; count++) { - history.add(tailDim.dungeonGenerator); + if (tailDim.dungeonGenerator == null || tailDim.dungeonGenerator.getDungeonType().Owner != pack) + { + //We've reached a dimension that doesn't belong to our pack. Stop the search here. + break; + } + history.add(tailDim.dungeonGenerator); if (count + 1 < maxSize) { for (LinkData link : tailDim.getLinksInDim()) diff --git a/StevenDimDoors/mod_pocketDim/util/WeightedContainer.java b/StevenDimDoors/mod_pocketDim/util/WeightedContainer.java index e69e48b..71332f3 100644 --- a/StevenDimDoors/mod_pocketDim/util/WeightedContainer.java +++ b/StevenDimDoors/mod_pocketDim/util/WeightedContainer.java @@ -17,11 +17,15 @@ public class WeightedContainer extends WeightedRandomItem { { super(weight); this.data = data; - super.itemWeight = weight; } public T getData() { return data; } + + public WeightedContainer clone() + { + return new WeightedContainer(data, itemWeight); + } } From 35329f9024a076174951797694e247670e27df38 Mon Sep 17 00:00:00 2001 From: SenseiKiwi Date: Mon, 5 Aug 2013 09:50:00 -0400 Subject: [PATCH 03/10] Adding DungeonChainRuleDefinition For some odd reason, git didn't commit DungeonChainRuleDefinition in my last commit. Adding it again now. --- .../pack/DungeonChainRuleDefinition.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonChainRuleDefinition.java diff --git a/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonChainRuleDefinition.java b/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonChainRuleDefinition.java new file mode 100644 index 0000000..f06cded --- /dev/null +++ b/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonChainRuleDefinition.java @@ -0,0 +1,28 @@ +package StevenDimDoors.mod_pocketDim.dungeon.pack; + +import java.util.ArrayList; + +import StevenDimDoors.mod_pocketDim.util.WeightedContainer; + +public class DungeonChainRuleDefinition +{ + private ArrayList conditions; + private ArrayList> products; + + public DungeonChainRuleDefinition(ArrayList conditions, ArrayList> products) + { + this.conditions = conditions; + this.products = products; + } + + public ArrayList getCondition() + { + return conditions; + } + + public ArrayList> getProducts() + { + return products; + } + +} From f372b9ccb59cb171da2bbd63186f72648f92e146 Mon Sep 17 00:00:00 2001 From: SenseiKiwi Date: Mon, 5 Aug 2013 20:16:45 -0400 Subject: [PATCH 04/10] Basic Configurable Dungeon Chains Completed a basic version of configurable dungeon chains. Almost all of the final funcionality is present. However, the configuration is hardcoded at the moment, not read from a file. This was done for testing purposes. I'll add reading from config files soon. Dungeon packs are partially implemented. Built-in and custom dungeons are currently thrown into the default pack, Ruins. The next step is to generalize the dungeon registration code in DungeonHelper so that we can detect dungeon packs, read their config files, and register dungeons with their corresponding pack. dd-export will need to support packs as well. dd-rift will have issues dealing with duplicate dungeon names across packs, but this isn't a major concern and can be dealt with in the long term. --- .../mod_pocketDim/SchematicLoader.java | 13 ++-- .../dungeon/pack/DungeonChainRule.java | 7 +- .../dungeon/pack/DungeonPack.java | 4 +- .../mod_pocketDim/helpers/DungeonHelper.java | 78 ++++++++++++++----- .../util/BaseConfigurationProcessor.java | 52 +++++++++++++ .../ConfigurationProcessingException.java | 21 +++++ schematics/ruins/rules.txt | 33 ++++++++ 7 files changed, 180 insertions(+), 28 deletions(-) create mode 100644 StevenDimDoors/mod_pocketDim/util/BaseConfigurationProcessor.java create mode 100644 StevenDimDoors/mod_pocketDim/util/ConfigurationProcessingException.java create mode 100644 schematics/ruins/rules.txt diff --git a/StevenDimDoors/mod_pocketDim/SchematicLoader.java b/StevenDimDoors/mod_pocketDim/SchematicLoader.java index 00d1b6a..b1c796a 100644 --- a/StevenDimDoors/mod_pocketDim/SchematicLoader.java +++ b/StevenDimDoors/mod_pocketDim/SchematicLoader.java @@ -43,12 +43,15 @@ public class SchematicLoader if (dimList.get(destDimID).dungeonGenerator == null) { //The following initialization code is based on code from ChunkProviderGenerate. - //It makes our generation depend on the world seed. + //It makes our generation depend on the world seed. We have an additional seed here + //to prevent correlations between the selected dungeons and the locations of gateways. + //TODO: We should centralize RNG initialization and world-seed modifiers for each specific application. - Random random = new Random(world.getSeed()); - long factorA = random.nextLong() / 2L * 2L + 1L; - long factorB = random.nextLong() / 2L * 2L + 1L; - random.setSeed((link.destXCoord >> 4) * factorA + (link.destZCoord >> 4) * factorB ^ world.getSeed()); + final long localSeed = world.getSeed() ^ 0x2F50DB9B4A8057E4L; + final Random random = new Random(); + final long factorA = random.nextLong() / 2L * 2L + 1L; + final long factorB = random.nextLong() / 2L * 2L + 1L; + random.setSeed((link.destXCoord >> 4) * factorA + (link.destZCoord >> 4) * factorB ^ localSeed); dungeonHelper.generateDungeonLink(link, dungeonHelper.RuinsPack, random); } diff --git a/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonChainRule.java b/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonChainRule.java index 472f20b..17554dc 100644 --- a/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonChainRule.java +++ b/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonChainRule.java @@ -15,12 +15,13 @@ public class DungeonChainRule ArrayList conditionNames = source.getCondition(); ArrayList> productNames = source.getProducts(); + //Obtain the IDs of dungeon types in reverse order. Reverse order makes comparing against chain histories easy. condition = new int[conditionNames.size()]; - for (int k = 0; k < condition.length; k++) + for (int src = 0, dst = condition.length - 1; src < condition.length; src++, dst--) { - condition[k] = nameToTypeMapping.get(conditionNames.get(k)).ID; + condition[dst] = nameToTypeMapping.get(conditionNames.get(src)).ID; } - products = new ArrayList>(); + products = new ArrayList>(productNames.size()); for (WeightedContainer product : productNames) { products.add(new WeightedContainer(nameToTypeMapping.get(product.getData()), product.itemWeight )); diff --git a/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonPack.java b/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonPack.java index 9db777d..40bc2c7 100644 --- a/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonPack.java +++ b/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonPack.java @@ -162,11 +162,11 @@ public class DungeonPack { excludedDungeons = new HashSet(history); } - + //List which dungeons are allowed ArrayList candidates; ArrayList group = groupedDungeons.get(nextType.ID); - if (excludedDungeons != null) + if (excludedDungeons != null && !excludedDungeons.isEmpty()) { candidates = new ArrayList(group.size()); for (DungeonGenerator dungeon : group) diff --git a/StevenDimDoors/mod_pocketDim/helpers/DungeonHelper.java b/StevenDimDoors/mod_pocketDim/helpers/DungeonHelper.java index c364297..372979d 100644 --- a/StevenDimDoors/mod_pocketDim/helpers/DungeonHelper.java +++ b/StevenDimDoors/mod_pocketDim/helpers/DungeonHelper.java @@ -9,7 +9,9 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; +import java.util.LinkedList; import java.util.List; +import java.util.Queue; import java.util.Random; import java.util.regex.Pattern; @@ -423,32 +425,72 @@ public class DungeonHelper //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 - dimHelper helper = dimHelper.instance; ArrayList history = new ArrayList(); - DimData tailDim = helper.getDimData(helper.getLinkDataFromCoords(dimData.exitDimLink.destXCoord, dimData.exitDimLink.destYCoord, dimData.exitDimLink.destZCoord, dimData.exitDimLink.destDimID).destDimID); + DimData tailDim = dimData; + boolean found = true; - for (int count = 0; count < maxSize; count++) + if (dimData.dungeonGenerator == null || dimData.dungeonGenerator.getDungeonType().Owner != pack || maxSize < 1) { - if (tailDim.dungeonGenerator == null || tailDim.dungeonGenerator.getDungeonType().Owner != pack) + //The initial dimension is already outside our pack. Return an empty list. + return history; + } + history.add(dimData.dungeonGenerator); + + for (int count = 1; count < maxSize && found; count++) + { + found = false; + for (LinkData link : tailDim.getLinksInDim()) { - //We've reached a dimension that doesn't belong to our pack. Stop the search here. - break; - } - - history.add(tailDim.dungeonGenerator); - if (count + 1 < maxSize) - { - 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) { - DimData nextDim = dimHelper.instance.getDimData(link.destDimID); - if (helper.getDimDepth(link.destDimID) == tailDim.depth + 1) - { - tailDim = nextDim; - break; - } + tailDim = neighbor; + history.add(tailDim.dungeonGenerator); + found = true; + break; } } } return history; } + + 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 + //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 + + dimHelper helper = dimHelper.instance; + ArrayList dungeons = new ArrayList(); + DimData root = helper.getDimData(helper.getLinkDataFromCoords(dimData.exitDimLink.destXCoord, dimData.exitDimLink.destYCoord, dimData.exitDimLink.destZCoord, dimData.exitDimLink.destDimID).destDimID); + HashSet checked = new HashSet(); + Queue pendingDimensions = new LinkedList(); + + if (root.dungeonGenerator == null) + { + return dungeons; + } + pendingDimensions.add(root); + checked.add(root); + + while (dungeons.size() < maxSize && !pendingDimensions.isEmpty()) + { + DimData current = pendingDimensions.remove(); + for (LinkData link : current.getLinksInDim()) + { + DimData child = helper.getDimData(link.destDimID); + if (child.depth == current.depth + 1 && child.dungeonGenerator != null && checked.add(child)) + { + dungeons.add(child.dungeonGenerator); + pendingDimensions.add(child); + } + if (dungeons.size() == maxSize) + { + break; + } + } + } + return dungeons; + } } \ No newline at end of file diff --git a/StevenDimDoors/mod_pocketDim/util/BaseConfigurationProcessor.java b/StevenDimDoors/mod_pocketDim/util/BaseConfigurationProcessor.java new file mode 100644 index 0000000..8fe75a2 --- /dev/null +++ b/StevenDimDoors/mod_pocketDim/util/BaseConfigurationProcessor.java @@ -0,0 +1,52 @@ +package StevenDimDoors.mod_pocketDim.util; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; + +public abstract class BaseConfigurationProcessor +{ + public BaseConfigurationProcessor() { } + + public boolean canRead() + { + return true; + } + + public boolean canWrite() + { + return true; + } + + public T readFromFile(String path) throws FileNotFoundException, ConfigurationProcessingException + { + return readFromFile(new File(path)); + } + + public T readFromFile(File file) throws FileNotFoundException, ConfigurationProcessingException + { + return readFromStream(new FileInputStream(file)); + } + + public T readFromResource(String resourcePath) throws ConfigurationProcessingException + { + return readFromStream(this.getClass().getResourceAsStream(resourcePath)); + } + + public abstract T readFromStream(InputStream inputStream) throws ConfigurationProcessingException; + + public void writeToFile(File file, T data) throws FileNotFoundException, ConfigurationProcessingException + { + writeToStream(new FileOutputStream(file), data); + } + + public void writeToFile(String path, T data) throws FileNotFoundException, ConfigurationProcessingException + { + writeToFile(new File(path), data); + } + + public abstract void writeToStream(OutputStream outputStream, T data) throws ConfigurationProcessingException; +} diff --git a/StevenDimDoors/mod_pocketDim/util/ConfigurationProcessingException.java b/StevenDimDoors/mod_pocketDim/util/ConfigurationProcessingException.java new file mode 100644 index 0000000..1793f22 --- /dev/null +++ b/StevenDimDoors/mod_pocketDim/util/ConfigurationProcessingException.java @@ -0,0 +1,21 @@ +package StevenDimDoors.mod_pocketDim.util; + +public class ConfigurationProcessingException extends Exception +{ + private static final long serialVersionUID = -4525298050874891911L; + + public ConfigurationProcessingException() + { + super(); + } + + public ConfigurationProcessingException(String message) + { + super(message); + } + + public ConfigurationProcessingException(String message, Throwable cause) + { + super(message, cause); + } +} diff --git a/schematics/ruins/rules.txt b/schematics/ruins/rules.txt new file mode 100644 index 0000000..086036a --- /dev/null +++ b/schematics/ruins/rules.txt @@ -0,0 +1,33 @@ +Version 1 +Types: +Hub +Trap +SimpleHall +ComplexHall +Exit +DeadEnd +Maze + +Settings: +AllowRepetitionsInBranch = false +AllowPackChangeOut = true +AllowPackChangeIn = true +PackWeight = 100 + +Rules: + +Exit -> DeadEnd Exit + +DeadEnd -> DeadEnd Exit + +? ? ? ? ? ? ? ? -> Trap#20 SimpleHall#40 ComplexHall#10 Exit#20 DeadEnd#10 + +? ? ? ? -> Trap#18 SimpleHall#40 ComplexHall#10 Exit#18 DeadEnd#10 Hub#4 + +? ? ? -> ComplexHall Hub Trap SimpleHall Maze + +? ? -> ComplexHall Hub Trap SimpleHall Maze + +? -> ComplexHall#40 Hub#30 Trap#10 SimpleHall#10 Maze#10 + +-> ComplexHall#40 Hub#30 Trap#10 SimpleHall#10 Maze#10 \ No newline at end of file From 6b69d3d1386435b78a2b3e0b944d4c63f3b2b797 Mon Sep 17 00:00:00 2001 From: SenseiKiwi Date: Wed, 7 Aug 2013 22:11:10 -0400 Subject: [PATCH 05/10] Added Nether Gateway Chance Correction Modified RiftGenerator to correct for the rarity of Rift Gateways in the Nether. Our config settings allow us to set the probability that we will attempt to generate a gateway in a given chunk. However, that doesn't guarantee that a gateway will generate. I collected a lot of data and determined that generation succeeds in the Nether only about 15% of the time. That's compared to 30% in the Overworld when counting oceans (which always fail) and about 75% (sometimes higher) when traveling mainly on land. RiftGenerator now corrects for this by multiplying the chance of attempting to generate gateways in the Nether by 4. Gateways in the Nether are still relatively rare and hard to find, but you'll occasionally come across them now. Also reorganized the code a little for clarify. --- .../mod_pocketDim/RiftGenerator.java | 125 +++++++++++------- 1 file changed, 79 insertions(+), 46 deletions(-) diff --git a/StevenDimDoors/mod_pocketDim/RiftGenerator.java b/StevenDimDoors/mod_pocketDim/RiftGenerator.java index 7b7f36e..1009b31 100644 --- a/StevenDimDoors/mod_pocketDim/RiftGenerator.java +++ b/StevenDimDoors/mod_pocketDim/RiftGenerator.java @@ -24,6 +24,9 @@ public class RiftGenerator implements IWorldGenerator private static final int CHUNK_LENGTH = 16; private static final int GATEWAY_RADIUS = 4; private static final int MAX_GATEWAY_GENERATION_ATTEMPTS = 10; + private static final int NETHER_CHANCE_CORRECTION = 4; + private static final int OVERWORLD_DIMENSION_ID = 0; + private static final int NETHER_DIMENSION_ID = -1; private static DDProperties properties = null; public RiftGenerator() @@ -43,16 +46,30 @@ public class RiftGenerator implements IWorldGenerator return; } //This check prevents a crash related to superflat worlds not loading World 0 - if (dimHelper.getWorld(0) == null) + if (dimHelper.getWorld(OVERWORLD_DIMENSION_ID) == null) { return; } int x, y, z; int attempts; - int blockID; + int correction; boolean valid; LinkData link; + + //Check if we're generating things in the Nether + if (world.provider.dimensionId == NETHER_DIMENSION_ID) + { + //The terrain in the Nether makes it much harder for our gateway spawning algorithm to find a spot to place a gateway. + //Tests show that only about 15% of attempts succeed. Compensate for this by multiplying the chance of generation + //by a correction factor. + correction = NETHER_CHANCE_CORRECTION; + } + else + { + //No correction + correction = 1; + } //Randomly decide whether to place a cluster of rifts here if (random.nextInt(MAX_CLUSTER_GENERATION_CHANCE) < properties.ClusterGenerationChance) @@ -91,7 +108,8 @@ public class RiftGenerator implements IWorldGenerator //Check if generating structures is enabled and randomly decide whether to place a Rift Gateway here. //This only happens if a rift cluster was NOT generated. - else if (random.nextInt(MAX_GATEWAY_GENERATION_CHANCE) < properties.GatewayGenerationChance && isStructureGenerationAllowed()) + else if (random.nextInt(MAX_GATEWAY_GENERATION_CHANCE) < properties.GatewayGenerationChance * correction && + isStructureGenerationAllowed()) { valid = false; x = y = z = 0; //Stop the compiler from freaking out @@ -117,59 +135,74 @@ public class RiftGenerator implements IWorldGenerator //If the current dimension isn't Limbo, build a Rift Gateway out of Stone Bricks if (world.provider.dimensionId != properties.LimboDimensionID) { - blockID = Block.stoneBrick.blockID; - - //Replace some of the ground around the gateway with bricks - for (int xc = -GATEWAY_RADIUS; xc <= GATEWAY_RADIUS; xc++) - { - for (int zc= -GATEWAY_RADIUS; zc <= GATEWAY_RADIUS; zc++) - { - //Check that the block is supported by an opaque block. - //This prevents us from building over a cliff, on the peak of a mountain, - //or the surface of the ocean or a frozen lake. - if (world.isBlockOpaqueCube(x + xc, y - 2, z + zc)) - { - //Randomly choose whether to place bricks or not. The math is designed so that the - //chances of placing a block decrease as we get farther from the gateway's center. - if (Math.abs(xc) + Math.abs(zc) < random.nextInt(2) + 3) - { - //Place Stone Bricks - world.setBlock(x + xc, y - 1, z + zc, blockID, 0, 3); - } - else if (Math.abs(xc) + Math.abs(zc) < random.nextInt(3) + 3) - { - //Place Cracked Stone Bricks - world.setBlock(x + xc, y - 1, z + zc, blockID, 2, 3); - } - } - } - } - - //Use Chiseled Stone Bricks to top off the pillars around the door - world.setBlock(x, y + 2, z + 1, blockID, 3, 3); - world.setBlock(x, y + 2, z - 1, blockID, 3, 3); + createStoneGateway(world, x, y, z, random); } else { - //Build the gateway out of Unraveled Fabric. Since nearly all the blocks in Limbo are of - //that type, there is no point replacing the ground. Just build the tops of the columns here. - blockID = properties.LimboBlockID; - world.setBlock(x, y + 2, z + 1, blockID, 0, 3); - world.setBlock(x, y + 2, z - 1, blockID, 0, 3); + createLimboGateway(world, x, y, z); } //Place the shiny transient door into a dungeon itemDimDoor.placeDoorBlock(world, x, y + 1, z, 0, mod_pocketDim.transientDoor); - - //Build the columns around the door - world.setBlock(x, y + 1, z - 1, blockID, 0, 3); - world.setBlock(x, y + 1, z + 1, blockID, 0, 3); - world.setBlock(x, y, z - 1, blockID, 0, 3); - world.setBlock(x, y, z + 1, blockID, 0, 3); } } } + private static void createStoneGateway(World world, int x, int y, int z, Random random) + { + final int blockID = Block.stoneBrick.blockID; + + //Replace some of the ground around the gateway with bricks + for (int xc = -GATEWAY_RADIUS; xc <= GATEWAY_RADIUS; xc++) + { + for (int zc= -GATEWAY_RADIUS; zc <= GATEWAY_RADIUS; zc++) + { + //Check that the block is supported by an opaque block. + //This prevents us from building over a cliff, on the peak of a mountain, + //or the surface of the ocean or a frozen lake. + if (world.isBlockOpaqueCube(x + xc, y - 2, z + zc)) + { + //Randomly choose whether to place bricks or not. The math is designed so that the + //chances of placing a block decrease as we get farther from the gateway's center. + if (Math.abs(xc) + Math.abs(zc) < random.nextInt(2) + 3) + { + //Place Stone Bricks + world.setBlock(x + xc, y - 1, z + zc, blockID, 0, 3); + } + else if (Math.abs(xc) + Math.abs(zc) < random.nextInt(3) + 3) + { + //Place Cracked Stone Bricks + world.setBlock(x + xc, y - 1, z + zc, blockID, 2, 3); + } + } + } + } + + //Use Chiseled Stone Bricks to top off the pillars around the door + world.setBlock(x, y + 2, z + 1, blockID, 3, 3); + world.setBlock(x, y + 2, z - 1, blockID, 3, 3); + //Build the columns around the door + world.setBlock(x, y + 1, z - 1, blockID, 0, 3); + world.setBlock(x, y + 1, z + 1, blockID, 0, 3); + world.setBlock(x, y, z - 1, blockID, 0, 3); + world.setBlock(x, y, z + 1, blockID, 0, 3); + } + + private static void createLimboGateway(World world, int x, int y, int z) + { + //Build the gateway out of Unraveled Fabric. Since nearly all the blocks in Limbo are of + //that type, there is no point replacing the ground. + final int blockID = properties.LimboBlockID; + world.setBlock(x, y + 2, z + 1, blockID, 0, 3); + world.setBlock(x, y + 2, z - 1, blockID, 0, 3); + + //Build the columns around the door + world.setBlock(x, y + 1, z - 1, blockID, 0, 3); + world.setBlock(x, y + 1, z + 1, blockID, 0, 3); + world.setBlock(x, y, z - 1, blockID, 0, 3); + world.setBlock(x, y, z + 1, blockID, 0, 3); + } + private static boolean checkGatewayLocation(World world, int x, int y, int z) { //Check if the point is within the acceptable altitude range, the block above that point is empty, @@ -195,6 +228,6 @@ public class RiftGenerator implements IWorldGenerator private static boolean isStructureGenerationAllowed() { - return DimensionManager.getWorld(0).getWorldInfo().isMapFeaturesEnabled(); + return DimensionManager.getWorld(OVERWORLD_DIMENSION_ID).getWorldInfo().isMapFeaturesEnabled(); } } From 7537d6cd7a5c3efd40e2ab90da70715a15b28f78 Mon Sep 17 00:00:00 2001 From: SenseiKiwi Date: Thu, 15 Aug 2013 05:35:03 -0400 Subject: [PATCH 06/10] Decreased Default Gateway Generation Chance Decreased the default chance of a gateway generating. This won't affect users unless they clear their config files. The decision is based on user feedback and recent experiments on how common gateways could be. --- StevenDimDoors/mod_pocketDim/DDProperties.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/StevenDimDoors/mod_pocketDim/DDProperties.java b/StevenDimDoors/mod_pocketDim/DDProperties.java index 243be95..a10cb31 100644 --- a/StevenDimDoors/mod_pocketDim/DDProperties.java +++ b/StevenDimDoors/mod_pocketDim/DDProperties.java @@ -214,9 +214,9 @@ public class DDProperties "Sets the chance (out of " + RiftGenerator.MAX_CLUSTER_GENERATION_CHANCE + ") that a cluster of rifts will " + "generate in a given chunk. The default chance is 3.").getInt(); - GatewayGenerationChance = config.get(Configuration.CATEGORY_GENERAL, "Gateway Generation Chance", 40, + GatewayGenerationChance = config.get(Configuration.CATEGORY_GENERAL, "Gateway Generation Chance", 10, "Sets the chance (out of " + RiftGenerator.MAX_GATEWAY_GENERATION_CHANCE + ") that a Rift Gateway will " + - "generate in a given chunk. The default chance is 40.").getInt(); + "generate in a given chunk. The default chance is 10.").getInt(); RiftSpreadModifier = config.get(Configuration.CATEGORY_GENERAL, "Rift Spread Modifier", 3, "Sets the number of times a rift can spread. 0 prevents rifts from spreading at all. " + From dc7289a04817a2f25160f193ab0308c2e1cc7234 Mon Sep 17 00:00:00 2001 From: SenseiKiwi Date: Thu, 15 Aug 2013 07:29:37 -0400 Subject: [PATCH 07/10] Dungeon Selection Fix and Chain Config Change Changed how Random is initialized in SchematicLoader to prevent issues with dim doors in the same chunks leading to the same dungeon. The selection seems much more varied now. Also changed the hardcoded config for the default dungeon chains to the one we'll be using later (from a file). For testing purposes. --- .../mod_pocketDim/SchematicLoader.java | 51 +++++++++++++++---- .../mod_pocketDim/helpers/DungeonHelper.java | 15 ++++-- 2 files changed, 53 insertions(+), 13 deletions(-) diff --git a/StevenDimDoors/mod_pocketDim/SchematicLoader.java b/StevenDimDoors/mod_pocketDim/SchematicLoader.java index b1c796a..f5006f4 100644 --- a/StevenDimDoors/mod_pocketDim/SchematicLoader.java +++ b/StevenDimDoors/mod_pocketDim/SchematicLoader.java @@ -42,16 +42,9 @@ public class SchematicLoader if (dimList.get(destDimID).dungeonGenerator == null) { - //The following initialization code is based on code from ChunkProviderGenerate. - //It makes our generation depend on the world seed. We have an additional seed here - //to prevent correlations between the selected dungeons and the locations of gateways. //TODO: We should centralize RNG initialization and world-seed modifiers for each specific application. - - final long localSeed = world.getSeed() ^ 0x2F50DB9B4A8057E4L; - final Random random = new Random(); - final long factorA = random.nextLong() / 2L * 2L + 1L; - final long factorB = random.nextLong() / 2L * 2L + 1L; - random.setSeed((link.destXCoord >> 4) * factorA + (link.destZCoord >> 4) * factorB ^ localSeed); + final long localSeed = world.getSeed() ^ 0x2F50DB9B4A8057E4L ^ computeDestinationHash(link); + final Random random = new Random(localSeed); dungeonHelper.generateDungeonLink(link, dungeonHelper.RuinsPack, random); } @@ -160,4 +153,44 @@ public class SchematicLoader } return dungeon; } + + private static long computeDestinationHash(LinkData link) + { + //Time for some witchcraft. + //The code here is inspired by a discussion on Stack Overflow regarding hash codes for 3D. + //Source: http://stackoverflow.com/questions/9858376/hashcode-for-3d-integer-coordinates-with-high-spatial-coherence + + //Use 8 bits from Y and 24 bits from X and Z. Mix in 8 bits from the destination dim ID too - that means + //even if you aligned two doors perfectly between two pockets, it's unlikely they would lead to the same dungeon. + + int bit; + int index; + long hash; + int w = link.destDimID; + int x = link.destXCoord; + int y = link.destYCoord; + int z = link.destZCoord; + + hash = 0; + index = 0; + for (bit = 0; bit < 8; bit++) + { + hash |= ((w >> bit) & 1) << index; + index++; + hash |= ((x >> bit) & 1) << index; + index++; + hash |= ((y >> bit) & 1) << index; + index++; + hash |= ((z >> bit) & 1) << index; + index++; + } + for (; bit < 24; bit++) + { + hash |= ((x >> bit) & 1) << index; + index++; + hash |= ((z >> bit) & 1) << index; + index++; + } + return hash; + } } \ No newline at end of file diff --git a/StevenDimDoors/mod_pocketDim/helpers/DungeonHelper.java b/StevenDimDoors/mod_pocketDim/helpers/DungeonHelper.java index 372979d..ae5e6c1 100644 --- a/StevenDimDoors/mod_pocketDim/helpers/DungeonHelper.java +++ b/StevenDimDoors/mod_pocketDim/helpers/DungeonHelper.java @@ -114,10 +114,17 @@ public class DungeonHelper //It'll be removed later when we read dungeon configurations from files. ArrayList rules = new ArrayList(); - rules.add(parseDefinitionUnsafe("? ? ? -> DeadEnd Exit")); - rules.add(parseDefinitionUnsafe("Trap -> ?")); - rules.add(parseDefinitionUnsafe("Hub -> Trap")); - rules.add(parseDefinitionUnsafe("? -> Hub")); + + rules.add(parseDefinitionUnsafe("? ? ? ? ? ? ? ? -> Trap#20 SimpleHall#40 ComplexHall#10 Exit#20 DeadEnd#10")); + + rules.add(parseDefinitionUnsafe("? ? ? ? -> Trap#18 SimpleHall#40 ComplexHall#10 Exit#18 DeadEnd#10 Hub#4")); + + rules.add(parseDefinitionUnsafe("? ? ? -> ComplexHall Hub Trap SimpleHall Maze")); + + rules.add(parseDefinitionUnsafe("? ? -> ComplexHall Hub Trap SimpleHall Maze")); + + rules.add(parseDefinitionUnsafe("? -> ComplexHall#40 Hub#30 Trap#10 SimpleHall#10 Maze#10")); + rules.add(parseDefinitionUnsafe("-> ComplexHall#40 Hub#30 Trap#10 SimpleHall#10 Maze#10")); String[] typeNames = "Hub Trap Maze Exit DeadEnd SimpleHall ComplexHall".toUpperCase().split(" "); From caf33bd866487882b70450bd9aeff5edb4103609 Mon Sep 17 00:00:00 2001 From: SenseiKiwi Date: Sat, 17 Aug 2013 20:32:30 -0400 Subject: [PATCH 08/10] Improved EventHookContainer Made some changes to EventHookContainer.onWorldLoad() to remove redundant code that encouraged bugs. Unfortunately, a lot of link-related code needs to be rewritten to get rid of bugs, so that'll come after dungeon packs are completed. --- .../mod_pocketDim/EventHookContainer.java | 38 ++++++++----------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/StevenDimDoors/mod_pocketDim/EventHookContainer.java b/StevenDimDoors/mod_pocketDim/EventHookContainer.java index 95742f3..d2e12b1 100644 --- a/StevenDimDoors/mod_pocketDim/EventHookContainer.java +++ b/StevenDimDoors/mod_pocketDim/EventHookContainer.java @@ -46,32 +46,26 @@ public class EventHookContainer dimHelper.instance.interDimLinkList.clear(); dimHelper.instance.initPockets(); } - for (Integer ids : dimHelper.getIDs()) - { - World world = dimHelper.getWorld(ids); + + //TODO: In the future, we should iterate over DimHelper's dimension list. We ignore other dimensions anyway. + for (int dimensionID : dimHelper.getIDs()) + { + World world = dimHelper.getWorld(dimensionID); int linkCount = 0; - if (dimHelper.dimList.containsKey(world.provider.dimensionId)) + if (dimHelper.dimList.containsKey(dimensionID)) { - //TODO added temporary Try/catch block to prevent a crash here, getLinksInDim needs to be looked at - try - { - for (LinkData link:dimHelper.instance.getDimData(world.provider.dimensionId).getLinksInDim()) - { - if (!mod_pocketDim.blockRift.isBlockImmune(world, link.locXCoord, link.locYCoord, link.locZCoord)) - { - dimHelper.getWorld(link.locDimID).setBlock(link.locXCoord, link.locYCoord, link.locZCoord, properties.RiftBlockID); - } - linkCount++; - if (linkCount >= 100) - { - break; - } - } - } - catch(Exception e) + for (LinkData link : dimHelper.instance.getDimData(dimensionID).getLinksInDim()) { - e.printStackTrace(); + if (!mod_pocketDim.blockRift.isBlockImmune(world, link.locXCoord, link.locYCoord, link.locZCoord)) + { + world.setBlock(link.locXCoord, link.locYCoord, link.locZCoord, properties.RiftBlockID); + } + linkCount++; + if (linkCount >= 100) + { + break; + } } } } From acab06115abf45c7753b84dc99dbc51fab67a90d Mon Sep 17 00:00:00 2001 From: SenseiKiwi Date: Tue, 20 Aug 2013 18:54:30 -0400 Subject: [PATCH 09/10] Progress on Implementing Dungeon Packs Added code for parsing dungeon pack config files. The settings for our built-in dungeons are now read from a file instead of being hardcoded. One or two settings aren't being accessed yet and we still don't search for other dungeon packs in the custom dungeon folder. That'll come in another commit. --- .../dungeon/pack/DungeonPackConfig.java | 48 +++ .../dungeon/pack/DungeonPackConfigReader.java | 386 ++++++++++++++++++ .../mod_pocketDim/helpers/DungeonHelper.java | 62 +-- schematics/ruins/rules.txt | 8 +- 4 files changed, 452 insertions(+), 52 deletions(-) create mode 100644 StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonPackConfigReader.java diff --git a/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonPackConfig.java b/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonPackConfig.java index c99dd91..842c94c 100644 --- a/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonPackConfig.java +++ b/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonPackConfig.java @@ -7,6 +7,10 @@ public class DungeonPackConfig private String name; private ArrayList typeNames; private boolean allowDuplicatesInChain; + private boolean allowPackChangeIn; + private boolean allowPackChangeOut; + private boolean distortDoorCoordinates; + private int packWeight; private ArrayList rules; public DungeonPackConfig() { } @@ -17,6 +21,10 @@ public class DungeonPackConfig this.name = source.name; this.typeNames = (ArrayList) source.typeNames.clone(); 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(); } @@ -75,4 +83,44 @@ public class DungeonPackConfig { return rules; } + + public boolean allowPackChangeIn() + { + return allowPackChangeIn; + } + + public void setAllowPackChangeIn(boolean value) + { + this.allowPackChangeIn = value; + } + + public boolean allowPackChangeOut() + { + return allowPackChangeOut; + } + + public void setAllowPackChangeOut(boolean value) + { + this.allowPackChangeOut = value; + } + + public int getPackWeight() + { + return packWeight; + } + + public void setPackWeight(int packWeight) + { + this.packWeight = packWeight; + } + + public boolean getDistortDoorCoordinates() + { + return distortDoorCoordinates; + } + + public void setDistortDoorCoordinates(boolean value) + { + this.distortDoorCoordinates = value; + } } diff --git a/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonPackConfigReader.java b/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonPackConfigReader.java new file mode 100644 index 0000000..cf907ef --- /dev/null +++ b/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonPackConfigReader.java @@ -0,0 +1,386 @@ +package StevenDimDoors.mod_pocketDim.dungeon.pack; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +import StevenDimDoors.mod_pocketDim.util.BaseConfigurationProcessor; +import StevenDimDoors.mod_pocketDim.util.ConfigurationProcessingException; +import StevenDimDoors.mod_pocketDim.util.WeightedContainer; + +import com.google.common.base.CharMatcher; +import com.google.common.base.Splitter; +import com.google.common.primitives.Ints; + +public class DungeonPackConfigReader extends BaseConfigurationProcessor +{ + private interface ILineProcessor + { + public void process(String line, DungeonPackConfig config) throws ConfigurationProcessingException; + } + + //Note: These constants aren't static so that the memory will be released once + //we're done using it an instance. These aren't objects that we need to hold + //onto throughout the lifetime of MC, only at loading time. + + private final int CONFIG_VERSION = 1; + private final int LOOKAHEAD_LIMIT = 1024; + private final int MAX_PRODUCT_WEIGHT = 10000; + private final int DEFAULT_PRODUCT_WEIGHT = 100; + private final int MAX_DUNGEON_PACK_WEIGHT = 10000; + private final int DEFAULT_DUNGEON_PACK_WEIGHT = 100; + private final String COMMENT_MARKER = "##"; + + private final Pattern DUNGEON_TYPE_PATTERN = Pattern.compile("[A-Za-z0-9_\\-]{1,20}"); + + private final Splitter WHITESPACE_SPLITTER = Splitter.on(CharMatcher.WHITESPACE).omitEmptyStrings(); + private final String SETTING_SEPARATOR = "="; + private final String RULE_SEPARATOR = "->"; + private final String WEIGHT_SEPARATOR = "#"; + + public DungeonPackConfigReader() { } + + @Override + public DungeonPackConfig readFromStream(InputStream inputStream) throws ConfigurationProcessingException + { + BufferedReader reader = null; + try + { + DungeonPackConfig config = new DungeonPackConfig(); + reader = new BufferedReader(new InputStreamReader(inputStream)); + + //Check the config format version + int version = readVersion(reader); + if (version != CONFIG_VERSION) + { + throw new ConfigurationProcessingException("The dungeon pack config has an incompatible version."); + } + + config.setTypeNames(new ArrayList()); + config.setRules(new ArrayList()); + + //Read the dungeon types + if (findSection("Types", reader)) + { + processLines(reader, config, new DungeonTypeProcessor()); + } + + //Load default settings + config.setAllowDuplicatesInChain(true); + config.setAllowPackChangeIn(true); + config.setAllowPackChangeOut(true); + config.setDistortDoorCoordinates(false); + config.setPackWeight(DEFAULT_DUNGEON_PACK_WEIGHT); + + //Read the settings section + if (findSection("Settings", reader)) + { + processLines(reader, config, new DungeonSettingsParser()); + } + + //Read the rules section + if (findSection("Rules", reader)) + { + processLines(reader, config, new RuleDefinitionParser()); + } + + return config; + } + catch (ConfigurationProcessingException ex) + { + throw ex; + } + catch (Exception ex) + { + throw new ConfigurationProcessingException("An unexpected error occurred while trying to read the configuration file.", ex); + } + finally + { + if (reader != null) + { + try + { + reader.close(); + } + catch (IOException ex) { } + } + } + } + + private int readVersion(BufferedReader reader) throws ConfigurationProcessingException, IOException + { + String firstLine = reader.readLine(); + String[] parts = firstLine.split("\\s", 0); + Integer version = null; + + if (parts.length == 2 && parts[0].equalsIgnoreCase("version")) + { + version = Ints.tryParse(parts[1]); + } + + if (version == null) + { + throw new ConfigurationProcessingException("Could not parse the config format version."); + } + return version; + } + + private void processLines(BufferedReader reader, DungeonPackConfig config, ILineProcessor processor) throws IOException, ConfigurationProcessingException + { + String line; + + while (reader.ready()) + { + reader.mark(LOOKAHEAD_LIMIT); + line = reader.readLine(); + if (!line.startsWith(COMMENT_MARKER)) + { + line = line.trim(); + if (line.length() > 0) + { + if (line.endsWith(":")) + { + //Consider this line a section header, reset the reader to undo consuming it + reader.reset(); + break; + } + else + { + processor.process(line, config); + } + } + } + } + } + + private boolean findSection(String name, BufferedReader reader) throws IOException, ConfigurationProcessingException + { + boolean found = false; + boolean matched = false; + String line = null; + String label = name + ":"; + + //Find the next section header + //Ignore blank lines and comment lines, stop for headers, and throw an exception for anything else + while (!found && reader.ready()) + { + reader.mark(LOOKAHEAD_LIMIT); + line = reader.readLine(); + if (!line.startsWith(COMMENT_MARKER)) + { + line = line.trim(); + if (line.length() > 0) + { + if (line.endsWith(":")) + { + //Consider this line a section header + found = true; + matched = line.equalsIgnoreCase(label); + } + else + { + //This line is invalid + throw new ConfigurationProcessingException("The dungeon pack config has an incorrect line where a section was expected: " + line); + } + } + } + } + + //Check if the header matches the one we're looking for. + //If it doesn't match, undo consuming the line so it can be read later. + if (found && !matched) + { + reader.reset(); + } + return found; + } + + private class DungeonTypeProcessor implements ILineProcessor + { + public void process(String line, DungeonPackConfig config) throws ConfigurationProcessingException + { + List typeNames = config.getTypeNames(); + + //Check if the dungeon type has a name that meets our restrictions + if (DUNGEON_TYPE_PATTERN.matcher(line).matches()) + { + //Ignore duplicate dungeon types + line = line.toUpperCase(); + if (!typeNames.contains(line)) + { + typeNames.add(line); + } + } + else + { + throw new ConfigurationProcessingException("The dungeon pack config has a dungeon type with illegal characters in its name: " + line); + } + } + } + + private class DungeonSettingsParser implements ILineProcessor + { + public void process(String line, DungeonPackConfig config) throws ConfigurationProcessingException + { + //The various settings that we support will be hardcoded here. + //In the future, if we get more settings, then this should be + //refactored to use a more lookup-driven approach. + + boolean valid = true; + String[] settingParts = line.split(SETTING_SEPARATOR, 2); + if (settingParts.length == 2) + { + try + { + String name = settingParts[0]; + String value = settingParts[1]; + if (name.equalsIgnoreCase("AllowDuplicatesInChain")) + { + config.setAllowDuplicatesInChain(parseBoolean(value)); + } + else if (name.equalsIgnoreCase("AllowPackChangeOut")) + { + config.setAllowPackChangeOut(parseBoolean(value)); + } + else if (name.equalsIgnoreCase("AllowPackChangeIn")) + { + config.setAllowPackChangeIn(parseBoolean(value)); + } + else if (name.equalsIgnoreCase("DistortDoorCoordinates")) + { + config.setDistortDoorCoordinates(parseBoolean(value)); + } + else if (name.equalsIgnoreCase("PackWeight")) + { + int weight = Integer.parseInt(value); + if (weight >= 0 && weight <= MAX_DUNGEON_PACK_WEIGHT) + { + config.setPackWeight(weight); + } + else + { + valid = false; + } + } + } + catch (Exception e) + { + valid = false; + } + } + else + { + valid = false; + } + + if (!valid) + { + throw new ConfigurationProcessingException("The dungeon pack config has an invalid setting: " + line); + } + } + } + + private class RuleDefinitionParser implements ILineProcessor + { + public void process(String definition, DungeonPackConfig config) throws ConfigurationProcessingException + { + String[] ruleParts; + String[] productParts; + String ruleCondition; + String ruleProduct; + ArrayList condition; + ArrayList> products; + List typeNames = config.getTypeNames(); + + ruleParts = definition.toUpperCase().split(RULE_SEPARATOR, -1); + if (ruleParts.length != 2) + { + throw new ConfigurationProcessingException("The dungeon pack config has an invalid rule: " + definition); + } + + ruleCondition = ruleParts[0]; + ruleProduct = ruleParts[1]; + condition = new ArrayList(); + products = new ArrayList>(); + + for (String typeName : WHITESPACE_SPLITTER.split(ruleCondition)) + { + if (isKnownDungeonType(typeName, typeNames)) + { + condition.add(typeName); + } + else + { + throw new ConfigurationProcessingException("The dungeon pack config has a rule condition with an unknown dungeon type: " + typeName); + } + } + + for (String product : WHITESPACE_SPLITTER.split(ruleProduct)) + { + Integer weight; + String typeName; + + productParts = product.split(WEIGHT_SEPARATOR, -1); + if (productParts.length > 2 || productParts.length == 0) + { + throw new ConfigurationProcessingException("The dungeon pack config has a rule with an invalid product: " + product); + } + + typeName = productParts[0]; + if (isKnownDungeonType(typeName, typeNames)) + { + if (productParts.length > 1) + { + weight = Ints.tryParse(productParts[1]); + if (weight == null || (weight > MAX_PRODUCT_WEIGHT) || (weight < 0)) + { + throw new ConfigurationProcessingException("The dungeon pack config has a rule with an invalid product weight: " + product); + } + } + else + { + weight = DEFAULT_PRODUCT_WEIGHT; + } + products.add(new WeightedContainer(typeName, weight)); + } + else + { + throw new ConfigurationProcessingException("The dungeon pack config has an unknown dungeon type in a rule: " + typeName); + } + } + config.getRules().add( new DungeonChainRuleDefinition(condition, products) ); + } + } + + private static boolean isKnownDungeonType(String typeName, List typeNames) + { + return typeName.equals(DungeonType.WILDCARD_TYPE.Name) || typeNames.contains(typeName); + } + + private static boolean parseBoolean(String value) + { + if (value.equalsIgnoreCase("true")) + return true; + if (value.equalsIgnoreCase("false")) + return false; + throw new IllegalArgumentException("The boolean value must be either \"true\" or \"false\", ignoring case."); + } + + @Override + public boolean canWrite() + { + return false; + } + + @Override + public void writeToStream(OutputStream outputStream, DungeonPackConfig data) + throws ConfigurationProcessingException + { + throw new UnsupportedOperationException("DungeonPackConfigReader does not support writing."); + } +} diff --git a/StevenDimDoors/mod_pocketDim/helpers/DungeonHelper.java b/StevenDimDoors/mod_pocketDim/helpers/DungeonHelper.java index ae5e6c1..4a4b9a4 100644 --- a/StevenDimDoors/mod_pocketDim/helpers/DungeonHelper.java +++ b/StevenDimDoors/mod_pocketDim/helpers/DungeonHelper.java @@ -5,7 +5,6 @@ import java.io.File; import java.io.InputStream; import java.io.InputStreamReader; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; @@ -22,12 +21,12 @@ import StevenDimDoors.mod_pocketDim.DungeonGenerator; import StevenDimDoors.mod_pocketDim.LinkData; import StevenDimDoors.mod_pocketDim.mod_pocketDim; import StevenDimDoors.mod_pocketDim.dungeon.DungeonSchematic; -import StevenDimDoors.mod_pocketDim.dungeon.pack.DungeonChainRuleDefinition; import StevenDimDoors.mod_pocketDim.dungeon.pack.DungeonPack; import StevenDimDoors.mod_pocketDim.dungeon.pack.DungeonPackConfig; +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.WeightedContainer; +import StevenDimDoors.mod_pocketDim.util.ConfigurationProcessingException; public class DungeonHelper { @@ -112,56 +111,21 @@ public class DungeonHelper { //This is a temporarily function for testing dungeon packs. //It'll be removed later when we read dungeon configurations from files. - - ArrayList rules = new ArrayList(); - - rules.add(parseDefinitionUnsafe("? ? ? ? ? ? ? ? -> Trap#20 SimpleHall#40 ComplexHall#10 Exit#20 DeadEnd#10")); - - rules.add(parseDefinitionUnsafe("? ? ? ? -> Trap#18 SimpleHall#40 ComplexHall#10 Exit#18 DeadEnd#10 Hub#4")); - - rules.add(parseDefinitionUnsafe("? ? ? -> ComplexHall Hub Trap SimpleHall Maze")); - - rules.add(parseDefinitionUnsafe("? ? -> ComplexHall Hub Trap SimpleHall Maze")); - - rules.add(parseDefinitionUnsafe("? -> ComplexHall#40 Hub#30 Trap#10 SimpleHall#10 Maze#10")); - - rules.add(parseDefinitionUnsafe("-> ComplexHall#40 Hub#30 Trap#10 SimpleHall#10 Maze#10")); - - String[] typeNames = "Hub Trap Maze Exit DeadEnd SimpleHall ComplexHall".toUpperCase().split(" "); - - DungeonPackConfig config = new DungeonPackConfig(); - config.setName("ruins"); - config.setAllowDuplicatesInChain(false); - config.setRules(rules); - config.setTypeNames(new ArrayList(Arrays.asList(typeNames))); - return config; - } - - private static DungeonChainRuleDefinition parseDefinitionUnsafe(String definition) - { - //This is an improvised parsing function for rule definitions. Only for testing!!! - definition = definition.toUpperCase(); - String[] parts = definition.split("->"); - ArrayList condition = new ArrayList(); - ArrayList> products = new ArrayList>(); - - for (String conditionPart : parts[0].split(" ")) + + DungeonPackConfig config; + try { - if (!conditionPart.isEmpty()) - condition.add(conditionPart); + config = (new DungeonPackConfigReader()).readFromResource("/schematics/ruins/rules.txt"); + config.setName("ruins"); + return config; } - - for (String product : parts[1].split(" ")) + catch (ConfigurationProcessingException e) { - if (!product.isEmpty()) - { - String[] productParts = product.split("#"); - String productType = productParts[0]; - int weight = (productParts.length > 1) ? Integer.parseInt(productParts[1]) : 100; - products.add(new WeightedContainer(productType, weight)); - } + //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; } - return new DungeonChainRuleDefinition(condition, products); } public List getRegisteredDungeons() diff --git a/schematics/ruins/rules.txt b/schematics/ruins/rules.txt index 086036a..ef838c1 100644 --- a/schematics/ruins/rules.txt +++ b/schematics/ruins/rules.txt @@ -9,10 +9,12 @@ DeadEnd Maze Settings: -AllowRepetitionsInBranch = false +AllowDuplicatesInChain = false AllowPackChangeOut = true -AllowPackChangeIn = true -PackWeight = 100 +DistortDoorCoordinates = true + +## Prevent this pack from being selected for transitioning in once we've transitioned out +AllowPackChangeIn = false Rules: From 4d1503db3fff45028ae060e1d2ce7f17a1aadaaa Mon Sep 17 00:00:00 2001 From: SenseiKiwi Date: Tue, 20 Aug 2013 18:55:26 -0400 Subject: [PATCH 10/10] Minor Change Minor change - fixing spacing that was messed up by Eclipse. --- .../dungeon/pack/DungeonPackConfigReader.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonPackConfigReader.java b/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonPackConfigReader.java index cf907ef..8aac967 100644 --- a/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonPackConfigReader.java +++ b/StevenDimDoors/mod_pocketDim/dungeon/pack/DungeonPackConfigReader.java @@ -378,9 +378,8 @@ public class DungeonPackConfigReader extends BaseConfigurationProcessor