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); } - - }