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