package games.strategy.engine.data;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.concurrent.atomic.AtomicReference;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import games.strategy.debug.ClientLogger;
import games.strategy.engine.ClientContext;
import games.strategy.engine.data.gameparser.XmlGameElementMapper;
import games.strategy.engine.data.properties.BooleanProperty;
import games.strategy.engine.data.properties.ColorProperty;
import games.strategy.engine.data.properties.ComboProperty;
import games.strategy.engine.data.properties.FileProperty;
import games.strategy.engine.data.properties.GameProperties;
import games.strategy.engine.data.properties.IEditableProperty;
import games.strategy.engine.data.properties.NumberProperty;
import games.strategy.engine.data.properties.StringProperty;
import games.strategy.engine.delegate.IDelegate;
import games.strategy.engine.framework.IGameLoader;
import games.strategy.triplea.Constants;
import games.strategy.triplea.attachments.TechAbilityAttachment;
import games.strategy.triplea.attachments.TerritoryAttachment;
import games.strategy.triplea.attachments.UnitAttachment;
import games.strategy.triplea.delegate.GenericTechAdvance;
import games.strategy.triplea.delegate.TechAdvance;
import games.strategy.triplea.formatter.MyFormatter;
import games.strategy.util.Tuple;
import games.strategy.util.Version;
public class GameParser {
private static final Class<?>[] SETTER_ARGS = {String.class};
private GameData data;
private final Collection<SAXParseException> errorsSAX = new ArrayList<>();
public static final String DTD_FILE_NAME = "game.dtd";
private final String mapName;
public GameParser(final String mapName) {
this.mapName = mapName;
}
/**
* Parses a file into a GameData object.
*
* @param delayParsing
* Should we only parse the game name, notes, and playerlist? Normally this should be "false", except for the
* game chooser which
* should use the user set preference.
*/
public synchronized GameData parse(final InputStream stream, final AtomicReference<String> gameName,
final boolean delayParsing)
throws GameParseException, SAXException, EngineVersionException, IllegalArgumentException {
if (stream == null) {
throw new IllegalArgumentException("Stream must be non null");
}
Document doc = null;
try {
doc = getDocument(stream);
} catch (final IOException | ParserConfigurationException e) {
throw new IllegalStateException("Error parsing: " + mapName, e);
}
final Element root = doc.getDocumentElement();
data = new GameData();
// mandatory fields
// get the name of the map
parseInfo(getSingleChild("info", root));
if (gameName != null) {
gameName.set(data.getGameName());
}
// test minimum engine version FIRST
parseMinimumEngineVersionNumber(getSingleChild("triplea", root, true));
parseGameLoader(getSingleChild("loader", root));
// if we manage to get this far, past the minimum engine version number test, AND we are still good, then check and
// see if we have any
// SAX errors we need to show
if (!errorsSAX.isEmpty()) {
for (final SAXParseException error : errorsSAX) {
System.err.println("SAXParseException: game: "
+ (data == null ? "?" : (data.getGameName() == null ? "?" : data.getGameName())) + ", line: "
+ error.getLineNumber() + ", column: " + error.getColumnNumber() + ", error: " + error.getMessage());
}
}
parseDiceSides(getSingleChild("diceSides", root, true));
final Element playerListNode = getSingleChild("playerList", root);
parsePlayerList(playerListNode);
parseAlliances(playerListNode);
final Node properties = getSingleChild("propertyList", root, true);
if (properties != null) {
parseProperties(properties);
}
// everything until here is needed to select a game, the rest can be parsed when a game is selected
if (delayParsing) {
return data;
}
parseMap(getSingleChild("map", root));
final Element resourceList = getSingleChild("resourceList", root, true);
if (resourceList != null) {
parseResources(resourceList);
}
final Element unitList = getSingleChild("unitList", root, true);
if (unitList != null) {
parseUnits(unitList);
}
// Parse all different relationshipTypes that are defined in the xml, for example: War, Allied, Neutral, NAP
final Element relationshipTypes = getSingleChild("relationshipTypes", root, true);
if (relationshipTypes != null) {
parseRelationshipTypes(relationshipTypes);
}
final Element territoryEffectList = getSingleChild("territoryEffectList", root, true);
if (territoryEffectList != null) {
parseTerritoryEffects(territoryEffectList);
}
parseGamePlay(getSingleChild("gamePlay", root));
final Element production = getSingleChild("production", root, true);
if (production != null) {
parseProduction(production);
}
final Element technology = getSingleChild("technology", root, true);
if (technology != null) {
parseTechnology(technology);
} else {
TechAdvance.createDefaultTechAdvances(data);
}
final Element attachmentList = getSingleChild("attachmentList", root, true);
if (attachmentList != null) {
parseAttachments(attachmentList);
}
final Node initialization = getSingleChild("initialize", root, true);
if (initialization != null) {
parseInitialization(initialization);
}
// set & override default relationships
// sets the relationship between all players and the NullPlayer to NullRelation
// (with archeType War)
data.getRelationshipTracker().setNullPlayerRelations();
// sets the relationship for all players with themselfs to the SelfRelation (with archeType Allied)
data.getRelationshipTracker().setSelfRelations();
// set default tech attachments (comes after we parse all technologies, parse all attachments, and parse all game
// options/properties)
if (data.getGameLoader() instanceof games.strategy.triplea.TripleA) {
checkThatAllUnitsHaveAttachments(data);
TechAbilityAttachment.setDefaultTechnologyAttachments(data);
}
try {
validate();
} catch (final Exception e) {
ClientLogger.logQuietly("Error parsing: " + mapName, e);
throw new GameParseException(mapName, e.getMessage());
}
return data;
}
private void parseDiceSides(final Node diceSides) {
if (diceSides == null) {
data.setDiceSides(6);
} else {
data.setDiceSides(Integer.parseInt(((Element) diceSides).getAttribute("value")));
}
}
private void parseMinimumEngineVersionNumber(final Node minimumVersion) throws EngineVersionException {
if (minimumVersion == null) {
return;
}
final Version mapCompatibleWithTripleaVersion =
new Version(((Element) minimumVersion).getAttribute("minimumVersion"));
if (mapCompatibleWithTripleaVersion.isGreaterThan(ClientContext.engineVersion().getVersion(), true)) {
throw new EngineVersionException("Trying to play a map made for a newer version of TripleA. Map named '"
+ data.getGameName() + "' requires at least TripleA version " + mapCompatibleWithTripleaVersion.toString());
}
}
private void validate() throws GameParseException {
// validate unit attachments
for (final UnitType u : data.getUnitTypeList()) {
validateAttachments(u);
}
for (final Territory t : data.getMap()) {
validateAttachments(t);
}
for (final Resource r : data.getResourceList().getResources()) {
validateAttachments(r);
}
for (final PlayerID r : data.getPlayerList().getPlayers()) {
validateAttachments(r);
}
for (final RelationshipType r : data.getRelationshipTypeList().getAllRelationshipTypes()) {
validateAttachments(r);
}
for (final TerritoryEffect r : data.getTerritoryEffectList().values()) {
validateAttachments(r);
}
for (final TechAdvance r : data.getTechnologyFrontier().getTechs()) {
validateAttachments(r);
}
// if relationships are used, every player should have a relationship with every other player
validateRelationships();
}
private void validateRelationships() throws GameParseException {
// for every player
for (final PlayerID player : data.getPlayerList()) {
// in relation to every player
for (final PlayerID player2 : data.getPlayerList()) {
// See if there is a relationship between them
if ((data.getRelationshipTracker().getRelationshipType(player, player2) == null)) {
throw new GameParseException(mapName,
"No relation set for: " + player.getName() + " and " + player2.getName());
// or else throw an exception!
}
}
}
}
private void validateAttachments(final Attachable attachable) throws GameParseException {
for (final IAttachment a : attachable.getAttachments().values()) {
a.validate(data);
}
}
public Document getDocument(final InputStream input) throws SAXException, IOException, ParserConfigurationException {
final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(true);
// get the dtd location
final String dtdFile = "/games/strategy/engine/xml/" + DTD_FILE_NAME;
final URL url = GameParser.class.getResource(dtdFile);
if (url == null) {
throw new RuntimeException("Map: " + mapName + ", " + String.format("Could not find in classpath %s", dtdFile));
}
final String dtdSystem = url.toExternalForm();
final String system = dtdSystem.substring(0, dtdSystem.length() - 8);
final DocumentBuilder builder = factory.newDocumentBuilder();
builder.setErrorHandler(new ErrorHandler() {
@Override
public void fatalError(final SAXParseException exception) {
errorsSAX.add(exception);
}
@Override
public void error(final SAXParseException exception) {
errorsSAX.add(exception);
}
@Override
public void warning(final SAXParseException exception) {
errorsSAX.add(exception);
}
});
return builder.parse(input, system);
}
/**
* If mustfind is true and cannot find the player an exception will be thrown.
*/
private PlayerID getPlayerID(final Element element, final String attribute, final boolean mustFind)
throws GameParseException {
final String name = element.getAttribute(attribute);
final PlayerID player = data.getPlayerList().getPlayerID(name);
if (player == null && mustFind) {
throw new GameParseException(mapName, "Could not find player. name:" + name);
}
return player;
}
/**
* If mustfind is true and cannot find the player an exception will be thrown.
*
* @return a RelationshipType from the relationshipTypeList, at this point all relationshipTypes should have been
* declared
* @throws GameParseException
* when
*/
private RelationshipType getRelationshipType(final Element element, final String attribute, final boolean mustFind)
throws GameParseException {
final String name = element.getAttribute(attribute);
final RelationshipType relation = data.getRelationshipTypeList().getRelationshipType(name);
if (relation == null && mustFind) {
throw new GameParseException(mapName, "Could not find relation name:" + name);
}
return relation;
}
private TerritoryEffect getTerritoryEffect(final Element element, final String attribute, final boolean mustFind)
throws GameParseException {
final String name = element.getAttribute(attribute);
final TerritoryEffect effect = data.getTerritoryEffectList().get(name);
if (effect == null && mustFind) {
throw new GameParseException(mapName, "Could not find territoryEffect name:" + name);
}
return effect;
}
/**
* If mustfind is true and cannot find the productionRule an exception will be thrown.
*/
private ProductionRule getProductionRule(final Element element, final String attribute, final boolean mustFind)
throws GameParseException {
final String name = element.getAttribute(attribute);
final ProductionRule productionRule = data.getProductionRuleList().getProductionRule(name);
if (productionRule == null && mustFind) {
throw new GameParseException(mapName, "Could not find production rule. name:" + name);
}
return productionRule;
}
/**
* If mustfind is true and cannot find the productionRule an exception will be thrown.
*/
private RepairRule getRepairRule(final Element element, final String attribute, final boolean mustFind)
throws GameParseException {
final String name = element.getAttribute(attribute);
final RepairRule repairRule = data.getRepairRuleList().getRepairRule(name);
if (repairRule == null && mustFind) {
throw new GameParseException(mapName, "Could not find production rule. name:" + name);
}
return repairRule;
}
/**
* If mustfind is true and cannot find the territory an exception will be thrown.
*/
private Territory getTerritory(final Element element, final String attribute, final boolean mustFind)
throws GameParseException {
final String name = element.getAttribute(attribute);
final Territory territory = data.getMap().getTerritory(name);
if (territory == null && mustFind) {
throw new GameParseException(mapName, "Could not find territory. name:" + name);
}
return territory;
}
/**
* If mustfind is true and cannot find the unitType an exception will be thrown.
*/
private UnitType getUnitType(final Element element, final String attribute, final boolean mustFind)
throws GameParseException {
final String name = element.getAttribute(attribute);
final UnitType type = data.getUnitTypeList().getUnitType(name);
if (type == null && mustFind) {
throw new GameParseException(mapName, "Could not find unitType. name:" + name);
}
return type;
}
/**
* If mustfind is true and cannot find the technology an exception will be thrown.
*/
private TechAdvance getTechnology(final Element element, final String attribute, final boolean mustFind)
throws GameParseException {
final String name = element.getAttribute(attribute);
TechAdvance type = data.getTechnologyFrontier().getAdvanceByName(name);
if (type == null) {
type = data.getTechnologyFrontier().getAdvanceByProperty(name);
}
if (type == null && mustFind) {
throw new GameParseException(mapName, "Could not find technology. name:" + name);
}
return type;
}
/**
* If mustfind is true and cannot find the Delegate an exception will be thrown.
*/
private IDelegate getDelegate(final Element element, final String attribute, final boolean mustFind)
throws GameParseException {
final String name = element.getAttribute(attribute);
final IDelegate delegate = data.getDelegateList().getDelegate(name);
if (delegate == null && mustFind) {
throw new GameParseException(mapName, "Could not find delegate. name:" + name);
}
return delegate;
}
/**
* If mustfind is true and cannot find the Resource an exception will be thrown.
*/
private Resource getResource(final Element element, final String attribute, final boolean mustFind)
throws GameParseException {
final String name = element.getAttribute(attribute);
final Resource resource = data.getResourceList().getResource(name);
if (resource == null && mustFind) {
throw new GameParseException(mapName, "Could not find resource. name:" + name);
}
return resource;
}
/**
* If mustfind is true and cannot find the productionRule an exception will be thrown.
*/
private ProductionFrontier getProductionFrontier(final Element element, final String attribute,
final boolean mustFind) throws GameParseException {
final String name = element.getAttribute(attribute);
final ProductionFrontier productionFrontier = data.getProductionFrontierList().getProductionFrontier(name);
if (productionFrontier == null && mustFind) {
throw new GameParseException(mapName, "Could not find production frontier. name:" + name);
}
return productionFrontier;
}
/**
* If mustfind is true and cannot find the productionRule an exception will be thrown.
*/
private RepairFrontier getRepairFrontier(final Element element, final String attribute, final boolean mustFind)
throws GameParseException {
final String name = element.getAttribute(attribute);
final RepairFrontier repairFrontier = data.getRepairFrontierList().getRepairFrontier(name);
if (repairFrontier == null && mustFind) {
throw new GameParseException(mapName, "Could not find production frontier. name:" + name);
}
return repairFrontier;
}
/**
* Loads an instance of the given class.
* Assumes a zero argument constructor.
*/
private Object getInstance(final String className) throws GameParseException {
Object instance = null;
try {
final Class<?> instanceClass = Class.forName(className);
instance = instanceClass.newInstance();
// a lot can go wrong, the following list is just a subset of potential pitfalls
} catch (final ClassNotFoundException cnfe) {
throw new GameParseException(mapName, "Class <" + className + "> could not be found.");
} catch (final InstantiationException ie) {
throw new GameParseException(mapName,
"Class <" + className + "> could not be instantiated. ->" + ie.getMessage());
} catch (final IllegalAccessException iae) {
throw new GameParseException(mapName, "Constructor could not be accessed ->" + iae.getMessage());
}
return instance;
}
/**
* Get the given child.
* If there is not exactly one child throw a SAXExcpetion
*/
private Element getSingleChild(final String name, final Element node) throws GameParseException {
return getSingleChild(name, node, false);
}
/**
* If optional is true, will not throw an exception if there are 0 children.
*/
private Element getSingleChild(final String name, final Node node, final boolean optional) throws GameParseException {
final List<Element> children = getChildren(name, node);
// none found
if (children.size() == 0) {
if (optional) {
return null;
}
throw new GameParseException(mapName, "No child called " + name);
}
// too many found
if (children.size() > 1) {
throw new GameParseException(mapName, "Too many children named " + name);
}
return children.get(0);
}
private List<Element> getChildren(final String name, final Node node) {
final ArrayList<Element> found = new ArrayList<>();
final NodeList children = node.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
final Node current = children.item(i);
if (current.getNodeName().equals(name)) {
found.add((Element) current);
}
}
return found;
}
private List<Node> getNonTextNodesIgnoring(final Node node, final String ignore) {
final List<Node> rVal = getNonTextNodes(node);
final Iterator<Node> iter = rVal.iterator();
while (iter.hasNext()) {
if (((Element) iter.next()).getTagName().equals(ignore)) {
iter.remove();
}
}
return rVal;
}
private List<Node> getNonTextNodes(final Node node) {
final ArrayList<Node> found = new ArrayList<>();
final NodeList children = node.getChildNodes();
for (int i = 0; i < children.getLength(); ++i) {
final Node current = children.item(i);
if (!(current.getNodeType() == Node.TEXT_NODE)) {
found.add(current);
}
}
return found;
}
private void parseInfo(final Node info) {
final String gameName = ((Element) info).getAttribute("name");
data.setGameName(gameName);
final String version = ((Element) info).getAttribute("version");
data.setGameVersion(new Version(version));
}
private void parseGameLoader(final Node loader) throws GameParseException {
final String className = ((Element) loader).getAttribute("javaClass");
final Object instance = getInstance(className);
if (!(instance instanceof IGameLoader)) {
throw new GameParseException(mapName, "Loader must implement IGameLoader. Class Name:" + className);
}
data.setGameLoader((IGameLoader) instance);
}
private void parseMap(final Node map) throws GameParseException {
final List<Element> grids = getChildren("grid", map);
parseGrids(grids);
// get the Territories
final List<Element> territories = getChildren("territory", map);
parseTerritories(territories);
final List<Element> connections = getChildren("connection", map);
parseConnections(connections);
}
private void parseGrids(final List<Element> grids) throws GameParseException {
for (final Element current : grids) {
final String gridType = current.getAttribute("type");
final String name = current.getAttribute("name");
final String xs = current.getAttribute("x");
final String ys = current.getAttribute("y");
final List<Element> waterNodes = getChildren("water", current);
final Set<String> water = parseGridWater(waterNodes);
final String horizontalConnections = current.getAttribute("horizontal-connections");
final String verticalConnections = current.getAttribute("vertical-connections");
final String diagonalConnections = current.getAttribute("diagonal-connections");
setGrids(data, gridType, name, xs, ys, water, horizontalConnections, verticalConnections, diagonalConnections,
false);
}
}
/**
* Creates and adds new territories and their connections to their map, based on a grid.
*/
private void setGrids(final GameData data, final String gridType, final String name, final String xs,
final String ys, final Set<String> water, final String horizontalConnections, final String verticalConnections,
final String diagonalConnections, final boolean addingOntoExistingMap) throws GameParseException {
final GameMap map = data.getMap();
boolean horizontalConnectionsImplict;
if (horizontalConnections.equals("implicit")) {
horizontalConnectionsImplict = true;
} else if (horizontalConnections.equals("explicit")) {
horizontalConnectionsImplict = false;
} else {
throw new GameParseException(mapName,
"horizontal-connections attribute must be either \"explicit\" or \"implicit\"");
}
boolean verticalConnectionsImplict;
if (verticalConnections.equals("implicit")) {
verticalConnectionsImplict = true;
} else if (verticalConnections.equals("explicit")) {
verticalConnectionsImplict = false;
} else {
throw new GameParseException(mapName,
"vertical-connections attribute must be either \"explicit\" or \"implicit\"");
}
boolean diagonalConnectionsImplict;
if (diagonalConnections.equals("implicit")) {
diagonalConnectionsImplict = true;
} else if (diagonalConnections.equals("explicit")) {
diagonalConnectionsImplict = false;
} else {
throw new GameParseException(mapName,
"diagonal-connections attribute must be either \"explicit\" or \"implicit\"");
}
final int x_size = Integer.valueOf(xs);
int y_size;
if (ys != null) {
y_size = Integer.valueOf(ys);
} else {
y_size = 0;
}
map.setGridDimensions(x_size, y_size);
if (gridType.equals("square")) {
// Add territories
for (int y = 0; y < y_size; y++) {
for (int x = 0; x < x_size; x++) {
boolean isWater;
isWater = water.contains(x + "-" + y);
final Territory newTerritory = new Territory(name + "_" + x + "_" + y, isWater, data, x, y);
if (addingOntoExistingMap && map.getTerritories().contains(newTerritory)) {
continue;
}
map.addTerritory(newTerritory);
}
}
if (addingOntoExistingMap) {
map.reorderTerritoryList();
}
// Add any implicit horizontal connections
if (horizontalConnectionsImplict) {
for (int y = 0; y < y_size; y++) {
for (int x = 0; x < x_size - 1; x++) {
map.addConnection(map.getTerritoryFromCoordinates(x, y), map.getTerritoryFromCoordinates(x + 1, y));
}
}
}
// Add any implicit vertical connections
if (verticalConnectionsImplict) {
for (int x = 0; x < x_size; x++) {
for (int y = 0; y < y_size - 1; y++) {
map.addConnection(map.getTerritoryFromCoordinates(x, y), map.getTerritoryFromCoordinates(x, y + 1));
}
}
}
// Add any implicit acute diagonal connections
if (diagonalConnectionsImplict) {
for (int y = 0; y < y_size - 1; y++) {
for (int x = 0; x < x_size - 1; x++) {
map.addConnection(map.getTerritoryFromCoordinates(x, y), map.getTerritoryFromCoordinates(x + 1, y + 1));
}
}
}
// Add any implicit obtuse diagonal connections
if (diagonalConnectionsImplict) {
for (int y = 0; y < y_size - 1; y++) {
for (int x = 1; x < x_size; x++) {
map.addConnection(map.getTerritoryFromCoordinates(x, y), map.getTerritoryFromCoordinates(x - 1, y + 1));
}
}
}
// This type is a triangular grid of points and lines, used for in several rail games
} else if (gridType.equals("points-and-lines")) {
// Add territories
for (int y = 0; y < y_size; y++) {
for (int x = 0; x < x_size; x++) {
final boolean isWater = false;
if (!water.contains(x + "-" + y)) {
final Territory newTerritory = new Territory(name + "_" + x + "_" + y, isWater, data, x, y);
if (addingOntoExistingMap && map.getTerritories().contains(newTerritory)) {
continue;
}
map.addTerritory(newTerritory);
}
}
}
if (addingOntoExistingMap) {
map.reorderTerritoryList();
}
// Add any implicit horizontal connections
if (horizontalConnectionsImplict) {
for (int y = 0; y < y_size; y++) {
for (int x = 0; x < x_size - 1; x++) {
final Territory from = map.getTerritoryFromCoordinates(x, y);
final Territory to = map.getTerritoryFromCoordinates(x + 1, y);
if (from != null && to != null) {
map.addConnection(from, to);
}
}
}
}
// Add any implicit acute diagonal connections
if (diagonalConnectionsImplict) {
for (int y = 1; y < y_size; y++) {
for (int x = 0; x < x_size - 1; x++) {
if (y % 4 == 0 || (y + 1) % 4 == 0) {
final Territory from = map.getTerritoryFromCoordinates(x, y);
final Territory to = map.getTerritoryFromCoordinates(x, y - 1);
if (from != null && to != null) {
map.addConnection(from, to);
}
} else {
final Territory from = map.getTerritoryFromCoordinates(x, y);
final Territory to = map.getTerritoryFromCoordinates(x + 1, y - 1);
if (from != null && to != null) {
map.addConnection(from, to);
}
}
}
}
}
// Add any implicit obtuse diagonal connections
if (diagonalConnectionsImplict) {
for (int y = 1; y < y_size; y++) {
for (int x = 0; x < x_size - 1; x++) {
if (y % 4 == 0 || (y + 1) % 4 == 0) {
final Territory from = map.getTerritoryFromCoordinates(x, y);
final Territory to = map.getTerritoryFromCoordinates(x - 1, y - 1);
if (from != null && to != null) {
map.addConnection(from, to);
}
} else {
final Territory from = map.getTerritoryFromCoordinates(x, y);
final Territory to = map.getTerritoryFromCoordinates(x, y - 1);
if (from != null && to != null) {
map.addConnection(from, to);
}
}
}
}
}
}
}
private Set<String> parseGridWater(final List<Element> waterNodes) {
final Set<String> set = new HashSet<>();
for (final Element current : waterNodes) {
final int x = Integer.valueOf(current.getAttribute("x"));
final int y = Integer.valueOf(current.getAttribute("y"));
set.add(x + "-" + y);
}
return set;
}
private void parseTerritories(final List<Element> territories) {
final GameMap map = data.getMap();
for (final Element current : territories) {
final boolean water = current.getAttribute("water").trim().equalsIgnoreCase("true");
final String name = current.getAttribute("name");
final Territory newTerritory = new Territory(name, water, data);
map.addTerritory(newTerritory);
}
}
private void parseConnections(final List<Element> connections) throws GameParseException {
final GameMap map = data.getMap();
for (final Element current : connections) {
final Territory t1 = getTerritory(current, "t1", true);
final Territory t2 = getTerritory(current, "t2", true);
map.addConnection(t1, t2);
}
}
private void parseResources(final Element root) {
final Iterator<Element> iter = getChildren("resource", root).iterator();
while (iter.hasNext()) {
data.getResourceList().addResource(new Resource(iter.next().getAttribute("name"), data));
}
}
private void parseRelationshipTypes(final Element root) {
final Iterator<Element> iter = getChildren("relationshipType", root).iterator();
while (iter.hasNext()) {
data.getRelationshipTypeList().addRelationshipType(new RelationshipType(iter.next().getAttribute("name"), data));
}
}
private void parseTerritoryEffects(final Element root) {
final Iterator<Element> iter = getChildren("territoryEffect", root).iterator();
while (iter.hasNext()) {
final String name = iter.next().getAttribute("name");
data.getTerritoryEffectList().put(name, new TerritoryEffect(name, data));
}
}
private void parseUnits(final Element root) {
final Iterator<Element> iter = getChildren("unit", root).iterator();
while (iter.hasNext()) {
data.getUnitTypeList().addUnitType(new UnitType(iter.next().getAttribute("name"), data));
}
}
/**
* @param root
* root node containing the playerList.
*/
private void parsePlayerList(final Element root) {
final PlayerList playerList = data.getPlayerList();
for (final Element current : getChildren("player", root)) {
final String name = current.getAttribute("name");
// It appears the commented line ALWAYS returns false regardless of the value of current.getAttribute("optional")
// boolean isOptional = Boolean.getBoolean(current.getAttribute("optional"));
final boolean isOptional = current.getAttribute("optional").equals("true");
final boolean canBeDisabled = current.getAttribute("canBeDisabled").equals("true");
final PlayerID newPlayer = new PlayerID(name, isOptional, canBeDisabled, data);
playerList.addPlayerID(newPlayer);
}
}
private void parseAlliances(final Element root) throws GameParseException {
final AllianceTracker allianceTracker = data.getAllianceTracker();
final Collection<PlayerID> players = data.getPlayerList().getPlayers();
for (final Element current : getChildren("alliance", root)) {
final PlayerID p1 = getPlayerID(current, "player", true);
final String alliance = current.getAttribute("alliance");
allianceTracker.addToAlliance(p1, alliance);
}
// if relationships aren't initialized based on relationshipInitialize we use the alliances to set the relationships
if (getSingleChild("relationshipInitialize", root, true) == null) {
final RelationshipTracker relationshipTracker = data.getRelationshipTracker();
final RelationshipTypeList relationshipTypeList = data.getRelationshipTypeList();
// iterate through all players to get known allies and enemies
for (final PlayerID currentPlayer : players) {
// start with all players as enemies
// start with no players as allies
final Set<PlayerID> allies = allianceTracker.getAllies(currentPlayer);
final Set<PlayerID> enemies = new HashSet<>(players);
enemies.removeAll(allies);
// remove self from enemieslist (in case of free-for-all)
enemies.remove(currentPlayer);
// remove self from allieslist (in case you are a member of an alliance)
allies.remove(currentPlayer);
// At this point enemies and allies should be set for this player.
for (final PlayerID alliedPLayer : allies) {
relationshipTracker.setRelationship(currentPlayer, alliedPLayer,
relationshipTypeList.getDefaultAlliedRelationship());
}
for (final PlayerID enemyPlayer : enemies) {
relationshipTracker.setRelationship(currentPlayer, enemyPlayer,
relationshipTypeList.getDefaultWarRelationship());
}
}
}
}
private void parseRelationInitialize(final List<Element> relations) throws GameParseException {
if (relations.size() > 0) {
final RelationshipTracker tracker = data.getRelationshipTracker();
for (final Element current : relations) {
final PlayerID p1 = getPlayerID(current, "player1", true);
final PlayerID p2 = getPlayerID(current, "player2", true);
final RelationshipType r = getRelationshipType(current, "type", true);
final int roundValue = Integer.valueOf(current.getAttribute("roundValue"));
tracker.setRelationship(p1, p2, r, roundValue);
}
}
}
private void parseGamePlay(final Element root) throws GameParseException {
parseDelegates(getChildren("delegate", root));
parseSequence(getSingleChild("sequence", root));
parseOffset(getSingleChild("offset", root, true));
}
private void parseProperties(final Node root) throws GameParseException {
final Collection<String> runningList = new ArrayList<>();
final GameProperties properties = data.getProperties();
final Iterator<Element> children = getChildren("property", root).iterator();
while (children.hasNext()) {
final Element current = children.next();
final String editable = current.getAttribute("editable");
final String property = current.getAttribute("name");
String value = current.getAttribute("value");
runningList.add(property);
if (value == null || value.length() == 0) {
final List<Element> valueChildren = getChildren("value", current);
if (!valueChildren.isEmpty()) {
final Element valueNode = valueChildren.get(0);
if (valueNode != null) {
value = valueNode.getTextContent();
}
}
}
if (editable != null && editable.equalsIgnoreCase("true")) {
parseEditableProperty(current, property, value);
} else {
final List<Node> children2 = getNonTextNodesIgnoring(current, "value");
if (children2.size() == 0) {
// we don't know what type this property is!!, it appears like only numbers and string may be represented
// without proper type
// definition
try {
// test if it is an integer
final int integer = Integer.parseInt(value);
properties.set(property, integer);
} catch (final NumberFormatException e) {
// then it must be a string
properties.set(property, value);
}
} else {
final String type = children2.get(0).getNodeName();
if (type.equals("boolean")) {
properties.set(property, Boolean.valueOf(value));
} else if (type.equals("file")) {
properties.set(property, new File(value));
} else if (type.equals("number")) {
int intValue = 0;
if (value != null) {
try {
intValue = Integer.parseInt(value);
} catch (final NumberFormatException e) {
// value already 0
}
}
properties.set(property, intValue);
} else {
properties.set(property, value);
}
}
}
}
// add properties for all triplea related maps here:
if (!runningList.contains(Constants.AI_BONUS_INCOME_FLAT_RATE)) {
data.getProperties()
.addEditableProperty(new NumberProperty(Constants.AI_BONUS_INCOME_FLAT_RATE, null, 40, -20, 0));
}
if (!runningList.contains(Constants.AI_BONUS_INCOME_PERCENTAGE)) {
data.getProperties()
.addEditableProperty(new NumberProperty(Constants.AI_BONUS_INCOME_PERCENTAGE, null, 200, -100, 0));
}
if (!runningList.contains(Constants.AI_BONUS_ATTACK)) {
data.getProperties()
.addEditableProperty(new NumberProperty(Constants.AI_BONUS_ATTACK, null, data.getDiceSides(), 0, 0));
}
if (!runningList.contains(Constants.AI_BONUS_DEFENSE)) {
data.getProperties()
.addEditableProperty(new NumberProperty(Constants.AI_BONUS_DEFENSE, null, data.getDiceSides(), 0, 0));
}
}
private void parseEditableProperty(final Element property, final String name, final String defaultValue)
throws GameParseException {
// what type
final List<Node> children = getNonTextNodes(property);
if (children.size() != 1) {
throw new GameParseException(mapName,
"Editable properties must have exactly 1 child specifying the type. Number of children found:"
+ children.size() + " for node:" + property.getNodeName());
}
final Element child = (Element) children.get(0);
final String childName = child.getNodeName();
IEditableProperty editableProperty;
if (childName.equals("boolean")) {
editableProperty = new BooleanProperty(name, null, Boolean.valueOf(defaultValue).booleanValue());
} else if (childName.equals("file")) {
editableProperty = new FileProperty(name, null, defaultValue);
} else if (childName.equals("list") || childName.equals("combo")) {
final StringTokenizer tokenizer = new StringTokenizer(child.getAttribute("values"), ",");
final Collection<String> values = new ArrayList<>();
while (tokenizer.hasMoreElements()) {
values.add(tokenizer.nextToken());
}
editableProperty = new ComboProperty<>(name, null, defaultValue, values);
} else if (childName.equals("number")) {
final int max = Integer.valueOf(child.getAttribute("max")).intValue();
final int min = Integer.valueOf(child.getAttribute("min")).intValue();
final int def = Integer.valueOf(defaultValue).intValue();
editableProperty = new NumberProperty(name, null, max, min, def);
} else if (childName.equals("color")) {
// Parse the value as a hexidecimal number
final int def = Integer.valueOf(defaultValue, 16).intValue();
editableProperty = new ColorProperty(name, null, def);
} else if (childName.equals("string")) {
editableProperty = new StringProperty(name, null, defaultValue);
} else {
throw new GameParseException(mapName, "Unrecognized property type:" + childName);
}
data.getProperties().addEditableProperty(editableProperty);
}
private void parseOffset(final Node offsetAttributes) {
if (offsetAttributes == null) {
return;
}
final int roundOffset = Integer.parseInt(((Element) offsetAttributes).getAttribute("round"));
data.getSequence().setRoundOffset(roundOffset);
}
private void parseDelegates(final List<Element> delegateList) throws GameParseException {
final DelegateList delegates = data.getDelegateList();
final Iterator<Element> iterator = delegateList.iterator();
while (iterator.hasNext()) {
final Element current = iterator.next();
// load the class
final String className = current.getAttribute("javaClass");
XmlGameElementMapper elementMapper = new XmlGameElementMapper();
IDelegate delegate = elementMapper.getDelegate(className).orElseThrow(
() -> new GameParseException(mapName, "Class <" + className + "> is not a delegate."));
final String name = current.getAttribute("name");
String displayName = current.getAttribute("display");
if (displayName == null) {
displayName = name;
}
delegate.initialize(name, displayName);
delegates.addDelegate(delegate);
}
}
private void parseSequence(final Node sequence) throws GameParseException {
parseSteps(getChildren("step", sequence));
}
private void parseSteps(final List<Element> stepList) throws GameParseException {
final Iterator<Element> iterator = stepList.iterator();
while (iterator.hasNext()) {
final Element current = iterator.next();
final IDelegate delegate = getDelegate(current, "delegate", true);
final PlayerID player = getPlayerID(current, "player", false);
final String name = current.getAttribute("name");
String displayName = null;
final List<Element> propertyElements = getChildren("stepProperty", current);
final Properties stepProperties = pareStepProperties(propertyElements);
if (current.hasAttribute("display")) {
displayName = current.getAttribute("display");
}
final GameStep step = new GameStep(name, displayName, player, delegate, data, stepProperties);
if (current.hasAttribute("maxRunCount")) {
final int runCount = Integer.parseInt(current.getAttribute("maxRunCount"));
if (runCount <= 0) {
throw new GameParseException(mapName, "maxRunCount must be positive");
}
step.setMaxRunCount(runCount);
}
data.getSequence().addStep(step);
}
}
private Properties pareStepProperties(final List<Element> properties) {
final Properties rVal = new Properties();
for (final Element stepProperty : properties) {
final String name = stepProperty.getAttribute("name");
final String value = stepProperty.getAttribute("value");
rVal.setProperty(name, value);
}
return rVal;
}
private void parseProduction(final Node root) throws GameParseException {
parseProductionRules(getChildren("productionRule", root));
parseProductionFrontiers(getChildren("productionFrontier", root));
parsePlayerProduction(getChildren("playerProduction", root));
parseRepairRules(getChildren("repairRule", root));
parseRepairFrontiers(getChildren("repairFrontier", root));
parsePlayerRepair(getChildren("playerRepair", root));
}
private void parseTechnology(final Node root) throws GameParseException {
parseTechnologies(getSingleChild("technologies", root, true));
parsePlayerTech(getChildren("playerTech", root));
}
private void parseProductionRules(final List<Element> elements) throws GameParseException {
for (final Element current : elements) {
final String name = current.getAttribute("name");
final ProductionRule rule = new ProductionRule(name, data);
parseCosts(rule, getChildren("cost", current));
parseResults(rule, getChildren("result", current));
data.getProductionRuleList().addProductionRule(rule);
}
}
private void parseRepairRules(final List<Element> elements) throws GameParseException {
for (final Element current : elements) {
final String name = current.getAttribute("name");
final RepairRule rule = new RepairRule(name, data);
parseRepairCosts(rule, getChildren("cost", current));
parseRepairResults(rule, getChildren("result", current));
data.getRepairRuleList().addRepairRule(rule);
}
}
private void parseCosts(final ProductionRule rule, final List<Element> elements) throws GameParseException {
if (elements.size() == 0) {
throw new GameParseException(mapName, "no costs for rule:" + rule.getName());
}
for (final Element current : elements) {
final Resource resource = getResource(current, "resource", true);
final int quantity = Integer.parseInt(current.getAttribute("quantity"));
rule.addCost(resource, quantity);
}
}
private void parseRepairCosts(final RepairRule rule, final List<Element> elements) throws GameParseException {
if (elements.size() == 0) {
throw new GameParseException(mapName, "no costs for rule:" + rule.getName());
}
for (final Element current : elements) {
final Resource resource = getResource(current, "resource", true);
final int quantity = Integer.parseInt(current.getAttribute("quantity"));
rule.addCost(resource, quantity);
}
}
private void parseResults(final ProductionRule rule, final List<Element> elements) throws GameParseException {
if (elements.size() == 0) {
throw new GameParseException(mapName, "no results for rule:" + rule.getName());
}
for (final Element current : elements) {
// must find either a resource or a unit with the given name
NamedAttachable result = null;
result = getResource(current, "resourceOrUnit", false);
if (result == null) {
result = getUnitType(current, "resourceOrUnit", false);
}
if (result == null) {
throw new GameParseException(
"mapName, Could not find resource or unit" + current.getAttribute("resourceOrUnit"));
}
final int quantity = Integer.parseInt(current.getAttribute("quantity"));
rule.addResult(result, quantity);
}
}
private void parseRepairResults(final RepairRule rule, final List<Element> elements) throws GameParseException {
if (elements.size() == 0) {
throw new GameParseException(mapName, "no results for rule:" + rule.getName());
}
for (final Element current : elements) {
// must find either a resource or a unit with the given name
NamedAttachable result = null;
result = getResource(current, "resourceOrUnit", false);
if (result == null) {
result = getUnitType(current, "resourceOrUnit", false);
}
if (result == null) {
throw new GameParseException(mapName,
"Could not find resource or unit" + current.getAttribute("resourceOrUnit"));
}
final int quantity = Integer.parseInt(current.getAttribute("quantity"));
rule.addResult(result, quantity);
}
}
private void parseProductionFrontiers(final List<Element> elements) throws GameParseException {
final ProductionFrontierList frontiers = data.getProductionFrontierList();
for (final Element current : elements) {
final String name = current.getAttribute("name");
final ProductionFrontier frontier = new ProductionFrontier(name, data);
parseFrontierRules(getChildren("frontierRules", current), frontier);
frontiers.addProductionFrontier(frontier);
}
}
private void parseTechnologies(final Node element) {
if (element == null) {
return;
}
final TechnologyFrontier allTechs = data.getTechnologyFrontier();
parseTechs(getChildren("techname", element), allTechs);
}
private void parsePlayerTech(final List<Element> elements) throws GameParseException {
for (final Element current : elements) {
final PlayerID player = getPlayerID(current, "player", true);
final TechnologyFrontierList categories = player.getTechnologyFrontierList();
parseCategories(getChildren("category", current), categories);
}
}
private void parseCategories(final List<Element> elements, final TechnologyFrontierList categories)
throws GameParseException {
for (final Element current : elements) {
final TechnologyFrontier tf = new TechnologyFrontier(current.getAttribute("name"), data);
parseCategoryTechs(getChildren("tech", current), tf);
categories.addTechnologyFrontier(tf);
}
}
private void parseRepairFrontiers(final List<Element> elements) throws GameParseException {
final RepairFrontierList frontiers = data.getRepairFrontierList();
for (final Element current : elements) {
final String name = current.getAttribute("name");
final RepairFrontier frontier = new RepairFrontier(name, data);
parseRepairFrontierRules(getChildren("repairRules", current), frontier);
frontiers.addRepairFrontier(frontier);
}
}
private void parsePlayerProduction(final List<Element> elements) throws GameParseException {
for (final Element current : elements) {
final PlayerID player = getPlayerID(current, "player", true);
final ProductionFrontier frontier = getProductionFrontier(current, "frontier", true);
player.setProductionFrontier(frontier);
}
}
private void parsePlayerRepair(final List<Element> elements) throws GameParseException {
for (final Element current : elements) {
final PlayerID player = getPlayerID(current, "player", true);
final RepairFrontier repairFrontier = getRepairFrontier(current, "frontier", true);
player.setRepairFrontier(repairFrontier);
}
}
private void parseFrontierRules(final List<Element> elements, final ProductionFrontier frontier)
throws GameParseException {
final Iterator<Element> iter = elements.iterator();
while (iter.hasNext()) {
final ProductionRule rule = getProductionRule(iter.next(), "name", true);
frontier.addRule(rule);
}
}
private void parseTechs(final List<Element> elements, final TechnologyFrontier allTechsFrontier) {
for (final Element current : elements) {
final String name = current.getAttribute("name");
final String tech = current.getAttribute("tech");
TechAdvance ta;
if (tech.length() > 0) {
ta = new GenericTechAdvance(name, TechAdvance.findDefinedAdvanceAndCreateAdvance(tech, data), data);
} else {
try {
ta = TechAdvance.findDefinedAdvanceAndCreateAdvance(name, data);
} catch (final IllegalArgumentException e) {
ta = new GenericTechAdvance(name, null, data);
}
}
allTechsFrontier.addAdvance(ta);
}
}
private void parseCategoryTechs(final List<Element> elements, final TechnologyFrontier frontier)
throws GameParseException {
for (final Element current : elements) {
TechAdvance ta = data.getTechnologyFrontier().getAdvanceByProperty(current.getAttribute("name"));
if (ta == null) {
ta = data.getTechnologyFrontier().getAdvanceByName(current.getAttribute("name"));
}
if (ta == null) {
throw new GameParseException(mapName, "Technology not found :" + current.getAttribute("name"));
}
frontier.addAdvance(ta);
}
}
private void parseRepairFrontierRules(final List<Element> elements, final RepairFrontier frontier)
throws GameParseException {
final Iterator<Element> iter = elements.iterator();
while (iter.hasNext()) {
final RepairRule rule = getRepairRule(iter.next(), "name", true);
frontier.addRule(rule);
}
}
private void parseAttachments(final Element root) throws GameParseException {
for (final Element current : getChildren("attachment", root)) {
final String className = current.getAttribute("javaClass");
final Attachable attachable = findAttachment(current, current.getAttribute("type"));
final String name = current.getAttribute("name");
final List<Element> options = getChildren("option", current);
IAttachment attachment = new XmlGameElementMapper().getAttachment(className, name, attachable, data)
.orElseThrow(
() -> new GameParseException(mapName, "Attachment of type " + className + " could not be instantiated"));
attachable.addAttachment(name, attachment);
final ArrayList<Tuple<String, String>> attachmentOptionValues = setValues(attachment, options);
// keep a list of attachment references in the order they were added
data.addToAttachmentOrderAndValues(Tuple.of(attachment, attachmentOptionValues));
}
}
private Attachable findAttachment(final Element element, final String type) throws GameParseException {
Attachable returnVal;
final String name = "attachTo";
if (type.equals("unitType")) {
returnVal = getUnitType(element, name, true);
} else if (type.equals("territory")) {
returnVal = getTerritory(element, name, true);
} else if (type.equals("resource")) {
returnVal = getResource(element, name, true);
} else if (type.equals("territoryEffect")) {
returnVal = getTerritoryEffect(element, name, true);
} else if (type.equals("player")) {
returnVal = getPlayerID(element, name, true);
} else if (type.equals("relationship")) {
returnVal = this.getRelationshipType(element, name, true);
} else if (type.equals("technology")) {
returnVal = getTechnology(element, name, true);
} else {
throw new GameParseException(mapName, "Type not found to attach to:" + type);
}
return returnVal;
}
private static String capitalizeFirstLetter(final String aString) {
char first = aString.charAt(0);
first = Character.toUpperCase(first);
return first + aString.substring(1);
}
private ArrayList<Tuple<String, String>> setValues(final IAttachment attachment, final List<Element> values)
throws GameParseException {
final ArrayList<Tuple<String, String>> options = new ArrayList<>();
for (final Element current : values) {
// find the setter
String name = null;
Method setter = null;
try {
name = current.getAttribute("name");
if (name.length() == 0) {
throw new GameParseException(mapName, "Option name with 0 length");
}
setter = attachment.getClass().getMethod("set" + capitalizeFirstLetter(name), SETTER_ARGS);
} catch (final NoSuchMethodException nsme) {
throw new GameParseException(mapName, "The following option name of " + attachment.getName() + " of class "
+ attachment.getClass().getName().substring(attachment.getClass().getName().lastIndexOf('.') + 1)
+ " are either misspelled or exist only in a future version of TripleA. Setter: " + name);
}
// find the value
final String value = current.getAttribute("value");
final String count = current.getAttribute("count");
String itemValues;
if (count.length() > 0) {
itemValues = count + ":" + value;
} else {
itemValues = value;
}
// invoke
try {
final Object[] args = {itemValues};
setter.invoke(attachment, args);
} catch (final IllegalAccessException iae) {
throw new GameParseException(mapName,
"Setter not public. Setter:" + name + " Class:" + attachment.getClass().getName());
} catch (final InvocationTargetException ite) {
ite.getCause().printStackTrace(System.out);
throw new GameParseException(mapName,
"Error setting property:" + name + " cause:" + ite.getCause().getMessage());
}
options.add(Tuple.of(name, itemValues));
}
return options;
}
private void parseInitialization(final Node root) throws GameParseException {
// parse territory owners
final Node owner = getSingleChild("ownerInitialize", root, true);
if (owner != null) {
parseOwner(getChildren("territoryOwner", owner));
}
// parse initial unit placement
final Node unit = getSingleChild("unitInitialize", root, true);
if (unit != null) {
parseUnitPlacement(getChildren("unitPlacement", unit));
parseHeldUnits(getChildren("heldUnits", unit));
}
// parse resources given
final Node resource = getSingleChild("resourceInitialize", root, true);
if (resource != null) {
parseResourceInitialization(getChildren("resourceGiven", resource));
}
// parse relationships
final Node relationInitialize = getSingleChild("relationshipInitialize", root, true);
if (relationInitialize != null) {
parseRelationInitialize(getChildren("relationship", relationInitialize));
}
}
private void parseOwner(final List<Element> elements) throws GameParseException {
for (final Element current : elements) {
final Territory territory = getTerritory(current, "territory", true);
final PlayerID owner = getPlayerID(current, "owner", true);
territory.setOwner(owner);
// Set the original owner on startup.
// TODO Look into this
// The addition of this caused the automated tests to fail as TestAttachment can't be cast to TerritoryAttachment
// The addition of this IF to pass the tests is wrong, but works until a better solution is found.
// Kevin will look into it.
if (!territory.getData().getGameName().equals("gameExample")
&& !territory.getData().getGameName().equals("test")) {
// set the original owner
final TerritoryAttachment ta = TerritoryAttachment.get(territory);
if (ta != null) {
// If we already have an original owner set (ie: we set it previously in the attachment using originalOwner or
// occupiedTerrOf),
// then we DO NOT set the original owner again.
// This is how we can have a game start with territories owned by 1 faction but controlled by a 2nd faction.
final PlayerID currentOwner = ta.getOriginalOwner();
if (currentOwner == null) {
ta.setOriginalOwner(owner);
}
}
}
}
}
private void parseUnitPlacement(final List<Element> elements) throws GameParseException {
for (final Element current : elements) {
final Territory territory = getTerritory(current, "territory", true);
final UnitType type = getUnitType(current, "unitType", true);
final String ownerString = current.getAttribute("owner");
final String hitsTakenString = current.getAttribute("hitsTaken");
final String unitDamageString = current.getAttribute("unitDamage");
final PlayerID owner;
if (ownerString == null || ownerString.trim().length() == 0) {
owner = PlayerID.NULL_PLAYERID;
} else {
owner = getPlayerID(current, "owner", false);
}
final int hits;
if (hitsTakenString != null && hitsTakenString.trim().length() > 0) {
hits = Integer.parseInt(hitsTakenString);
if (hits < 0 || hits > UnitAttachment.get(type).getHitPoints() - 1) {
throw new GameParseException(mapName,
"hitsTaken cannot be less than zero or greater than one less than total hitpPoints");
}
} else {
hits = 0;
}
final int unitDamage;
if (unitDamageString != null && unitDamageString.trim().length() > 0) {
unitDamage = Integer.parseInt(unitDamageString);
if (unitDamage < 0) {
throw new GameParseException(mapName, "unitDamage cannot be less than zero");
}
} else {
unitDamage = 0;
}
final int quantity = Integer.parseInt(current.getAttribute("quantity"));
territory.getUnits().addAllUnits(type.create(quantity, owner, false, hits, unitDamage));
}
}
private void parseHeldUnits(final List<Element> elements) throws GameParseException {
for (final Element current : elements) {
final PlayerID player = getPlayerID(current, "player", true);
final UnitType type = getUnitType(current, "unitType", true);
final int quantity = Integer.parseInt(current.getAttribute("quantity"));
player.getUnits().addAllUnits(type.create(quantity, player));
}
}
private void parseResourceInitialization(final List<Element> elements) throws GameParseException {
for (final Element current : elements) {
final PlayerID player = getPlayerID(current, "player", true);
final Resource resource = getResource(current, "resource", true);
final int quantity = Integer.parseInt(current.getAttribute("quantity"));
player.getResources().addResource(resource, quantity);
}
}
private void checkThatAllUnitsHaveAttachments(final GameData data) throws GameParseException {
final Collection<UnitType> errors = new ArrayList<>();
for (final UnitType ut : data.getUnitTypeList().getAllUnitTypes()) {
final UnitAttachment ua = UnitAttachment.get(ut);
if (ua == null) {
errors.add(ut);
}
}
if (!errors.isEmpty()) {
throw new GameParseException(mapName,
data.getGameName() + " does not have unit attachments for: " + MyFormatter.defaultNamedToTextList(errors));
}
}
}