Merge pull request #69 from SenseiKiwi/DungeonPacks

Progress on Implementing Dungeon Packs
This commit is contained in:
StevenRS11 2013-08-20 18:20:59 -07:00
commit 8c0c89c79e
5 changed files with 467 additions and 74 deletions

View file

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

View file

@ -7,6 +7,10 @@ public class DungeonPackConfig
private String name;
private ArrayList<String> typeNames;
private boolean allowDuplicatesInChain;
private boolean allowPackChangeIn;
private boolean allowPackChangeOut;
private boolean distortDoorCoordinates;
private int packWeight;
private ArrayList<DungeonChainRuleDefinition> rules;
public DungeonPackConfig() { }
@ -17,6 +21,10 @@ public class DungeonPackConfig
this.name = source.name;
this.typeNames = (ArrayList<String>) 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<DungeonChainRuleDefinition>) 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;
}
}

View file

@ -0,0 +1,385 @@
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<DungeonPackConfig>
{
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<String>());
config.setRules(new ArrayList<DungeonChainRuleDefinition>());
//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<String> 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<String> condition;
ArrayList<WeightedContainer<String>> products;
List<String> 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<String>();
products = new ArrayList<WeightedContainer<String>>();
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<String>(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<String> 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.");
}
}

View file

@ -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<DungeonChainRuleDefinition> rules = new ArrayList<DungeonChainRuleDefinition>();
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<String>(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<String> condition = new ArrayList<String>();
ArrayList<WeightedContainer<String>> products = new ArrayList<WeightedContainer<String>>();
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<String>(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<DungeonGenerator> getRegisteredDungeons()

View file

@ -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: