/** * Copyright (C) 2002-2012 The FreeCol Team * * This file is part of FreeCol. * * FreeCol is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * FreeCol is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with FreeCol. If not, see <http://www.gnu.org/licenses/>. */ package net.sf.freecol.common.model; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.io.StringWriter; import java.lang.reflect.Constructor; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.logging.Logger; import org.freecolandroid.xml.stream.XMLInputFactory; import org.freecolandroid.xml.stream.XMLStreamConstants; import org.freecolandroid.xml.stream.XMLStreamException; import org.freecolandroid.xml.stream.XMLStreamReader; import org.freecolandroid.xml.stream.XMLStreamWriter; import net.sf.freecol.FreeCol; import net.sf.freecol.common.io.FreeColTcFile; import net.sf.freecol.common.option.AbstractOption; import net.sf.freecol.common.option.BooleanOption; import net.sf.freecol.common.option.IntegerOption; import net.sf.freecol.common.option.Option; import net.sf.freecol.common.option.OptionGroup; import net.sf.freecol.common.option.RangeOption; import net.sf.freecol.common.option.StringOption; /** * This class encapsulates any parts of the "specification" for FreeCol that are * expressed best using XML. The XML is loaded through the class loader from the * resource named "specification.xml" in the same package as this class. */ public final class Specification { public static final FreeColGameObjectType MOVEMENT_PENALTY_SOURCE = new FreeColGameObjectType("model.source.movementPenalty"); public static final FreeColGameObjectType ARTILLERY_PENALTY_SOURCE = new FreeColGameObjectType("model.source.artilleryInTheOpen"); public static final FreeColGameObjectType ATTACK_BONUS_SOURCE = new FreeColGameObjectType("model.source.attackBonus"); public static final FreeColGameObjectType FORTIFICATION_BONUS_SOURCE = new FreeColGameObjectType("model.source.fortified"); public static final FreeColGameObjectType INDIAN_RAID_BONUS_SOURCE = new FreeColGameObjectType("model.source.artilleryAgainstRaid"); public static final FreeColGameObjectType AMPHIBIOUS_ATTACK_PENALTY_SOURCE = new FreeColGameObjectType("model.source.amphibiousAttack"); public static final FreeColGameObjectType BASE_OFFENCE_SOURCE = new FreeColGameObjectType("model.source.baseOffence"); public static final FreeColGameObjectType BASE_DEFENCE_SOURCE = new FreeColGameObjectType("model.source.baseDefence"); public static final FreeColGameObjectType CARGO_PENALTY_SOURCE = new FreeColGameObjectType("model.source.cargoPenalty"); public static final FreeColGameObjectType AMBUSH_BONUS_SOURCE = new FreeColGameObjectType("model.source.ambushBonus"); public static final FreeColGameObjectType COLONY_GOODS_PARTY_SOURCE = new FreeColGameObjectType("model.source.colonyGoodsParty"); public static final FreeColGameObjectType SHIP_TRADE_PENALTY_SOURCE = new FreeColGameObjectType("model.source.shipTradePenalty"); private static final Logger logger = Logger.getLogger(Specification.class.getName()); private final Map<String, FreeColGameObjectType> allTypes = new HashMap<String, FreeColGameObjectType>(); private final Map<String, AbstractOption> allOptions = new HashMap<String, AbstractOption>(); private final Map<String, OptionGroup> allOptionGroups = new HashMap<String, OptionGroup>(); private final Map<GoodsType, UnitType> experts = new HashMap<GoodsType, UnitType>(); private final Map<String, List<Ability>> allAbilities = new HashMap<String, List<Ability>>(); private final Map<String, List<Modifier>> allModifiers = new HashMap<String, List<Modifier>>(); private final List<BuildingType> buildingTypeList = new ArrayList<BuildingType>(); private final List<GoodsType> goodsTypeList = new ArrayList<GoodsType>(); private final List<GoodsType> farmedGoodsTypeList = new ArrayList<GoodsType>(); private final List<GoodsType> foodGoodsTypeList = new ArrayList<GoodsType>(); private final List<GoodsType> newWorldGoodsTypeList = new ArrayList<GoodsType>(); private final List<GoodsType> libertyGoodsTypeList = new ArrayList<GoodsType>(); private final List<GoodsType> immigrationGoodsTypeList = new ArrayList<GoodsType>(); private final List<GoodsType> rawBuildingGoodsTypeList = new ArrayList<GoodsType>(); private final List<ResourceType> resourceTypeList = new ArrayList<ResourceType>(); private final List<TileType> tileTypeList = new ArrayList<TileType>(); private final List<TileImprovementType> tileImprovementTypeList = new ArrayList<TileImprovementType>(); private final List<UnitType> unitTypeList = new ArrayList<UnitType>(); private final List<UnitType> unitTypesTrainedInEurope = new ArrayList<UnitType>(); private final List<UnitType> unitTypesPurchasedInEurope = new ArrayList<UnitType>(); private final List<FoundingFather> foundingFathers = new ArrayList<FoundingFather>(); private final List<Nation> nations = new ArrayList<Nation>(); private final List<Nation> europeanNations = new ArrayList<Nation>(); private final List<Nation> REFNations = new ArrayList<Nation>(); private final List<Nation> indianNations = new ArrayList<Nation>(); private final List<NationType> nationTypes = new ArrayList<NationType>(); private final List<EuropeanNationType> europeanNationTypes = new ArrayList<EuropeanNationType>(); private final List<EuropeanNationType> REFNationTypes = new ArrayList<EuropeanNationType>(); private final List<IndianNationType> indianNationTypes = new ArrayList<IndianNationType>(); private final List<EquipmentType> equipmentTypes = new ArrayList<EquipmentType>(); private final List<Event> events = new ArrayList<Event>(); private final List<Modifier> specialModifiers = new ArrayList<Modifier>(); private final Map<String, ChildReader> readerMap = new HashMap<String, ChildReader>(); private int storableTypes = 0; private boolean initialized = false; private String id; private String difficultyLevel; /** * Creates a new Specification object by loading it from the * given <code>InputStream</code>. * * @param in an <code>InputStream</code> value */ public Specification(InputStream in) { this(); initialized = false; load(in); clean(); initialized = true; } public Specification() { logger.fine("Initializing Specification"); for (FreeColGameObjectType source : new FreeColGameObjectType[] { MOVEMENT_PENALTY_SOURCE, ARTILLERY_PENALTY_SOURCE, ATTACK_BONUS_SOURCE, FORTIFICATION_BONUS_SOURCE, INDIAN_RAID_BONUS_SOURCE, AMPHIBIOUS_ATTACK_PENALTY_SOURCE, BASE_OFFENCE_SOURCE, BASE_DEFENCE_SOURCE, CARGO_PENALTY_SOURCE, AMBUSH_BONUS_SOURCE, COLONY_GOODS_PARTY_SOURCE }) { allTypes.put(source.getId(), source); } readerMap.put("nations", new TypeReader<Nation>(Nation.class, nations)); readerMap.put("building-types", new TypeReader<BuildingType>(BuildingType.class, buildingTypeList)); readerMap.put("european-nation-types", new TypeReader<EuropeanNationType>(EuropeanNationType.class, europeanNationTypes)); readerMap.put("equipment-types", new TypeReader<EquipmentType>(EquipmentType.class, equipmentTypes)); readerMap.put("events", new TypeReader<Event>(Event.class, events)); readerMap.put("founding-fathers", new TypeReader<FoundingFather>(FoundingFather.class, foundingFathers)); readerMap.put("goods-types", new TypeReader<GoodsType>(GoodsType.class, goodsTypeList)); readerMap.put("indian-nation-types", new TypeReader<IndianNationType>(IndianNationType.class, indianNationTypes)); readerMap.put("resource-types", new TypeReader<ResourceType>(ResourceType.class, resourceTypeList)); readerMap.put("tile-types", new TypeReader<TileType>(TileType.class, tileTypeList)); readerMap.put("tileimprovement-types", new TypeReader<TileImprovementType>(TileImprovementType.class, tileImprovementTypeList)); readerMap.put("unit-types", new TypeReader<UnitType>(UnitType.class, unitTypeList)); readerMap.put("modifiers", new ModifierReader()); readerMap.put("options", new OptionReader()); } private void load(InputStream in) { try { XMLStreamReader xsr = XMLInputFactory.newInstance().createXMLStreamReader(in); xsr.nextTag(); readFromXMLImpl(xsr); } catch (Exception e) { StringWriter sw = new StringWriter(); e.printStackTrace(new PrintWriter(sw)); logger.warning(sw.toString()); throw new RuntimeException("Error parsing specification: " + e.toString()); } } public void loadFragment(InputStream in) { initialized = false; load(in); initialized = true; } public void clean() { logger.finest("Cleaning up specification."); Iterator<FreeColGameObjectType> typeIterator = allTypes.values().iterator(); while (typeIterator.hasNext()) { FreeColGameObjectType type = typeIterator.next(); if (type.isAbstractType()) { typeIterator.remove(); } } for (Nation nation : nations) { if (nation.getType().isEuropean()) { if (nation.getType().isREF()) { REFNations.add(nation); } else { europeanNations.add(nation); } } else { indianNations.add(nation); } } nationTypes.addAll(indianNationTypes); nationTypes.addAll(europeanNationTypes); Iterator<EuropeanNationType> iterator = europeanNationTypes.iterator(); while (iterator.hasNext()) { EuropeanNationType nationType = iterator.next(); if (nationType.isREF()) { REFNationTypes.add(nationType); iterator.remove(); } } for (UnitType unitType : unitTypeList) { if (unitType.getExpertProduction() != null) { experts.put(unitType.getExpertProduction(), unitType); } if (unitType.hasPrice()) { if (unitType.getSkill() > 0) { unitTypesTrainedInEurope.add(unitType); } else if (!unitType.hasSkill()) { unitTypesPurchasedInEurope.add(unitType); } } } for (GoodsType goodsType : goodsTypeList) { if (goodsType.isFarmed()) { farmedGoodsTypeList.add(goodsType); } if (goodsType.isFoodType()) { foodGoodsTypeList.add(goodsType); } if (goodsType.isNewWorldGoodsType()) { newWorldGoodsTypeList.add(goodsType); } if (goodsType.isLibertyGoodsType()) { libertyGoodsTypeList.add(goodsType); } if (goodsType.isImmigrationGoodsType()) { immigrationGoodsTypeList.add(goodsType); } if (goodsType.isRawBuildingMaterial() && !goodsType.isFoodType()) { rawBuildingGoodsTypeList.add(goodsType); } if (goodsType.isStorable()) { storableTypes++; } } // now that specification is complete, dynamically generate // option choices for (AbstractOption option : allOptions.values()) { option.generateChoices(); } if (difficultyLevel != null) { applyDifficultyLevel(difficultyLevel); } // Initialize the Turn class using GameOptions. try { Turn.setStartingYear(getInteger(GameOptions.STARTING_YEAR)); Turn.setSeasonYear(getInteger(GameOptions.SEASON_YEAR)); logger.info("Initialized turn."); } catch(Exception e) { logger.warning("Failed to set year options: " + e.toString()); } logger.info("Specification initialization complete. " + allTypes.size() + " FreeColGameObjectTypes,\n" + allOptions.size() + " Options, " + allAbilities.size() + " Abilities, " + allModifiers.size() + " Modifiers read."); } private interface ChildReader { public void readChildren(XMLStreamReader xsr) throws XMLStreamException; } private class ModifierReader implements ChildReader { public void readChildren(XMLStreamReader xsr) throws XMLStreamException { while (xsr.nextTag() != XMLStreamConstants.END_ELEMENT) { Modifier modifier = new Modifier(xsr, Specification.this); Specification.this.addModifier(modifier); Specification.this.specialModifiers.add(modifier); } } } private class TypeReader<T extends FreeColGameObjectType> implements ChildReader { private Class<T> type; private List<T> result; private int index = 0; // Is there really no easy way to capture T? public TypeReader(Class<T> type, List<T> listToFill) { result = listToFill; this.type = type; } public void readChildren(XMLStreamReader xsr) throws XMLStreamException { while (xsr.nextTag() != XMLStreamConstants.END_ELEMENT) { String id = xsr.getAttributeValue(null, FreeColObject.ID_ATTRIBUTE_TAG); if (id == null) { logger.warning("ID is 'null', element name is " + xsr.getLocalName()); } else if ("delete".equals(xsr.getLocalName())) { FreeColGameObjectType object = allTypes.remove(id); if (object != null) { result.remove(object); } } else { T object = getType(xsr.getAttributeValue(null, FreeColObject.ID_ATTRIBUTE_TAG), type); object.readFromXML(xsr); if (!object.isAbstractType() && !result.contains(object)) { result.add(object); object.setIndex(index); index++; } } } } } private class OptionReader implements ChildReader { public void readChildren(XMLStreamReader xsr) throws XMLStreamException { while (xsr.nextTag() != XMLStreamConstants.END_ELEMENT) { String optionType = xsr.getLocalName(); String recursiveString = xsr.getAttributeValue(null, "recursive"); boolean recursive = "false".equals(recursiveString) ? false : true; if (OptionGroup.getXMLElementTagName().equals(optionType)) { String id = xsr.getAttributeValue(null, FreeColObject.ID_ATTRIBUTE_TAG); OptionGroup group = allOptionGroups.get(id); if (group == null) { group = new OptionGroup(id, Specification.this); allOptionGroups.put(id, group); } group.readFromXML(xsr); Specification.this.addOptionGroup(group, recursive); } else { logger.finest("Parsing of " + optionType + " is not implemented yet"); xsr.nextTag(); } } } } // ---------------------------------------------------------- retrieval // methods /** * Describe <code>getId</code> method here. * * @return a <code>String</code> value */ public String getId() { return id; } /** * Registers an Ability as defined. * * @param ability an <code>Ability</code> value */ public void addAbility(Ability ability) { String id = ability.getId(); addAbility(id); allAbilities.get(id).add(ability); } /** * Registers an Ability's id as defined. This is useful for * abilities that are required rather than provided by * FreeColGameObjectTypes. * * @param id a <code>String</code> value */ public void addAbility(String id) { if (!allAbilities.containsKey(id)) { allAbilities.put(id, new ArrayList<Ability>()); } } /** * Return a list of all Abilities with the given id. * * @param id the ability id */ public List<Ability> getAbilities(String id) { return allAbilities.get(id); } /** * Return a list of FreeColGameObjectTypes that provide the required ability. * * @param id the ability id * @param value the ability value * @return a list of FreeColGameObjectTypes that provide the required ability. */ public List<FreeColGameObjectType> getTypesProviding(String id, boolean value) { List<FreeColGameObjectType> result = new ArrayList<FreeColGameObjectType>(); for (Ability ability : getAbilities(id)) { if (ability.getValue() == value && ability.getSource() != null) { result.add(ability.getSource()); } } return result; } /** * Add a modifier. * * @param modifier a <code>Modifier</code> value */ public void addModifier(Modifier modifier) { String id = modifier.getId(); if (!allModifiers.containsKey(id)) { allModifiers.put(id, new ArrayList<Modifier>()); } allModifiers.get(id).add(modifier); } /** * Return a list of all Modifiers with the given id. * * @param id the modifier id */ public List<Modifier> getModifiers(String id) { return allModifiers.get(id); } /** * Returns the <code>FreeColGameObjectType</code> with the given * ID. Throws an IllegalArgumentException if the ID is * null. Throws and IllegalArgumentException if no such Type * can be retrieved and initialization is complete. * * @param Id a <code>String</code> value * @param type a <code>Class</code> value * @return a <code>FreeColGameObjectType</code> value * @exception IllegalArgumentException if an error occurs */ public <T extends FreeColGameObjectType> T getType(String Id, Class<T> type) throws IllegalArgumentException { if (Id == null) { throw new IllegalArgumentException("Trying to retrieve FreeColGameObjectType" + " with ID 'null'."); } else if (allTypes.containsKey(Id)) { try { return type.cast(allTypes.get(Id)); } catch(ClassCastException cce) { logger.warning(Id + " caused ClassCastException!"); throw(cce); } } else if (allTypes.containsKey(mangle(Id))) { // @compat 0.9.x return type.cast(allTypes.get(mangle(Id))); // end compatibility code } else if (initialized) { throw new IllegalArgumentException("Undefined FreeColGameObjectType" + " with ID '" + Id + "'."); } else { // forward declaration of new type try { Constructor<T> c = type.getConstructor(String.class, Specification.class); T result = c.newInstance(Id, this); allTypes.put(Id, result); return result; } catch(Exception e) { logger.warning(e.toString()); return null; } } } // @compat 0.9.x private String mangle(String id) { int index = id.lastIndexOf('.'); if (index == -1) { return id; } else { return id.substring(0, index + 1) + id.substring(index + 1, index + 2).toLowerCase(Locale.US) + id.substring(index + 2); } } // end compatibility code public FreeColGameObjectType getType(String Id) throws IllegalArgumentException { return getType(Id, FreeColGameObjectType.class); } /** * Return all types which have any of the given abilities. * * @param abilities The abilities for the search * @return a <code>List</code> of <code>UnitType</code> */ public <T extends FreeColGameObjectType> List<T> getTypesWithAbility(Class<T> resultType, String... abilities) { ArrayList<T> result = new ArrayList<T>(); for (FreeColGameObjectType type : allTypes.values()) { if (resultType.isInstance(type)) { for (String ability : abilities) { if (type.hasAbility(ability)) { result.add(resultType.cast(type)); break; } } } } return result; } /** * Return all types which have none of the given abilities. * * @param abilities The abilities for the search * @return a <code>List</code> of <code>UnitType</code> */ public <T extends FreeColGameObjectType> List<T> getTypesWithoutAbility(Class<T> resultType, String... abilities) { ArrayList<T> result = new ArrayList<T>(); type: for (FreeColGameObjectType type : allTypes.values()) { if (resultType.isInstance(type)) { for (String ability : abilities) { if (type.hasAbility(ability)) continue type; } result.add(resultType.cast(type)); } } return result; } /** * Is option with this ID present? This is helpful when options are * optionally(!) present, for example model.option.priceIncrease.artillery * exists but model.option.priceIncrease.frigate does not. * * @param Id a <code>String</code> value * @return True/false on presence of option Id */ public boolean hasOption(String Id) { return Id != null && allOptions.containsKey(Id); } /** * Returns the <code>AbstractOption</code> with the given ID. Throws an * IllegalArgumentException if the ID is null or unknown. * * @param Id a <code>String</code> value * @return an <code>AbstractOption</code> value */ public AbstractOption getOption(String Id) throws IllegalArgumentException { if (Id == null) { throw new IllegalArgumentException("Trying to retrieve AbstractOption" + " with ID 'null'."); } else if (!allOptions.containsKey(Id)) { throw new IllegalArgumentException("Trying to retrieve AbstractOption" + " with ID '" + Id + "' returned 'null'."); } else { return allOptions.get(Id); } } /** * Returns the <code>OptionGroup</code> with the given ID. Throws an * IllegalArgumentException if the ID is null or unknown. * * @param Id a <code>String</code> value * @return an <code>OptionGroup</code> value */ public OptionGroup getOptionGroup(String Id) throws IllegalArgumentException { if (Id == null) { throw new IllegalArgumentException("Trying to retrieve OptionGroup" + " with ID 'null'."); } else if (!allOptionGroups.containsKey(Id)) { throw new IllegalArgumentException("Trying to retrieve OptionGroup" + " with ID '" + Id + "' returned 'null'."); } else { return allOptionGroups.get(Id); } } /** * Adds an <code>OptionGroup</code> to the specification * * @param optionGroup <code>OptionGroup</code> to add * @param recursive a <code>boolean</code> value */ private void addOptionGroup(OptionGroup optionGroup, boolean recursive) { // Add the options of the group Iterator<Option> iter = optionGroup.iterator(); while (iter.hasNext()) { Option option = iter.next(); if (option instanceof OptionGroup) { allOptionGroups.put(option.getId(), (OptionGroup) option); if (recursive) { addOptionGroup((OptionGroup) option, true); } } else { addAbstractOption((AbstractOption) option); } } } /** * Adds an <code>AbstractOption</code> to the specification * * @param abstractOption <code>AbstractOption</code> to add */ public void addAbstractOption(AbstractOption abstractOption) { // Add the option allOptions.put(abstractOption.getId(), abstractOption); } /** * Returns the <code>IntegerOption</code> with the given ID. Throws an * IllegalArgumentException if the ID is null, or if no such Type can be * retrieved. * * @param Id a <code>String</code> value * @return an <code>IntegerOption</code> value */ public IntegerOption getIntegerOption(String Id) { return (IntegerOption) getOption(Id); } /** * Returns the <code>RangeOption</code> with the given ID. Throws an * IllegalArgumentException if the ID is null, or if no such Type can be * retrieved. * * @param Id a <code>String</code> value * @return an <code>RangeOption</code> value */ public RangeOption getRangeOption(String Id) { return (RangeOption) getOption(Id); } /** * Returns the <code>BooleanOption</code> with the given ID. Throws an * IllegalArgumentException if the ID is null, or if no such Type can be * retrieved. * * @param Id a <code>String</code> value * @return an <code>BooleanOption</code> value */ public BooleanOption getBooleanOption(String Id) { return (BooleanOption) getOption(Id); } /** * Returns the <code>StringOption</code> with the given ID. Throws an * IllegalArgumentException if the ID is null, or if no such Type can be * retrieved. * * @param Id a <code>String</code> value * @return an <code>StringOption</code> value */ public StringOption getStringOption(String Id) { return (StringOption) getOption(Id); } /** * Gets the integer value of an option. * * @param id The id of the option. * @return The value. * @exception IllegalArgumentException If there is no integer * value associated with the specified option. * @exception NullPointerException if the given <code>Option</code> does not exist. */ public int getInteger(String id) { try { return ((IntegerOption) getOption(id)).getValue(); } catch (ClassCastException e) { throw new IllegalArgumentException("No integer value associated with the specified option."); } } /** * Gets the boolean value of an option. * * @param id The id of the option. * @return The value. * @exception IllegalArgumentException If there is no boolean * value associated with the specified option. * @exception NullPointerException if the given <code>Option</code> does not exist. */ public boolean getBoolean(String id) { try { return ((BooleanOption) getOption(id)).getValue(); } catch (ClassCastException e) { throw new IllegalArgumentException("No boolean value associated with the specified option."); } } // -- Buildings -- public List<BuildingType> getBuildingTypeList() { return buildingTypeList; } /** * Describe <code>numberOfBuildingTypes</code> method here. * * @return an <code>int</code> value */ public int numberOfBuildingTypes() { return buildingTypeList.size(); } /** * Describe <code>getBuildingType</code> method here. * * @param buildingTypeIndex an <code>int</code> value * @return a <code>BuildingType</code> value */ public BuildingType getBuildingType(int buildingTypeIndex) { return buildingTypeList.get(buildingTypeIndex); } public BuildingType getBuildingType(String id) { return getType(id, BuildingType.class); } // -- Goods -- public List<GoodsType> getGoodsTypeList() { return goodsTypeList; } /** * Describe <code>numberOfGoodsTypes</code> method here. * * @return an <code>int</code> value */ public int numberOfGoodsTypes() { return goodsTypeList.size(); } public int numberOfStoredGoodsTypes() { return storableTypes; } public List<GoodsType> getFarmedGoodsTypeList() { return farmedGoodsTypeList; } public List<GoodsType> getNewWorldGoodsTypeList() { return newWorldGoodsTypeList; } public List<GoodsType> getLibertyGoodsTypeList() { return libertyGoodsTypeList; } public List<GoodsType> getImmigrationGoodsTypeList() { return immigrationGoodsTypeList; } public List<GoodsType> getFoodGoodsTypeList() { return foodGoodsTypeList; } public int numberOfFarmedGoodsTypes() { return farmedGoodsTypeList.size(); } public final List<GoodsType> getRawBuildingGoodsTypeList() { return rawBuildingGoodsTypeList; } /** * Describe <code>getGoodsType</code> method here. * * @param id a <code>String</code> value * @return a <code>GoodsType</code> value */ public GoodsType getGoodsType(String id) { return getType(id, GoodsType.class); } /** * The general "Food" type is handled as a special case in many places. * Introduce this routine to collect them into one place, in the hope * we can one day deprecate this routine and clean up the special cases. * * @return The main food type ("model.goods.food"). */ public GoodsType getPrimaryFoodType() { return getGoodsType("model.goods.food"); } // -- Resources -- public List<ResourceType> getResourceTypeList() { return resourceTypeList; } public int numberOfResourceTypes() { return resourceTypeList.size(); } public ResourceType getResourceType(String id) { return getType(id, ResourceType.class); } // -- Tiles -- public List<TileType> getTileTypeList() { return tileTypeList; } public int numberOfTileTypes() { return tileTypeList.size(); } public TileType getTileType(String id) { return getType(id, TileType.class); } // -- Improvements -- public List<TileImprovementType> getTileImprovementTypeList() { return tileImprovementTypeList; } public TileImprovementType getTileImprovementType(String id) { return getType(id, TileImprovementType.class); } // -- Units -- public List<UnitType> getUnitTypeList() { return unitTypeList; } public int numberOfUnitTypes() { return unitTypeList.size(); } public UnitType getUnitType(String id) { return getType(id, UnitType.class); } /** * Gets the most vanilla unit type. * * Provides a type to use to make a neutral comparison of the * productivity of work locations. * * @return The free colonist unit type. */ public UnitType getDefaultUnitType() { return getUnitType("model.unit.freeColonist"); } public UnitType getExpertForProducing(GoodsType goodsType) { return experts.get(goodsType); } /** * Return the unit types which have any of the given abilities * * @param abilities The abilities for the search * @return a <code>List</code> of <code>UnitType</code> */ public List<UnitType> getUnitTypesWithAbility(String... abilities) { return getTypesWithAbility(UnitType.class, abilities); } /** * Return the unit types which have none of the given abilities * * @param abilities The abilities for the search * @return a <code>List</code> of <code>UnitType</code> */ public List<UnitType> getUnitTypesWithoutAbility(String... abilities) { return getTypesWithoutAbility(UnitType.class, abilities); } /** * Returns the unit types that can be trained in Europe. */ public List<UnitType> getUnitTypesTrainedInEurope() { return unitTypesTrainedInEurope; } /** * Returns the unit types that can be purchased in Europe. */ public List<UnitType> getUnitTypesPurchasedInEurope() { return unitTypesPurchasedInEurope; } // -- Founding Fathers -- public List<FoundingFather> getFoundingFathers() { return foundingFathers; } public int numberOfFoundingFathers() { return foundingFathers.size(); } public FoundingFather getFoundingFather(String id) { return getType(id, FoundingFather.class); } // -- NationTypes -- public List<NationType> getNationTypes() { return nationTypes; } public List<EuropeanNationType> getEuropeanNationTypes() { return europeanNationTypes; } public List<EuropeanNationType> getREFNationTypes() { return REFNationTypes; } public List<IndianNationType> getIndianNationTypes() { return indianNationTypes; } public int numberOfNationTypes() { return nationTypes.size(); } public NationType getNationType(String id) { return getType(id, NationType.class); } // -- Nations -- public List<Nation> getNations() { return nations; } public Nation getNation(String id) { return getType(id, Nation.class); } public List<Nation> getEuropeanNations() { return europeanNations; } public List<Nation> getIndianNations() { return indianNations; } public List<Nation> getREFNations() { return REFNations; } // -- EquipmentTypes -- public List<EquipmentType> getEquipmentTypeList() { return equipmentTypes; } public EquipmentType getEquipmentType(String id) { return getType(id, EquipmentType.class); } // -- DifficultyLevels -- public List<OptionGroup> getDifficultyLevels() { List<OptionGroup> result = new ArrayList<OptionGroup>(); for (Option option : allOptionGroups.get("difficultyLevels").getOptions()) { if (option instanceof OptionGroup) { result.add((OptionGroup) option); } } return result; } /** * Return the current difficulty level. * * @return the current difficulty level */ public OptionGroup getDifficultyLevel() { return allOptionGroups.get(difficultyLevel); } /** * Describe <code>getDifficultyLevel</code> method here. * * @param id a <code>String</code> value * @return a <code>DifficultyLevel</code> value */ public OptionGroup getDifficultyLevel(String id) { return allOptionGroups.get(id); } /** * Describe <code>getDifficultyLevel</code> method here. * * @param level an <code>int</code> value * @return a <code>DifficultyLevel</code> value */ public OptionGroup getDifficultyLevel(int level) { return getDifficultyLevels().get(level); } /** * Applies the difficulty level identified by the given integer to * the current specification. * * @param difficultyLevel index of difficulty level to apply */ public void applyDifficultyLevel(int difficultyLevel) { applyDifficultyLevel(getDifficultyLevel(difficultyLevel)); } /** * Applies the difficulty level identified by the given String to * the current specification. * * @param difficultyLevel id of difficulty level to apply */ public void applyDifficultyLevel(String difficultyLevel) { applyDifficultyLevel(getDifficultyLevel(difficultyLevel)); } /** * Applies the given difficulty level to the current * specification. * * @param level difficulty level to apply */ public void applyDifficultyLevel(OptionGroup level) { logger.fine("Applying difficulty level " + level.getId()); addOptionGroup(level, true); for (FreeColGameObjectType type : allTypes.values()) { type.applyDifficultyLevel(level); } // TODO: find a better place for this! if (FreeCol.getDebugLevel() >= FreeCol.DEBUG_FULL) { getIntegerOption(GameOptions.STARTING_MONEY).setValue(10000); } this.difficultyLevel = level.getId(); } // -- Events -- public List<Event> getEvents() { return events; } public Event getEvent(String id) { return getType(id, Event.class); } /** * Returns the FreeColGameObjectType identified by the * attributeName, or the default value if there is no such * attribute. * * @param in the XMLStreamReader * @param attributeName the name of the attribute identifying the * FreeColGameObjectType * @param returnClass the class of the return value * @param defaultValue the value to return if there is no * attribute named attributeName * @return a FreeColGameObjectType value */ public <T extends FreeColGameObjectType> T getType(XMLStreamReader in, String attributeName, Class<T> returnClass, T defaultValue) { final String attributeString = in.getAttributeValue(null, attributeName); if (attributeString != null) { return getType(attributeString, returnClass); } else { return defaultValue; } } /** * Makes an XML-representation of this object. * * @param out The output stream. * @throws XMLStreamException if there are any problems writing to the * stream. */ protected void toXMLImpl(XMLStreamWriter out) throws XMLStreamException { // Start element: out.writeStartElement(getXMLElementTagName()); // Add attributes: out.writeAttribute(FreeColObject.ID_ATTRIBUTE_TAG, getId()); if (difficultyLevel != null) { out.writeAttribute("difficultyLevel", difficultyLevel); } // copy the order of section in specification.xml writeSection(out, "modifiers", specialModifiers); writeSection(out, "events", events); writeSection(out, "goods-types", goodsTypeList); writeSection(out, "resource-types", resourceTypeList); writeSection(out, "tile-types", tileTypeList); writeSection(out, "equipment-types", equipmentTypes); writeSection(out, "tileimprovement-types", tileImprovementTypeList); writeSection(out, "unit-types", unitTypeList); writeSection(out, "building-types", buildingTypeList); writeSection(out, "founding-fathers", foundingFathers); writeSection(out, "european-nation-types", europeanNationTypes); writeSection(out, "european-nation-types", REFNationTypes); writeSection(out, "indian-nation-types", indianNationTypes); writeSection(out, "nations", nations); // option tree has been flattened out.writeStartElement("options"); for (OptionGroup item : allOptionGroups.values()) { if ("".equals(item.getGroup())) { item.toXML(out); } } out.writeEndElement(); // End element: out.writeEndElement(); } private <T extends FreeColObject> void writeSection(XMLStreamWriter out, String section, Collection<T> items) throws XMLStreamException { out.writeStartElement(section); for (T item : items) { item.toXMLImpl(out); } out.writeEndElement(); } public void readFromXMLImpl(XMLStreamReader xsr) throws XMLStreamException { String newId = xsr.getAttributeValue(null, FreeColObject.ID_ATTRIBUTE_TAG); if (difficultyLevel == null) { difficultyLevel = xsr.getAttributeValue(null, "difficultyLevel"); } logger.fine("Difficulty level is " + difficultyLevel); if (id == null) { // don't overwrite id with parent id! id = newId; } logger.fine("Reading specification " + newId); String parentId = xsr.getAttributeValue(null, "extends"); if (parentId != null) { try { FreeColTcFile parent = new FreeColTcFile(parentId); load(parent.getSpecificationInputStream()); initialized = false; } catch(IOException e) { throw new XMLStreamException("Failed to open parent specification: " + e); } } while (xsr.nextTag() != XMLStreamConstants.END_ELEMENT) { String childName = xsr.getLocalName(); logger.finest("Found child named " + childName); ChildReader reader = readerMap.get(childName); if (reader == null) { // @compat 0.9.x if ("improvementaction-types".equals(childName)) { while (xsr.nextTag() != XMLStreamConstants.END_ELEMENT) { // skip children while ("action".equals(xsr.getLocalName())) { xsr.nextTag(); } } } else { throw new RuntimeException("unexpected: " + childName); } // end compatibility code } else { reader.readChildren(xsr); } } if (difficultyLevel != null) { applyDifficultyLevel(difficultyLevel); } // @compat 0.9.x for (BuildingType bt : getBuildingTypeList()) { bt.fixup09x(); } if (getModifiers("model.modifier.nativeTreasureModifier") != null) { for (FoundingFather ff : getFoundingFathers()) { ff.fixup09x(); } } // end compatibility code // @compat 0.10.1 String[] years = new String[] { "startingYear", "seasonYear", "mandatoryColonyYear", "lastYear", "lastColonialYear" }; int[] values = new int[] { 1492, 1600, 1600, 1850, 1800 }; for (int index = 0; index < years.length; index++) { String id = "model.option." + years[index]; if (allOptions.get(id) == null) { IntegerOption option = new IntegerOption(id); option.setValue(values[index]); allOptions.put(id, option); } } // end compatibility code initialized = true; } public static String getXMLElementTagName() { return "freecol-specification"; } }