/** * 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.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map.Entry; import java.util.Set; import java.util.logging.Logger; import net.sf.freecol.FreeCol; import org.freecolandroid.xml.stream.XMLStreamConstants; import org.freecolandroid.xml.stream.XMLStreamException; import org.freecolandroid.xml.stream.XMLStreamReader; import org.freecolandroid.xml.stream.XMLStreamWriter; /** * Represents a colony. A colony contains {@link Building}s and * {@link ColonyTile}s. The latter represents the tiles around the * <code>Colony</code> where working is possible. */ public class Colony extends Settlement implements Nameable { private static final Logger logger = Logger.getLogger(Colony.class.getName()); public static final String BUILD_QUEUE_TAG = "buildQueueItem"; public static final String POPULATION_QUEUE_TAG = "populationQueueItem"; public static final String REARRANGE_WORKERS = "rearrangeWorkers"; public static final int LIBERTY_PER_REBEL = 200; public static final Ability HAS_PORT = new Ability("model.ability.hasPort"); public static final FreeColGameObjectType SOL_MODIFIER_SOURCE = new FreeColGameObjectType("model.source.solModifier"); public static enum ColonyChangeEvent { POPULATION_CHANGE, PRODUCTION_CHANGE, BONUS_CHANGE, WAREHOUSE_CHANGE, BUILD_QUEUE_CHANGE, UNIT_TYPE_CHANGE } public static enum NoBuildReason { NONE, NOT_BUILDING, NOT_BUILDABLE, POPULATION_TOO_SMALL, MISSING_BUILD_ABILITY, MISSING_ABILITY, WRONG_UPGRADE, LIMIT_EXCEEDED } private class Occupation { public WorkLocation workLocation; public GoodsType workType; public Occupation(WorkLocation workLocation, GoodsType workType) { this.workLocation = workLocation; this.workType = workType; } } /** A list of ColonyTiles. */ protected final List<ColonyTile> colonyTiles = new ArrayList<ColonyTile>(); /** A map of Buildings, indexed by the Id of their basic type. */ protected final java.util.Map<String, Building> buildingMap = new HashMap<String, Building>(); /** A map of ExportData, indexed by the Ids of GoodsTypes. */ protected final java.util.Map<String, ExportData> exportData = new HashMap<String, ExportData>(); /** The SoL membership this turn. */ protected int sonsOfLiberty; /** The SoL membership last turn. */ protected int oldSonsOfLiberty; /** The number of tories this turn. */ protected int tories; /** The number of tories last turn. */ protected int oldTories; /** The current production bonus. */ protected int productionBonus; /** * The number of immigration points. Immigration points are an * abstract game concept. They are generated by but are not * identical to crosses. */ protected int immigration; /** * The number of liberty points. Liberty points are an * abstract game concept. They are generated by but are not * identical to bells. */ protected int liberty; // Whether this colony is landlocked protected boolean landLocked = true; // Will only be used on enemy colonies: protected int unitCount = -1; protected int displayUnitCount = -1; protected String stockadeKey = null; /** The turn in which this colony was established. */ protected Turn established = new Turn(0); /** A list of Buildable items. */ protected BuildQueue<BuildableType> buildQueue = new BuildQueue<BuildableType>(this, BuildQueue.CompletionAction.REMOVE_EXCEPT_LAST, Consumer.COLONY_PRIORITY); /** The colonists that may be born. */ protected BuildQueue<UnitType> populationQueue = new BuildQueue<UnitType>(this, BuildQueue.CompletionAction.SHUFFLE, Consumer.POPULATION_PRIORITY); /** * Contains information about production and consumption. */ private ProductionCache productionCache = new ProductionCache(this); protected Colony() { // empty constructor } /** * Constructor for ServerColony. * * @param game The <code>Game</code> in which this object belongs. * @param owner The <code>Player</code> owning this <code>Colony</code>. * @param name The name of the new <code>Colony</code>. * @param tile The location of the <code>Colony</code>. */ protected Colony(Game game, Player owner, String name, Tile tile) { super(game, owner, name, tile); } /** * Initiates a new <code>Colony</code> from an XML representation. * * @param game The <code>Game</code> this object belongs to. * @param in The input stream containing the XML. * @throws XMLStreamException if an error occurred during parsing. */ public Colony(Game game, XMLStreamReader in) throws XMLStreamException { super(game, in); readFromXML(in); } /** * Initiates a new <code>Colony</code> with the given ID. The object * should later be initialized by calling either * {@link #readFromXML(XMLStreamReader)}. * * @param game The <code>Game</code> in which this object belong. * @param id The unique identifier for this object. */ public Colony(Game game, String id) { super(game, id); } /** * Creates a temporary copy of this colony for planning purposes. * The copy is identical except: * - it is obviously not actually present on the map * - it does not appear in the list of player colonies * - it contains no units in its work locations * - its export data is clear * - its build queue is empty * - its production cache is empty * - its name is prefixed with "scratch" * Note that this fields is shared--- do not mutate! * + the population queue * * @return A scratch version of this colony. */ public Colony getScratchColony() { Game game = getGame(); Player owner = getOwner(); Colony scratch = new Colony(game, owner, "scratch" + getName(), getTile().getScratchTile()); GoodsContainer container = new GoodsContainer(game, scratch); for (Goods g : getCompactGoods()) { container.addGoods(g.getType(), g.getAmount()); } scratch.setGoodsContainer(container); FeatureContainer fc = scratch.getFeatureContainer(); for (Ability a : getFeatureContainer().getAbilities()) fc.addAbility(a); for (Modifier m : getFeatureContainer().getModifiers()) fc.addModifier(m); scratch.colonyTiles.clear(); for (ColonyTile ct : colonyTiles) { Tile wt = ct.getWorkTile(); Tile t; if (ct.isColonyCenterTile()) { t = scratch.getTile(); t.setSettlement(scratch); } else { t = wt.getScratchTile(); } if (owner.owns(wt)) { t.setOwner(owner); t.setOwningSettlement(scratch); } scratch.colonyTiles.add(new ColonyTile(game, scratch, t)); } scratch.buildingMap.clear(); for (Entry<String, Building> e : buildingMap.entrySet()) { scratch.buildingMap.put(e.getKey(), new Building(game, scratch, e.getValue().getType())); } scratch.exportData.clear(); scratch.established = established; scratch.sonsOfLiberty = sonsOfLiberty; scratch.oldSonsOfLiberty = oldSonsOfLiberty; scratch.tories = tories; scratch.oldTories = oldTories; scratch.productionBonus = productionBonus; scratch.immigration = immigration; scratch.liberty = liberty; scratch.landLocked = landLocked; scratch.buildQueue.clear(); scratch.populationQueue = populationQueue; // ignore unitCount and stockadeKey // leave productionCache as is return scratch; } /** * Dispose of this scratch colony. Special handling to avoid mutating * the shared fields on dispose. */ public void disposeScratchColony() { populationQueue = null; for (ColonyTile ct : colonyTiles) { ct.getWorkTile().disposeScratchTile(); } colonyTiles.clear(); dispose(); } /** * Finds the corresponding work location in a scratch colony. * * @param wl The <code>WorkLocation</code> in the original colony. * @return The corresponding <code>WorkLocation</code> or null if not found. */ public WorkLocation getCorrespondingWorkLocation(WorkLocation wl) { Colony original = wl.getColony(); // Insist that this is a scratch colony, and the work location // is in the original or vice versa. if (getName().equals("scratch" + original.getName()) || original.getName().equals("scratch" + getName())) { if (wl instanceof Building) { // Types are unique for buildings, so use that as a key BuildingType type = ((Building)wl).getType(); for (Building b : getBuildings()) { if (b.getType() == type) return b; } } else if (wl instanceof ColonyTile) { // ColonyTiles are harder because the underlying tile is // also a scratch-version, but the scratch and original // tile share item containers. Tile workTile = ((ColonyTile)wl).getWorkTile(); for (ColonyTile c : getColonyTiles()) { if (c.getWorkTile().getTileItemContainer() == workTile.getTileItemContainer()) return c; } } } return null; } /** * Gets the name of this <code>Settlement</code> for a particular player. * * @param player A <code>Player</code> to return the name for. * @return The name as a <code>String</code>. */ public String getNameFor(Player player) { // Europeans can always work out the colony name. return getName(); } /** * Gets the image key for this colony. * * @return The image key. */ public String getImageKey() { if (isUndead()) return "undead"; int count = getDisplayUnitCount(); String key = (count <= 3) ? "small" : (count <= 7) ? "medium" : "large"; String stockade = getStockadeKey(); if (stockade != null) key += stockade; return "model.settlement." + key + ".image"; } /** * Is a building type able to be automatically built at no cost. * True when the player has a modifier that collapses the cost to zero. * * @param buildingType a <code>BuildingType</code> value * @return True if the building is available at zero cost. */ public boolean isAutomaticBuild(BuildingType buildingType) { float value = owner.getFeatureContainer() .applyModifier(100f, "model.modifier.buildingPriceBonus", buildingType, getGame().getTurn()); return value == 0f && canBuild(buildingType); } /** * Add a Building to this Colony. * * @param building a <code>Building</code> value */ public void addBuilding(final Building building) { BuildingType buildingType = building.getType().getFirstLevel(); buildingMap.put(buildingType.getId(), building); getFeatureContainer().add(building.getType().getFeatureContainer()); invalidateCache(); } /** * Remove a building from this Colony. * * @param building The <code>Building</code> to remove. * @return True if the building was removed. */ public boolean removeBuilding(final Building building) { BuildingType buildingType = building.getType().getFirstLevel(); boolean result = buildingMap.remove(buildingType.getId()) != null; getFeatureContainer().remove(building.getType().getFeatureContainer()); invalidateCache(); return result; } /** * Determines if this colony can build the given type of equipment. * Unlike canBuildEquipment, this takes goods "reserved" * for other purposes into account. * This colony-specific version also checks for requirements of the * current buildable. * * @param equipmentType an <code>EquipmentType</code> value * @return True if the colony can provide the equipment. * @see Settlement#canProvideEquipment(EquipmentType equipmentType) */ @Override public boolean canProvideEquipment(EquipmentType equipmentType) { BuildableType buildable = getCurrentlyBuilding(); for (AbstractGoods goods : equipmentType.getGoodsRequired()) { int available = getGoodsCount(goods.getType()); int breedingNumber = goods.getType().getBreedingNumber(); if (breedingNumber != GoodsType.INFINITY) { available -= breedingNumber; } if (buildable != null) { for (AbstractGoods ag : buildable.getGoodsRequired()) { if (ag.getType() == goods.getType()) { available -= ag.getAmount(); break; } } } if (available < goods.getAmount()) return false; } return true; } /** * Adds the goods for n of a piece of equipment to the colony. * * @param type The <code>EquipmentType</code> to add. * @param n The number of pieces of equipment (may be negative). */ public void addEquipmentGoods(EquipmentType type, int n) { for (AbstractGoods ag : type.getGoodsRequired()) { if (ag.getType().isStorable()) { addGoods(ag.getType(), n * ag.getAmount()); } } } /** * Returns true if the colony can reduce its population * voluntarily. This is generally the case, but can be prevented * by buildings such as the stockade. * * @return a <code>boolean</code> value */ public boolean canReducePopulation() { return getUnitCount() > FeatureContainer.applyModifierSet(0f, getGame().getTurn(), getModifierSet("model.modifier.minimumColonySize")); } /** * Updates SoL and builds Buildings that are free if possible. * * @param difference an <code>int</code> value */ public void updatePopulation(int difference) { int population = getUnitCount(); if (population > 0) { getTile().updatePlayerExploredTiles(); updateSoL(); updateProductionBonus(); } } /** * Describe <code>getExportData</code> method here. * * @param goodsType a <code>GoodsType</code> value * @return an <code>ExportData</code> value */ public ExportData getExportData(final GoodsType goodsType) { ExportData result = exportData.get(goodsType.getId()); if (result == null) { result = new ExportData(goodsType); setExportData(result); } return result; } /** * Describe <code>setExportData</code> method here. * * @param newExportData an <code>ExportData</code> value */ public final void setExportData(final ExportData newExportData) { exportData.put(newExportData.getId(), newExportData); } /** * How much of a goods type can be exported from this colony? * * @param goodsType The <code>GoodsType</code> to export. * @return The amount of this type of goods available for export. */ public int getExportAmount(GoodsType goodsType) { int present = getGoodsContainer().getGoodsCount(goodsType); int exportable = getExportData(goodsType).getExportLevel(); return (present < exportable) ? 0 : present - exportable; } /** * How much of a goods type can be imported into this colony? * * @param goodsType The <code>GoodsType</code> to import. * @return The amount of this type of goods that can be imported. */ public int getImportAmount(GoodsType goodsType) { int present = getGoodsContainer().getGoodsCount(goodsType); int capacity = getWarehouseCapacity(); return (present > capacity) ? 0 : capacity - present; } /** * Returns whether this colony is landlocked, or has access to water. * * @return <code>true</code> if there are no adjacent tiles to this * <code>Colony</code>'s tile being water tiles. */ public boolean isLandLocked() { return landLocked; } /** * Return whether this colony is connected to the HighSeas, or * not. A colony next to a lake would not be landlocked, for * example, but it might well be disconnected from Europe. * * @return a <code>boolean</code> value */ public boolean isConnected() { for (Tile t : getTile().getSurroundingTiles(1)) { if (t.isConnected()) return true; } return false; } /** * Returns whether this colony has undead units. * * @return whether this colony has undead units. */ public boolean isUndead() { final Iterator<Unit> unitIterator = getUnitIterator(); return unitIterator.hasNext() && unitIterator.next().isUndead(); } /** * Sets the owner of this <code>Colony</code>, including all units * within, and change main tile nation ownership. * * @param owner The <code>Player</code> that shall own this * <code>Settlement</code>. * @see Settlement#getOwner */ @Override public void changeOwner(Player owner) { super.changeOwner(owner); // Disable all exports for (ExportData exportDatum : exportData.values()) { exportDatum.setExported(false); } // Changing the owner might alter bonuses applied by founding fathers: updatePopulation(0); } /** * Collect the buildings for producing the given type of goods. * * @param goodsType The type of goods. * @return A <code>List</code> of <code>Building</code>s which produce * the given type of goods. */ public List<Building> getBuildingsForProducing(GoodsType goodsType) { List<Building> buildings = new ArrayList<Building>(); for (Building building : getBuildings()) { if (building.getGoodsOutputType() == goodsType) { buildings.add(building); } } return buildings; } /** * Collect the buildings for consuming the given type of goods. * * @param goodsType The type of goods. * @return A <code>List</code> of <code>Building</code>s which consume * the given type of goods. * @see Goods */ public List<Building> getBuildingsForConsuming(GoodsType goodsType) { List<Building> buildings = new ArrayList<Building>(); for (Building building : getBuildings()) { if (building.getGoodsInputType() == goodsType) { buildings.add(building); } } return buildings; } /** * Find a building for producing the given type of goods. * * @param goodsType The type of goods. * @return A <code>Building</code> which produces the given type of goods, * or <code>null</code> if such a building can not be found. */ public Building getBuildingForProducing(GoodsType goodsType) { List<Building> buildings = getBuildingsForProducing(goodsType); return (buildings.isEmpty()) ? null : buildings.get(0); } /** * Find a building for consuming the given type of goods. * * @param goodsType The type of goods. * @return A <code>Building</code> which consumes the given type of goods, * or <code>null</code> if such a building can not be found. */ public Building getBuildingForConsuming(GoodsType goodsType) { List<Building> buildings = getBuildingsForConsuming(goodsType); return (buildings.isEmpty()) ? null : buildings.get(0); } /** * Gets a list of every work location in this colony. * * @return The list of work locations. */ public List<WorkLocation> getAllWorkLocations() { List<WorkLocation> result = new ArrayList<WorkLocation>(buildingMap.values()); result.addAll(colonyTiles); return result; } /** * Gets a list of all freely available work locations * in this colony. * * @return The list of available <code>WorkLocation</code>s. */ public List<WorkLocation> getAvailableWorkLocations() { List<WorkLocation> result = new ArrayList<WorkLocation>(buildingMap.values()); for (ColonyTile ct : colonyTiles) { Tile tile = ct.getWorkTile(); if (tile.getOwningSettlement() == this || getOwner().canClaimForSettlement(tile)) { result.add(ct); } } return result; } /** * Gets a list of all current work locations in this colony. * * @return The list of current <code>WorkLocation</code>s. */ public List<WorkLocation> getCurrentWorkLocations() { List<WorkLocation> result = new ArrayList<WorkLocation>(buildingMap.values()); for (ColonyTile ct : colonyTiles) { Tile tile = ct.getWorkTile(); if (tile.getOwningSettlement() == this) result.add(ct); } return result; } /** * Gets a <code>List</code> of every {@link Building} in this * <code>Colony</code>. * * @return The <code>List</code>. * @see Building */ public List<Building> getBuildings() { return new ArrayList<Building>(buildingMap.values()); } /** * Gets a <code>List</code> of every {@link ColonyTile} in this * <code>Colony</code>. * * @return The <code>List</code>. * @see ColonyTile */ public List<ColonyTile> getColonyTiles() { return colonyTiles; } /** * Is a tile actually in use by this colony? * * @param tile The <code>Tile</code> to test. * @return True if this tile is actively in use by this colony. */ public boolean isTileInUse(Tile tile) { ColonyTile colonyTile = getColonyTile(tile); return colonyTile != null && !colonyTile.isEmpty(); } /** * Gets a <code>Building</code> of the specified type. * * @param type The type of the building to get. * @return The <code>Building</code>. */ public Building getBuilding(BuildingType type) { return buildingMap.get(type.getFirstLevel().getId()); } /** * Returns a <code>Building</code> with the given * <code>Ability</code>, or null, if none exists. * * @param ability a <code>String</code> value * @return a <code>Building</code> value */ public Building getBuildingWithAbility(String ability) { for (Building building : buildingMap.values()) { if (building.getType().hasAbility(ability)) { return building; } } return null; } /** * Returns the <code>ColonyTile</code> matching the given * <code>Tile</code>. * * @param t The <code>Tile</code> to get the <code>ColonyTile</code> * for. * @return The <code>ColonyTile</code> */ public ColonyTile getColonyTile(Tile t) { for (ColonyTile c : colonyTiles) { if (c.getWorkTile() == t) { return c; } } return null; } /** * Increment liberty points by amount given. * * @param amount an <code>int</code> value */ public void incrementLiberty(int amount) { liberty += amount; } /** * Increment immigration points by amount given. * * @param amount an <code>int</code> value */ public void incrementImmigration(int amount) { immigration += amount; } /** * Get the <code>Established</code> value. * * @return a <code>Turn</code> value */ public Turn getEstablished() { return established; } /** * Set the <code>Established</code> value. * * @param newEstablished The new Established value. */ public void setEstablished(final Turn newEstablished) { this.established = newEstablished; } /** * Adds a <code>Locatable</code> to this Location. * * @param locatable The <code>Locatable</code> to add to this Location. */ public boolean add(Locatable locatable) { return (locatable instanceof Unit) ? addUnit((Unit) locatable, null) : super.add(locatable); } /** * Removes a <code>Locatable</code> from this Location. * * @param locatable The <code>Locatable</code> to remove from this Location. * @return True if the remove succeeded. */ public boolean remove(Locatable locatable) { return (locatable instanceof Unit) ? removeUnit((Unit) locatable) : super.remove(locatable); } /** * Gets a work location within this colony to put a unit in. * * @param unit The <code>Unit</code> to place. * @return A work location for the unit, or null if none available. */ public WorkLocation getWorkLocationFor(Unit unit) { Occupation occupation = getOccupationFor(unit); if (occupation == null) { logger.warning("Could not find a WorkLocation for: " + unit.toString() + " in: " + getName()); return null; } if (occupation.workType != null) { unit.setWorkType(occupation.workType); } return occupation.workLocation; } /** * Adds a <code>Unit</code> to an optional * <code>WorkLocation</code> in this Colony. * * @param unit The <code>Unit</code> to add. * @param loc The <code>WorkLocation</code> to add to (if null, * one is chosen. * @return True if the add succeeded. */ public boolean addUnit(Unit unit, WorkLocation loc) { if (!unit.isPerson()) return false; if (loc == null) { loc = getWorkLocationFor(unit); if (loc == null) return false; } if (!loc.add(unit)) return false; Player owner = unit.getOwner(); owner.modifyScore(unit.getType().getScoreValue()); updatePopulation(1); unit.setState(Unit.UnitState.IN_COLONY); if (owner.isAI()) { firePropertyChange(REARRANGE_WORKERS, true, false); } return true; } /** * Removes a <code>Unit</code> from this Colony. * * @param unit The <code>Unit</code> to remove. * @return True if the remove succeeded. */ public boolean removeUnit(Unit unit) { Player owner = unit.getOwner(); for (WorkLocation w : getCurrentWorkLocations()) { if (w.contains(unit) && w.remove(unit)) { Unit teacher = unit.getTeacher(); if (teacher != null) { teacher.setStudent(null); unit.setTeacher(null); } owner.modifyScore(-unit.getType().getScoreValue()); updatePopulation(-1); unit.setState(Unit.UnitState.ACTIVE); if (owner.isAI()) { firePropertyChange(REARRANGE_WORKERS, true, false); } return true; } } return false; } /** * Add goods to this colony; * * @param goods an <code>AbstractGoods</code> value */ public boolean addGoods(AbstractGoods goods) { return addGoods(goods.getType(), goods.getAmount()); } /** * Add goods to this colony. * * @param type a <code>GoodsType</code> value * @param amount an <code>int</code> value */ public boolean addGoods(GoodsType type, int amount) { super.addGoods(type, amount); productionCache.invalidate(type); modifySpecialGoods(type, amount); return true; } /** * Removes a specified amount of a type of Goods from this Settlement. * * @param type The type of Goods to remove from this settlement. * @param amount The amount of Goods to remove from this settlement. */ public Goods removeGoods(GoodsType type, int amount) { Goods removed = super.removeGoods(type, amount); productionCache.invalidate(type); modifySpecialGoods(type, -removed.getAmount()); return removed; } /** * Removes the given Goods from the Settlement. * * @param goods a <code>Goods</code> value */ public Goods removeGoods(AbstractGoods goods) { return removeGoods(goods.getType(), goods.getAmount()); } /** * Removes all Goods of the given type from the Settlement. * * @param type a <code>GoodsType</code> value */ public Goods removeGoods(GoodsType type) { Goods removed = super.removeGoods(type); productionCache.invalidate(type); modifySpecialGoods(type, -removed.getAmount()); return removed; } protected void modifySpecialGoods(GoodsType goodsType, int amount) { FeatureContainer container = goodsType.getFeatureContainer(); Set<Modifier> libertyModifiers = container.getModifierSet("model.modifier.liberty"); if (!libertyModifiers.isEmpty()) { int newLiberty = (int) FeatureContainer.applyModifierSet(amount, getGame().getTurn(), libertyModifiers); incrementLiberty(newLiberty); getOwner().incrementLiberty(newLiberty); } Set<Modifier> immigrationModifiers = container.getModifierSet("model.modifier.immigration"); if (!immigrationModifiers.isEmpty()) { int newImmigration = (int) FeatureContainer.applyModifierSet(amount, getGame().getTurn(), immigrationModifiers); incrementImmigration(newImmigration); getOwner().incrementImmigration(newImmigration); } } /** * Gets the number of units inside this colony, which is just the sum * of the units at each work location. * * @return The number of <code>Unit</code>s in this colony. */ public int getUnitCount() { if (unitCount >= 0) return unitCount; int count = 0; for (WorkLocation w : getCurrentWorkLocations()) { count += w.getUnitCount(); } return count; } /** * Gets the apparent number of units at this colony. * Used in client enemy colonies * * @return The apparent number of <code>Unit</code>s at this colony. */ public int getDisplayUnitCount() { return (displayUnitCount > 0) ? displayUnitCount : getUnitCount(); } /** * Sets the apparent number of units inside the colony. * Used in client enemy colonies * * @param displayUnitCount The apparent number of <code>Unit</code>s * inside the colony. */ public void setDisplayUnitCount(int displayUnitCount) { this.displayUnitCount = displayUnitCount; } /** * Gets a list of all units in working in this colony. * * @return A list of <code>Unit</code>s in this colony. */ public List<Unit> getUnitList() { ArrayList<Unit> units = new ArrayList<Unit>(); for (WorkLocation wl : getCurrentWorkLocations()) { units.addAll(wl.getUnitList()); } return units; } public Iterator<Unit> getUnitIterator() { return getUnitList().iterator(); } public boolean contains(Locatable locatable) { throw new UnsupportedOperationException(); } public boolean canAdd(Locatable locatable) { if (locatable instanceof Unit && ((Unit) locatable).getOwner() == getOwner()) { return true; } else if (locatable instanceof Goods) { return true; } else { return false; } } /** * Returns true if this colony has a schoolhouse and the unit type is a * skilled unit type with a skill level not exceeding the level of the * schoolhouse. @see Building#canAdd * * @param unit The unit to add as a teacher. * @return <code>true</code> if this unit type could be added. */ public boolean canTrain(Unit unit) { return canTrain(unit.getType()); } /** * Returns true if this colony has a schoolhouse and the unit type is a * skilled unit type with a skill level not exceeding the level of the * schoolhouse. The number of units already in the schoolhouse and * the availability of pupils are not taken into account. @see * Building#canAdd * * @param unitType The unit type to add as a teacher. * @return <code>true</code> if this unit type could be added. */ public boolean canTrain(UnitType unitType) { if (!hasAbility(Ability.CAN_TEACH)) { return false; } for (Building building : buildingMap.values()) { if (building.canTeach() && building.canAdd(unitType)) { return true; } } return false; } /** * Returns a list of all teachers currently present in the school * building. */ public List<Unit> getTeachers() { List<Unit> teachers = new ArrayList<Unit>(); for (Building building : buildingMap.values()) { if (building.canTeach()) { teachers.addAll(building.getUnitList()); } } return teachers; } /** * Find a teacher for the specified student. * Do not search if ALLOW_STUDENT_SELECTION is true--- its the player's * job then. * * @param student The student <code>Unit</code> that needs a teacher. * @return A potential teacher, or null of none found. */ public Unit findTeacher(Unit student) { if (getSpecification().getBoolean(GameOptions.ALLOW_STUDENT_SELECTION)) return null; // No automatic assignment for (Building building : getBuildings()) { if (building.canTeach()) { for (Unit unit : building.getUnitList()) { if (unit.getStudent() == null && student.canBeStudent(unit)) return unit; } } } return null; } /** * Find a student for the specified teacher. * Do not search if ALLOW_STUDENT_SELECTION is true--- its the player's * job then. * * @param teacher The teacher <code>Unit</code> that needs a student. * @return A potential student, or null of none found. */ public Unit findStudent(final Unit teacher) { if (getSpecification().getBoolean(GameOptions.ALLOW_STUDENT_SELECTION)) return null; // No automatic assignment Unit student = null; GoodsType expertProduction = teacher.getType().getExpertProduction(); int skillLevel = INFINITY; for (Unit potentialStudent : getUnitList()) { /** * Always pick the student with the least skill first. * Break ties by favouring the one working in the teacher's trade, * otherwise first applicant wins. */ if (potentialStudent.getTeacher() == null && potentialStudent.canBeStudent(teacher) && (student == null || potentialStudent.getSkillLevel() < skillLevel || (potentialStudent.getSkillLevel() == skillLevel && potentialStudent.getWorkType() == expertProduction))) { student = potentialStudent; skillLevel = student.getSkillLevel(); } } return student; } /** * Gets the <code>Unit</code> that is currently defending this * <code>Colony</code>. * <p> * Note that this function will only return a unit working inside the colony. * Typically, colonies are also defended by units outside the colony on the same tile. * To consider units outside the colony as well, use (@see Tile#getDefendingUnit) instead. * <p> * Returns an arbitrary unarmed land unit unless Paul Revere is present * as founding father, in which case the unit can be armed as well. * * @param attacker The unit that would be attacking this colony. * @return The <code>Unit</code> that has been chosen to defend this * colony, or <code>null</code> if the colony belongs to another * player and client is not permitted to view contents. * @see Tile#getDefendingUnit(Unit) * @throws IllegalStateException if there are units in the colony */ @Override public Unit getDefendingUnit(Unit attacker) { List<Unit> unitList = getUnitList(); if (unitCount >= 0 && unitList.isEmpty()) { // There are units, but we don't see them return null; } Unit defender = null; float defencePower = -1.0f; for (Unit nextUnit : unitList) { float unitPower = getGame().getCombatModel() .getDefencePower(attacker, nextUnit); if (Unit.betterDefender(defender, defencePower, nextUnit, unitPower)) { defender = nextUnit; defencePower = unitPower; } } if (defender == null) { throw new IllegalStateException("Colony " + getName() + " contains no units!"); } else { return defender; } } /** * Gets the best defender type available to this colony. * * @return The best available defender type. */ public UnitType getBestDefenderType() { UnitType bestDefender = null; for (UnitType unitType : getSpecification().getUnitTypeList()) { if (unitType.getDefence() > 0 && (bestDefender == null || bestDefender.getDefence() < unitType.getDefence()) && !unitType.hasAbility(Ability.NAVAL_UNIT) && unitType.isAvailableTo(getOwner())) { bestDefender = unitType; } } return bestDefender; } /** * Gets the total defence power. * * @return The total defence power. */ public float getTotalDefencePower() { CombatModel cm = getGame().getCombatModel(); float defence = 0.0f; for (Unit unit : getTile().getUnitList()) { if (unit.isDefensiveUnit()) { defence += cm.getDefencePower(null, unit); } } return defence; } /** * Determines whether this colony is sufficiently unprotected and * contains something worth pillaging. To be called by CombatModels * when the attacker has defeated an unarmed colony defender. * * @param attacker The <code>Unit</code> that has defeated the defender. * @return True if the attacker can pillage this colony. */ public boolean canBePillaged(Unit attacker) { return !hasStockade() && attacker.hasAbility("model.ability.pillageUnprotectedColony") && !(getBurnableBuildingList().isEmpty() && getShipList().isEmpty() && (getLootableGoodsList().isEmpty() || !attacker.getType().canCarryGoods() || attacker.getSpaceLeft() == 0) && !canBePlundered()); } /** * Checks if this colony can be plundered. That is, can it yield * non-zero gold. * * @return True if at least one piece of gold can be plundered from this * colony. */ public boolean canBePlundered() { return owner.checkGold(1); } /** * Returns <code>true</code> if the number of enemy combat units * on all tiles that belong to the colony exceeds the number of * friendly combat units. At the moment, only the colony owner's * own units are considered friendly, but that could be extended * to include the units of allied players. * * TODO: if a colony is under siege, it should not be possible to * put units outside the colony, unless those units are armed. * * @return a <code>boolean</code> value */ public boolean isUnderSiege() { int friendlyUnits = 0; int enemyUnits = 0; for (ColonyTile colonyTile : colonyTiles) { for (Unit unit : colonyTile.getWorkTile().getUnitList()) { if (unit.getOwner() == getOwner()) { if (unit.isDefensiveUnit()) { friendlyUnits++; } } else if (getOwner().atWarWith(unit.getOwner())) { if (unit.isOffensiveUnit()) { enemyUnits++; } } } } return enemyUnits > friendlyUnits; } /** * Gets the buildings in this colony that could be burned by a raid. * * @return A list of burnable buildings. */ public List<Building> getBurnableBuildingList() { List<Building> buildingList = new ArrayList<Building>(); for (Building building : getBuildings()) { if (building.canBeDamaged()) buildingList.add(building); } return buildingList; } /** * Gets a list of all ships in this colony (although they are really * located on the colony tile). * * @return A list of ships in this colony. */ public List<Unit> getShipList() { List<Unit> shipList = new ArrayList<Unit>(); for (Unit u : getTile().getUnitList()) { if (u.isNaval()) shipList.add(u); } return shipList; } /** * Gets a list of all stored goods in this colony, suitable for * being looted. * * @return A list of lootable goods in this colony. */ public List<Goods> getLootableGoodsList() { List<Goods> goodsList = new ArrayList<Goods>(); for (Goods goods : getGoodsContainer().getGoods()) { if (goods.getType().isStorable()) goodsList.add(goods); } return goodsList; } /** * Gets the plunder range for this colony. * * @param attacker An attacking <code>Unit</code>. * @return The plunder range. */ public RandomRange getPlunderRange(Unit attacker) { if (canBePlundered()) { int upper = (owner.getGold() * (getUnitCount() + 1)) / (owner.getColoniesPopulation() + 1); if (upper > 0) return new RandomRange(100, 1, upper+1, 1); } return null; } /** * Returns a <code>List</code> with every unit type this colony may * build. * * @return A <code>List</code> with <code>UnitType</code> */ public List<UnitType> getBuildableUnits() { ArrayList<UnitType> buildableUnits = new ArrayList<UnitType>(); List<UnitType> unitTypes = getSpecification().getUnitTypeList(); for (UnitType unitType : unitTypes) { if (unitType.getGoodsRequired().isEmpty() == false && canBuild(unitType)) { buildableUnits.add(unitType); } } return buildableUnits; } /** * Returns the type of building currently being built. * * @return The type of building currently being built. */ public BuildableType getCurrentlyBuilding() { return buildQueue.getCurrentlyBuilding(); } /** * Sets the current type of buildable to be built and if it is a building * insist that there is only one in the queue. * * @param buildable The <code>BuildableType</code> to build. */ public void setCurrentlyBuilding(BuildableType buildable) { buildQueue.setCurrentlyBuilding(buildable); } /** * Returns how many turns it would take to build the given * <code>BuildableType</code>. * * @param buildable The <code>BuildableType</code> to build. * @return The number of turns to build the buildable, negative if * some goods are not being built, UNDEFINED if none is. */ public int getTurnsToComplete(BuildableType buildable) { return getTurnsToComplete(buildable, null); } /** * Returns how many turns it would take to build the given * <code>BuildableType</code>. * * @param buildable The <code>BuildableType</code> to build. * @param needed The <code>AbstractGoods</code> needed to continue * the build. * @return The number of turns to build the buildable, negative if * some goods are not being built, UNDEFINED if none is. */ public int getTurnsToComplete(BuildableType buildable, AbstractGoods needed) { int result = 0; boolean goodsMissing = false; boolean goodsBeingProduced = false; boolean productionMissing = false; ProductionInfo info = productionCache.getProductionInfo(buildQueue); for (AbstractGoods requiredGoods : buildable.getGoodsRequired()) { int amountNeeded = requiredGoods.getAmount(); int amountAvailable = getGoodsCount(requiredGoods.getType()); if (amountAvailable >= amountNeeded) { continue; } goodsMissing = true; int amountProduced = productionCache.getNetProductionOf(requiredGoods.getType()); if (info != null) { for (AbstractGoods consumed : info.getConsumption()) { if (consumed.getType() == requiredGoods.getType()) { // add the amount the build queue itself will consume amountProduced += consumed.getAmount(); break; } } } if (amountProduced <= 0) { productionMissing = true; if (needed != null) { needed.setType(requiredGoods.getType()); needed.setAmount(requiredGoods.getAmount()); } continue; } goodsBeingProduced = true; int amountRemaining = amountNeeded - amountAvailable; int eta = amountRemaining / amountProduced; if (amountRemaining % amountProduced != 0) { eta++; } result = Math.max(result, eta); } return (!goodsMissing) ? 0 : (!goodsBeingProduced) ? UNDEFINED : (productionMissing) ? -result : result; } /** * Get the <code>BuildQueue</code> value. * * @return a <code>List<Buildable></code> value */ public List<BuildableType> getBuildQueue() { return buildQueue.getValues(); } /** * Set the <code>BuildQueue</code> value. * * @param newBuildQueue The new BuildQueue value. */ public void setBuildQueue(final List<BuildableType> newBuildQueue) { buildQueue.setValues(newBuildQueue); } /** * Describe <code>getLiberty</code> method here. * * @return an <code>int</code> value */ public int getLiberty() { return liberty; } /** * Adds to the liberty points of the colony. Used only by DebugMenu. * * @param amount The number of liberty to add. */ public void addLiberty(int amount) { if (FreeCol.isInDebugMode()) { getOwner().incrementLiberty(amount); List<GoodsType> libertyTypeList = getSpecification().getLibertyGoodsTypeList(); if (getMembers() <= getUnitCount() + 1 && amount > 0 && !libertyTypeList.isEmpty()) { addGoods(libertyTypeList.get(0), amount); } updateSoL(); } } /** * Return the number of immigration points. * * @return an <code>int</code> value */ public int getImmigration() { return immigration; } /** * Returns the number of goods of a given type used by the settlement * each turn. * * @param goodsType <code>GoodsType</code> values * @return an <code>int</code> value */ public int getConsumptionOf(GoodsType goodsType) { int result = super.getConsumptionOf(goodsType); if (getSpecification().getGoodsType("model.goods.bells").equals(goodsType)) { result -= getSpecification().getIntegerOption("model.option.unitsThatUseNoBells").getValue(); } return Math.max(0, result); } /** * Returns the current SoL membership of the colony. * * @return The current SoL membership of the colony. */ public int getSoL() { return sonsOfLiberty; } /** * Calculates the current SoL membership of the colony based on * the liberty value and colonists. */ public void updateSoL() { int units = getUnitCount(); oldSonsOfLiberty = sonsOfLiberty; oldTories = tories; sonsOfLiberty = calculateMembership(units); tories = units - getMembers(); } /** * Returns the SoL membership of the colony based on the liberty * value and the number of colonists given. * * @param units an <code>int</code> value * @return an <code>int</code> value */ public int calculateMembership(int units) { if (units <= 0) { return 0; } // Update "addSol(int)" and "getMembers()" if this formula gets changed: int membership = (liberty * 100) / (LIBERTY_PER_REBEL * units); if (membership < 0) { membership = 0; } else if (membership > 100) { membership = 100; } return membership; } /** * Return the number of sons of liberty */ public int getMembers() { float result = (sonsOfLiberty * getUnitCount()) / 100f; return (int)Math.floor(result); } /** * Returns the Tory membership of the colony. * * @return The current Tory membership of the colony. */ public int getTory() { return 100 - getSoL(); } /** * Returns the production bonus, if any, of the colony. * * @return The current production bonus of the colony. */ public int getProductionBonus() { return productionBonus; } /** * Returns the current production <code>Modifier</code>, which is * generated from the current production bonus. * * @param goodsType a <code>GoodsType</code> value * @return a <code>Modifier</code> value */ public Modifier getProductionModifier(GoodsType goodsType) { Modifier result = new Modifier(goodsType.getId(), SOL_MODIFIER_SOURCE, productionBonus, Modifier.Type.ADDITIVE); result.setIndex(Modifier.COLONY_PRODUCTION_INDEX); return result; } /** * Gets a string representation of the Colony. Currently this method just * returns the name of the <code>Colony</code>, but that may change * later. * * @return The name of the colony. * @see #getName */ @Override public String toString() { return getName(); } /** * Returns the name of this location. * * @return The name of this location. */ public StringTemplate getLocationName() { return StringTemplate.name(getName()); } /** * Returns a suitable name for this colony for a particular player. * * @param player The <code>Player</code> to prepare the name for. * @return The name of this colony. */ public StringTemplate getLocationNameFor(Player player) { return StringTemplate.name(getNameFor(player)); } /** * Gets the combined production of all food types. * * @return an <code>int</code> value */ public int getFoodProduction() { int result = 0; for (GoodsType foodType : getSpecification().getFoodGoodsTypeList()) { result += getProductionOf(foodType); } return result; } /** * Returns the production of the given type of goods. * * @param goodsType The type of goods to get the production for. * @return The production of the given type of goods the current turn by all * of the <code>Colony</code>'s {@link Building buildings} and * {@link ColonyTile tiles}. */ public int getProductionOf(GoodsType goodsType) { int amount = 0; for (WorkLocation workLocation : getCurrentWorkLocations()) { amount += workLocation.getProductionOf(goodsType); } return amount; } /** * Gets a vacant <code>WorkLocation</code> for the given <code>Unit</code>. * * @param unit The <code>Unit</code> * @return A vacant <code>WorkLocation</code> for the given * <code>Unit</code> or <code>null</code> if there is no such * location. */ public WorkLocation getVacantWorkLocationFor(Unit unit) { Occupation occupation = getOccupationFor(unit); if (occupation == null) { return null; } else { return occupation.workLocation; } } /** * Returns an <code>Occupation</code> for the given <code>Unit</code>. * * @param unit The <code>Unit</code> * @return An <code>Occupation</code> for the given * <code>Unit</code> or <code>null</code> if there is none. */ private Occupation getOccupationFor(Unit unit) { for (AbstractGoods consumption : unit.getType().getConsumedGoods()) { // TODO: this should consider a list of all consumed // goods, weighted by priority. This, in turn, would // require the consumption element to state the // consequences if consumption can not be satisfied. if (consumption.getType().isFoodType() && productionCache.getNetProductionOf(consumption.getType()) < consumption.getAmount()) { // try to satisfied increased demands List<GoodsType> rawTypes = new ArrayList<GoodsType>(); for (GoodsType type : getSpecification().getGoodsTypeList()) { if (type.getStoredAs() == consumption.getType()) { rawTypes.add(type); } } rawTypes.add(consumption.getType()); ColonyTile bestTile = null; GoodsType bestWork = null; int bestAmount = 0; for (ColonyTile tile : colonyTiles) { switch (tile.getNoAddReason(unit)) { case NONE: case ALREADY_PRESENT: for (GoodsType type : rawTypes) { int amount = tile.getProductionOf(unit, type); if (amount > bestAmount) { bestAmount = amount; bestWork = type; bestTile = tile; } } break; default: break; } } if (bestAmount > 0) { return new Occupation(bestTile, bestWork); } else { for (GoodsType type : rawTypes) { for (Building building : getBuildingsForProducing(type)) { switch (building.getNoAddReason(unit)) { case NONE: case ALREADY_PRESENT: return new Occupation(building, type); default: break; } } } } } } GoodsType expertProduction = unit.getType().getExpertProduction(); if (expertProduction == null) { if (unit.getExperience() > 0) { expertProduction = unit.getWorkType(); if (expertProduction != null && expertProduction.isFarmed()) { ColonyTile colonyTile = getVacantColonyTileFor(unit, false, expertProduction); if (colonyTile != null) { return new Occupation(colonyTile, expertProduction); } } } } else if (expertProduction.isFarmed()) { ColonyTile colonyTile = getVacantColonyTileFor(unit, false, expertProduction); if (colonyTile != null) { return new Occupation(colonyTile, expertProduction); } } else { Building building = getBuildingFor(unit); if (building != null) { return new Occupation(building, building.getGoodsOutputType()); } } ColonyTile bestTile = null; GoodsType bestType = null; int bestProduction = 0; for (GoodsType foodType : getSpecification().getFoodGoodsTypeList()) { ColonyTile colonyTile = getVacantColonyTileFor(unit, false, foodType); if (colonyTile != null) { int production = colonyTile.getProductionOf(unit, foodType); if (production > bestProduction) { bestProduction = production; bestTile = colonyTile; bestType = foodType; } } } if (bestTile != null) { return new Occupation(bestTile, bestType); } Building building = getBuildingFor(unit); if (building != null) { return new Occupation(building, building.getGoodsOutputType()); } return null; } /** * Return the Building best suited for the given Unit. * * @param unit an <code>Unit</code> value * @return a <code>Building</code> value */ public Building getBuildingFor(Unit unit) { List<Building> buildings = new ArrayList<Building>(); GoodsType expertProduction = unit.getType().getExpertProduction(); if (expertProduction != null && !expertProduction.isFarmed()) { buildings.addAll(getBuildingsForProducing(expertProduction)); } buildings.addAll(getBuildings()); for (Building building : buildings) { switch (building.getNoAddReason(unit)) { case NONE: case ALREADY_PRESENT: if (building.getGoodsInputType() == null || getGoodsCount(building.getGoodsInputType()) > 0) { return building; } break; default: break; } } return null; } /** * Returns a vacant <code>ColonyTile</code> where the given * <code>unit</code> produces the maximum output of the given * <code>goodsType</code>. * * @param unit The <code>Unit</code> to find a vacant * <code>ColonyTile</code> for. * @param allowClaim Allow claiming free tiles from other settlements. * @param goodsTypes The types of goods that should be produced. * @return The <code>ColonyTile</code> giving the highest production of * the given goods for the given unit or <code>null</code> if * there is no available <code>ColonyTile</code> for producing * that goods. */ public ColonyTile getVacantColonyTileFor(Unit unit, boolean allowClaim, GoodsType... goodsTypes) { ColonyTile bestPick = null; int highestProduction = 0; for (ColonyTile colonyTile : colonyTiles) { switch (colonyTile.getNoAddReason(unit)) { case NONE: case ALREADY_PRESENT: Tile workTile = colonyTile.getWorkTile(); if (workTile.getOwningSettlement() == this || (allowClaim && owner.getLandPrice(workTile) == 0)) { for (GoodsType goodsType : goodsTypes) { int potential = colonyTile.getProductionOf(unit, goodsType); if (potential > highestProduction) { highestProduction = potential; bestPick = colonyTile; } } } break; default: break; } } return bestPick; } /** * Returns <code>true</code> if this Colony can breed the given * type of Goods. Only animals (such as horses) are expected to be * breedable. * * @param goodsType a <code>GoodsType</code> value * @return a <code>boolean</code> value */ public boolean canBreed(GoodsType goodsType) { int breedingNumber = goodsType.getBreedingNumber(); return (breedingNumber < GoodsType.INFINITY && breedingNumber <= getGoodsCount(goodsType)); } /** * Describe <code>canBuild</code> method here. * * @return a <code>boolean</code> value */ public boolean canBuild() { return canBuild(getCurrentlyBuilding()); } /** * Returns true if this Colony can build the given BuildableType. * * @param buildableType a <code>BuildableType</code> value * @return a <code>boolean</code> value */ public boolean canBuild(BuildableType buildableType) { return (getNoBuildReason(buildableType) == NoBuildReason.NONE); } /** * Return the reason why the give <code>BuildableType</code> can * not be built. * * @param buildableType a <code>BuildableType</code> value * @return a <code>NoBuildReason</code> value */ public NoBuildReason getNoBuildReason(BuildableType buildableType) { if (buildableType == null) { return NoBuildReason.NOT_BUILDING; } else if (buildableType.getGoodsRequired().isEmpty()) { return NoBuildReason.NOT_BUILDABLE; } else if (buildableType.getPopulationRequired() > getUnitCount()) { return NoBuildReason.POPULATION_TOO_SMALL; } else { java.util.Map<String, Boolean> requiredAbilities = buildableType.getAbilitiesRequired(); for (Entry<String, Boolean> entry : requiredAbilities.entrySet()) { if (hasAbility(entry.getKey()) != entry.getValue()) { return NoBuildReason.MISSING_ABILITY; } } if (buildableType.getLimits() != null) { for (Limit limit : buildableType.getLimits()) { if (!limit.evaluate(this)) { return NoBuildReason.LIMIT_EXCEEDED; } } } } if (buildableType instanceof BuildingType) { BuildingType newBuildingType = (BuildingType) buildableType; Building colonyBuilding = this.getBuilding(newBuildingType); if (colonyBuilding == null) { // the colony has no similar building yet if (newBuildingType.getUpgradesFrom() != null) { // we are trying to build an advanced factory, we // should build lower level shop first return NoBuildReason.WRONG_UPGRADE; } } else { // a building of the same family already exists if (colonyBuilding.getType().getUpgradesTo() != newBuildingType) { // the existing building's next upgrade is not the // new one we want to build return NoBuildReason.WRONG_UPGRADE; } } } else if (buildableType instanceof UnitType) { if (!buildableType.hasAbility(Ability.BORN_IN_COLONY) && !hasAbility(Ability.BUILD, buildableType)) { return NoBuildReason.MISSING_BUILD_ABILITY; } } return NoBuildReason.NONE; } /** * Returns the price for the remaining hammers and tools for the * {@link Building} that is currently being built. * * @return The price. * @see net.sf.freecol.client.control.InGameController#payForBuilding */ public int getPriceForBuilding() { return getPriceForBuilding(getCurrentlyBuilding()); } /** * Gets the price for the remaining resources to build a given buildable. * * @param type The <code>BuildableType</code> to build. * @return The price. * @see net.sf.freecol.client.control.InGameController#payForBuilding */ public int getPriceForBuilding(BuildableType type) { return priceGoodsForBuilding(getGoodsForBuilding(type)); } /** * Gets a price for a map of resources to build a given buildable. * * @param required The map of resources required. * @return The price. * @see net.sf.freecol.client.control.InGameController#payForBuilding */ public int priceGoodsForBuilding(HashMap<GoodsType, Integer> required) { int price = 0; Market market = getOwner().getMarket(); for (GoodsType goodsType : required.keySet()) { int amount = required.get(goodsType); if (goodsType.isStorable()) { // TODO: magic number! price += (market.getBidPrice(goodsType, amount) * 110) / 100; } else { price += goodsType.getPrice() * amount; } } return price; } /** * Gets a map of the types of goods and amount thereof required to * finish a buildable in this colony. * * @param type The <code>BuildableType</code> to build. * @return The map to completion. */ public HashMap<GoodsType, Integer> getGoodsForBuilding(BuildableType type) { HashMap<GoodsType, Integer> result = new HashMap<GoodsType, Integer>(); for (AbstractGoods goods : type.getGoodsRequired()) { GoodsType goodsType = goods.getType(); int remaining = goods.getAmount() - getGoodsCount(goodsType); if (remaining > 0) { result.put(goodsType, new Integer(remaining)); } } return result; } /** * Check if the owner can buy the remaining hammers and tools for * the {@link Building} that is currently being built. * * @exception IllegalStateException If the owner of this <code>Colony</code> * has an insufficient amount of gold. * @see #getPriceForBuilding */ public boolean canPayToFinishBuilding() { return canPayToFinishBuilding(getCurrentlyBuilding()); } /** * Check if the owner can buy the remaining hammers and tools for * the {@link Building} given. * * @param buildableType a <code>BuildableType</code> value * @return a <code>boolean</code> value * @exception IllegalStateException If the owner of this <code>Colony</code> * has an insufficient amount of gold. * @see #getPriceForBuilding */ public boolean canPayToFinishBuilding(BuildableType buildableType) { return buildableType != null && getOwner().checkGold(getPriceForBuilding(buildableType)); } /** * determine if there is a problem with the production of the specified good * * @param goodsType for this good * @param amount warehouse amount * @param production production per turn * @return all warnings */ public Collection<StringTemplate> getWarnings(GoodsType goodsType, int amount, int production) { List<StringTemplate> result = new LinkedList<StringTemplate>(); if (goodsType.isFoodType() && goodsType.isStorable()) { if (amount + production < 0) { result.add(StringTemplate.template("model.colony.famineFeared") .addName("%colony%", getName()) .addAmount("%number%", 0)); } } else { //food is never wasted -> new settler is produced int waste = (amount + production - getWarehouseCapacity()); if (waste > 0 && !getExportData(goodsType).isExported() && !goodsType.limitIgnored()) { result.add(StringTemplate.template("model.building.warehouseSoonFull") .add("%goods%", goodsType.getNameKey()) .addName("%colony%", getName()) .addAmount("%amount%", waste)); } } BuildableType currentlyBuilding = getCurrentlyBuilding(); if (currentlyBuilding != null) { for (AbstractGoods goods : currentlyBuilding.getGoodsRequired()) { if (goods.getType().equals(goodsType) && amount < goods.getAmount()) { result.add(StringTemplate.template("model.colony.buildableNeedsGoods") .addName("%colony%", getName()) .add("%buildable%", currentlyBuilding.getNameKey()) .addAmount("%amount%", (goods.getAmount() - amount)) .add("%goodsType%", goodsType.getNameKey())); } } } for (Building b : getBuildingsForProducing(goodsType)) { addInsufficientProductionMessage(result, productionCache.getProductionInfo(b)); } Building buildingForConsuming = getBuildingForConsuming(goodsType); if (buildingForConsuming != null && !buildingForConsuming.getGoodsOutputType().isStorable()) { //the warnings are for a non-storable good, which is not displayed in the trade report addInsufficientProductionMessage(result, productionCache.getProductionInfo(buildingForConsuming)); } return result; } /** * adds a message about insufficient production for a building * * @param warnings where to add the warnings * @param info the <code>ProductionInfo</code> for this building */ private void addInsufficientProductionMessage(List<StringTemplate> warnings, ProductionInfo info) { if (info != null && !info.getMaximumProduction().isEmpty()) { int missingOutput = info.getMaximumProduction().get(0).getAmount() - info.getProduction().get(0).getAmount(); if (missingOutput > 0) { GoodsType outputType = info.getProduction().get(0).getType(); GoodsType inputType = info.getConsumption().isEmpty() ? null : info.getConsumption().get(0).getType(); int missingInput = info.getMaximumConsumption().get(0).getAmount() - info.getConsumption().get(0).getAmount(); warnings.add(StringTemplate.template("model.colony.insufficientProduction") .addAmount("%outputAmount%", missingOutput) .add("%outputType%", outputType.getNameKey()) .addName("%colony%", getName()) .addAmount("%inputAmount%", missingInput) .add("%inputType%", inputType.getNameKey())); } } } /** * Returns 1, 0, or -1 to indicate that government would improve, * remain the same, or deteriorate if the colony had the given * population. * * @param unitCount The proposed population for the colony. * @return 1, 0 or -1. */ public int governmentChange(int unitCount) { final int veryBadGovernment = getSpecification() .getIntegerOption("model.option.veryBadGovernmentLimit").getValue(); final int badGovernment = getSpecification() .getIntegerOption("model.option.badGovernmentLimit").getValue(); int rebelPercent = calculateMembership(unitCount); int rebelCount = Math.round(0.01f * rebelPercent * unitCount); int loyalistCount = unitCount - rebelCount; int result = 0; if (rebelPercent >= 100) { // There are no tories left. if (sonsOfLiberty < 100) { result = 1; } } else if (rebelPercent >= 50) { if (sonsOfLiberty >= 100) { result = -1; } else if (sonsOfLiberty < 50) { result = 1; } } else { if (sonsOfLiberty >= 50) { result = -1; } else { // Now that no bonus is applied, penalties may. if (loyalistCount > veryBadGovernment) { if (tories <= veryBadGovernment) { result = -1; } } else if (loyalistCount > badGovernment) { if (tories <= badGovernment) { result = -1; } else if (tories > veryBadGovernment) { result = 1; } } else { if (tories > badGovernment) { result = 1; } } } } return result; } public ModelMessage checkForGovMgtChangeMessage() { final int veryBadGovernment = getSpecification() .getIntegerOption("model.option.veryBadGovernmentLimit").getValue(); final int badGovernment = getSpecification() .getIntegerOption("model.option.badGovernmentLimit").getValue(); String msgId = null; ModelMessage.MessageType msgType = ModelMessage.MessageType.GOVERNMENT_EFFICIENCY; if (sonsOfLiberty == 100) { // there are no tories left if (oldSonsOfLiberty < 100) { msgId = "model.colony.SoL100"; msgType = ModelMessage.MessageType.SONS_OF_LIBERTY; } } else if (sonsOfLiberty >= 50) { if (oldSonsOfLiberty == 100) { msgId = "model.colony.lostSoL100"; msgType = ModelMessage.MessageType.SONS_OF_LIBERTY; } else if (oldSonsOfLiberty < 50) { msgId = "model.colony.SoL50"; msgType = ModelMessage.MessageType.SONS_OF_LIBERTY; } } else { if (oldSonsOfLiberty >= 50) { msgId = "model.colony.lostSoL50"; msgType = ModelMessage.MessageType.SONS_OF_LIBERTY; } // Now that no bonus is applied, penalties may. if (tories > veryBadGovernment) { if (oldTories <= veryBadGovernment) { // government has become very bad msgId = "model.colony.veryBadGovernment"; } } else if (tories > badGovernment) { if (oldTories <= badGovernment) { // government has become bad msgId = "model.colony.badGovernment"; } else if (oldTories > veryBadGovernment) { // government has improved, but is still bad msgId = "model.colony.governmentImproved1"; } } else if (oldTories > badGovernment) { // government was bad, but has improved msgId = "model.colony.governmentImproved2"; } } GoodsType bells = getSpecification().getGoodsType("model.goods.bells"); return (msgId == null) ? null : new ModelMessage(msgType, msgId, this, bells) .addName("%colony%", getName()); } /** * Update the colony's production bonus. */ protected void updateProductionBonus() { final int veryBadGovernment = getSpecification() .getIntegerOption("model.option.veryBadGovernmentLimit").getValue(); final int badGovernment = getSpecification() .getIntegerOption("model.option.badGovernmentLimit").getValue(); int newBonus = (sonsOfLiberty >= 100) ? 2 : (sonsOfLiberty >= 50) ? 1 : (tories > veryBadGovernment) ? -2 : (tories > badGovernment) ? -1 : 0; if (productionBonus != newBonus) invalidateCache(); productionBonus = newBonus; } /** * Gets the number of units that would be good to add/remove from this * colony. That is the number of extra units that can be added without * damaging the production bonus, or the number of units to remove to * improve it. * * @return The number of units to add to the colony, or if negative * the negation of the number of units to remove. */ public int getPreferredSizeChange() { int i, limit, pop = getUnitCount(); if (productionBonus < 0) { limit = pop; for (i = 1; i < limit; i++) { if (governmentChange(pop - i) == 1) break; } return -i; } else { limit = getSpecification() .getIntegerOption("model.option.badGovernmentLimit").getValue(); for (i = 1; i < limit; i++) { if (governmentChange(pop + i) == -1) break; } return i - 1; } } /** * Propagates a global change in tension down to a settlement. * No-op for European colonies. * * @param player The <code>Player</code> towards whom the alarm is felt. * @param addToAlarm The amount to add to the current alarm level. * @return True if the settlement alarm level changes as a result * of this change. */ public boolean propagateAlarm(Player player, int addToAlarm) { return false; } /** * Returns the capacity of this colony's warehouse. All goods * above this limit, except Food, will be removed by the * end-of-turn processing. * * @return The capacity of this <code>Colony</code>'s warehouse. */ public int getGoodsCapacity() { /* This will return 0 unless additive modifiers are present. This is intentional. */ return (int) getFeatureContainer().applyModifier(0, "model.modifier.warehouseStorage", null, getGame().getTurn()); } public Building getWarehouse() { // TODO: it should search for more than one building? for (Building building : buildingMap.values()) { if (!building.getType().getModifierSet("model.modifier.warehouseStorage").isEmpty()) { return building; } } return null; } /** * Returns just this Colony itself. * * @return this colony. */ public Colony getColony() { return this; } /** * Returns true when colony has a stockade * * @return whether the colony has a stockade */ public boolean hasStockade() { return (getStockade() != null); } /** * Returns the stockade building * * @return a <code>Building</code> */ public Building getStockade() { // TODO: it should search for more than one building? for (Building building : buildingMap.values()) { if (!building.getType().getModifierSet(Modifier.DEFENCE).isEmpty()) { return building; } } return null; } /** * Gets the stockade key. * Uses the "stockadeKey" variable if it is non-null, which should * only be true for other player colonies. Otherwise, get the real value. * * @return The stockade key. */ public String getStockadeKey() { return (stockadeKey != null) ? stockadeKey : getTrueStockadeKey(); } /** * Gets the true stockade key, as should be visible to the owner * or a player that can see this colony. * * @return The true stockade key. */ public String getTrueStockadeKey() { Building stockade = getStockade(); return (stockade == null) ? null : stockade.getType().getId().substring("model.building".length()); } /** * Get the <code>Modifier</code> value. * * @param id a <code>String</code> value * @return a <code>Modifier</code> value */ public final Set<Modifier> getModifierSet(String id) { Set<Modifier> result = new HashSet<Modifier>(); result.addAll(getFeatureContainer().getModifierSet(id, null, getGame().getTurn())); if (owner != null) { // Null owner happens during dispose. result.addAll(owner.getFeatureContainer().getModifierSet(id, null, getGame().getTurn())); } return result; } /** * Returns true if the Colony, or its owner has the ability * identified by <code>id</code>. * * @param id a <code>String</code> value * @return a <code>boolean</code> value */ public boolean hasAbility(String id) { return hasAbility(id, null); } /** * Returns true if the Colony, or its owner has the ability * identified by <code>id</code>. * * @param id a <code>String</code> value * @param type a <code>FreeColGameObjectType</code> value * @return a <code>boolean</code> value */ public boolean hasAbility(String id, FreeColGameObjectType type) { HashSet<Ability> colonyAbilities = new HashSet<Ability>(getFeatureContainer().getAbilitySet(id, type, getGame().getTurn())); Set<Ability> playerAbilities = owner.getFeatureContainer().getAbilitySet(id, type, getGame().getTurn()); colonyAbilities.addAll(playerAbilities); return FeatureContainer.hasAbility(colonyAbilities); } /** * Verify if colony has the ability to bombard an enemy ship * adjacent to it. * @return true if it can, false otherwise */ public boolean canBombardEnemyShip() { if (isLandLocked()) { // only sea-side colonies can bombard return false; } else { // does it have the buildings that give such abilities? return hasAbility("model.ability.bombardShips"); } } /** * Dispose of this colony. * * @return A list of disposed objects. */ @Override public List<FreeColGameObject> disposeList() { List<FreeColGameObject> objects = new ArrayList<FreeColGameObject>(); for (WorkLocation workLocation : getAllWorkLocations()) { objects.addAll(((FreeColGameObject) workLocation).disposeList()); } TileImprovement road = getTile().getRoad(); if (road != null && road.isVirtual()) { getTile().getTileItemContainer().removeTileItem(road); } objects.addAll(super.disposeList()); return objects; } /** * Disposes this <code>Colony</code>. All <code>WorkLocation</code>s * owned by this <code>Colony</code> will also be destroyed. */ @Override public void dispose() { disposeList(); } /** * Returns the net production of the given GoodsType. * * @param goodsType a <code>GoodsType</code> value * @return an <code>int</code> value */ public int getNetProductionOf(GoodsType goodsType) { return productionCache.getNetProductionOf(goodsType); } public boolean isProductive(WorkLocation workLocation) { ProductionInfo info = productionCache.getProductionInfo(workLocation); return info != null && info.getProduction() != null && !info.getProduction().isEmpty() && info.getProduction().get(0).getAmount() > 0; } /** * Returns the net production of the given GoodsType adjusted by * the possible consumption of BuildQueues. * * @param goodsType a <code>GoodsType</code> value * @return an <code>int</code> value */ public int getAdjustedNetProductionOf(GoodsType goodsType) { int result = productionCache.getNetProductionOf(goodsType); for (BuildQueue<?> queue : new BuildQueue<?>[] { buildQueue, populationQueue }) { ProductionInfo info = productionCache.getProductionInfo(queue); if (info != null) { for (AbstractGoods goods : info.getConsumption()) { if (goods.getType() == goodsType) { result += goods.getAmount(); break; } } } } return result; } /** * Returns the ProductionInfo for the given Object. * * @param object an <code>Object</code> value * @return a <code>ProductionInfo</code> value */ public ProductionInfo getProductionInfo(Object object) { return productionCache.getProductionInfo(object); } /** * Invalidates the production cache. */ public void invalidateCache() { logger.finest("invalidating production cache"); productionCache.invalidate(); } /** * Gets a copy of the current production map. * Useful in the server at the point net production is applied to a colony. * * @return A copy of the current production map. */ protected TypeCountMap<GoodsType> getProductionMap() { return productionCache.getProductionMap(); } /** * Returns a list of all {@link Consumer}s in the colony sorted by * priority. Consumers include all object that consume goods, * e.g. Units, Buildings and BuildQueues. * * @return a list of consumers */ public List<Consumer> getConsumers() { List<Consumer> result = new ArrayList<Consumer>(); result.addAll(getUnitList()); result.addAll(buildingMap.values()); result.add(buildQueue); result.add(populationQueue); Collections.sort(result, Consumer.COMPARATOR); return result; } /** * Collects tiles that need exploring, plowing or road building * which may depend on current use within the colony. * * @param exploreTiles A list of <code>Tile</code>s to update with tiles * to explore. * @param clearTiles A list of <code>Tile</code>s to update with tiles * to clear. * @param plowTiles A list of <code>Tile</code>s to update with tiles * to plow. * @param roadTiles A list of <code>Tile</code>s to update with tiles * to build roads on. */ public void getColonyTileTodo(List<Tile> exploreTiles, List<Tile> clearTiles, List<Tile> plowTiles, List<Tile> roadTiles) { final Specification spec = getSpecification(); final TileImprovementType clearImprovement = spec.getTileImprovementType("model.improvement.clearForest"); final TileImprovementType plowImprovement = spec.getTileImprovementType("model.improvement.plow"); final TileImprovementType roadImprovement = spec.getTileImprovementType("model.improvement.road"); for (Tile t : getTile().getSurroundingTiles(1)) { if (t.hasLostCityRumour()) exploreTiles.add(t); } for (ColonyTile ct : getColonyTiles()) { Tile t = ct.getWorkTile(); if (t == null) continue; // Colony has not claimed the tile yet. if ((t.getTileItemContainer() == null || t.getTileItemContainer() .getImprovement(plowImprovement) == null) && plowImprovement.isTileTypeAllowed(t.getType())) { if (ct.isColonyCenterTile()) { plowTiles.add(t); } else { for (Unit u : ct.getUnitList()) { if (u != null && u.getWorkType() != null && plowImprovement.getBonus(u.getWorkType()) > 0) { plowTiles.add(t); break; } } } } // To assess whether other improvements are beneficial we // really need a unit, doing work, so we can compare the output // with and without the improvement. This means we can skip // further consideration of the colony center tile. if (ct.isColonyCenterTile() || ct.isEmpty()) continue; TileType oldType = t.getType(); TileType newType; if ((t.getTileItemContainer() == null || t.getTileItemContainer() .getImprovement(clearImprovement) == null) && clearImprovement.isTileTypeAllowed(t.getType()) && (newType = clearImprovement.getChange(oldType)) != null) { for (Unit u : ct.getUnitList()) { if (newType.getProductionOf(u.getWorkType(), u.getType()) > oldType.getProductionOf(u.getWorkType(), u.getType())) { clearTiles.add(t); break; } } } if (t.getRoad() == null && roadImprovement.isTileTypeAllowed(t.getType())) { for (Unit u : ct.getUnitList()) { if (roadImprovement.getBonus(u.getWorkType()) > 0) { roadTiles.add(t); break; } } } } } /** * Finds another unit in this colony that would be better at doing the * job of the specified unit. * * @param expert The <code>Unit</code> to consider. * @return A better expert, or null if none available. */ public Unit getBetterExpert(Unit expert) { GoodsType production = expert.getWorkType(); GoodsType expertise = expert.getType().getExpertProduction(); Unit bestExpert = null; int bestImprovement = 0; if (production == null || expertise == null || production == expertise) return null; // We have an expert not doing the job of their expertise. // Check if there is a non-expert doing the job instead. for (Unit nonExpert : getUnitList()) { if (nonExpert.getWorkType() != expertise || nonExpert.getType() == expert.getType()) continue; // We have found a unit of a different type doing the // job of this expert's expertise now check if the // production would be better if the units swapped // positions. int expertProductionNow = 0; int nonExpertProductionNow = 0; int expertProductionPotential = 0; int nonExpertProductionPotential = 0; // Get the current and potential productions for the // work location of the expert. WorkLocation ewl = expert.getWorkLocation(); if (ewl != null) { expertProductionNow = ewl.getProductionOf(expert, expertise); nonExpertProductionPotential = ewl.getProductionOf(nonExpert, expertise); } // Get the current and potential productions for the // work location of the non-expert. WorkLocation nwl = nonExpert.getWorkTile(); if (nwl != null) { nonExpertProductionNow = nwl.getProductionOf(nonExpert, expertise); expertProductionPotential = nwl.getProductionOf(expert, expertise); } // Find the unit that achieves the best improvement. int improvement = expertProductionPotential + nonExpertProductionPotential - expertProductionNow - nonExpertProductionNow; if (improvement > bestImprovement) { bestImprovement = improvement; bestExpert = nonExpert; } } return bestExpert; } // Serialization /** * This method writes an XML-representation of this object to the given * stream. <br> * <br> * Only attributes visible to the given <code>Player</code> will be added * to that representation if <code>showAll</code> is set to * <code>false</code>. * * @param out The target stream. * @param player The <code>Player</code> this XML-representation should be * made for, or <code>null</code> if * <code>showAll == true</code>. * @param showAll Only attributes visible to <code>player</code> will be * added to the representation if <code>showAll</code> is set * to <i>false</i>. * @param toSavedGame If <code>true</code> then information that is only * needed when saving a game is added. * @throws XMLStreamException if there are any problems writing to the * stream. */ @Override protected void toXMLImpl(XMLStreamWriter out, Player player, boolean showAll, boolean toSavedGame) throws XMLStreamException { PlayerExploredTile pet; out.writeStartElement(getXMLElementTagName()); super.writeAttributes(out); out.writeAttribute("established", Integer.toString(established.getNumber())); if (showAll || toSavedGame || player.owns(this)) { out.writeAttribute("sonsOfLiberty", Integer.toString(sonsOfLiberty)); out.writeAttribute("oldSonsOfLiberty", Integer.toString(oldSonsOfLiberty)); out.writeAttribute("tories", Integer.toString(tories)); out.writeAttribute("oldTories", Integer.toString(oldTories)); out.writeAttribute("liberty", Integer.toString(liberty)); out.writeAttribute("immigration", Integer.toString(immigration)); out.writeAttribute("productionBonus", Integer.toString(productionBonus)); out.writeAttribute("landLocked", Boolean.toString(landLocked)); } else if ((pet = getTile().getPlayerExploredTile(player)) != null) { if (pet.getColonyUnitCount() > 0) { out.writeAttribute("unitCount", Integer.toString(pet.getColonyUnitCount())); } if (pet.getColonyStockadeKey() != null) { out.writeAttribute("stockadeKey", pet.getColonyStockadeKey()); } } writeChildren(out, player, showAll, toSavedGame); out.writeEndElement(); } /** * {@inheritDoc} */ protected void writeChildren(XMLStreamWriter out, Player player, boolean showAll, boolean toSavedGame) throws XMLStreamException { if (showAll || toSavedGame || player.owns(this)) { for (ExportData data : exportData.values()) { data.toXML(out); } // Only write the features that need specific instantiation, // which is currently only those with increments. // Fixed features will be added from their origins (usually // buildings). for (Modifier modifier : getFeatureContainer().getModifiers()) { if (modifier.hasIncrement()) { modifier.toXML(out); } } for (WorkLocation workLocation : getAllWorkLocations()) { workLocation.toXML(out, player, showAll, toSavedGame); } for (BuildableType item : buildQueue.getValues()) { out.writeStartElement(BUILD_QUEUE_TAG); out.writeAttribute(ID_ATTRIBUTE_TAG, item.getId()); out.writeEndElement(); } for (BuildableType item : populationQueue.getValues()) { out.writeStartElement(POPULATION_QUEUE_TAG); out.writeAttribute(ID_ATTRIBUTE_TAG, item.getId()); out.writeEndElement(); } super.writeChildren(out, player, showAll, toSavedGame); } } /** * Initialize this object from an XML-representation of this object. * * @param in The input stream with the XML. */ @Override protected void readFromXMLImpl(XMLStreamReader in) throws XMLStreamException { super.readAttributes(in); int oldUnitCount = getUnitCount(); owner.addSettlement(this); established = new Turn(getAttribute(in, "established", 0)); sonsOfLiberty = getAttribute(in, "sonsOfLiberty", 0); oldSonsOfLiberty = getAttribute(in, "oldSonsOfLiberty", 0); tories = getAttribute(in, "tories", 0); oldTories = getAttribute(in, "oldTories", 0); liberty = getAttribute(in, "liberty", 0); immigration = getAttribute(in, "immigration", 0); productionBonus = getAttribute(in, "productionBonus", 0); landLocked = getAttribute(in, "landLocked", true); if (!landLocked) { getFeatureContainer().addAbility(HAS_PORT); } unitCount = getAttribute(in, "unitCount", -1); stockadeKey = in.getAttributeValue(null, "stockadeKey"); // Clear containers colonyTiles.clear(); buildingMap.clear(); exportData.clear(); buildQueue.clear(); populationQueue.clear(); // Read child elements: while (in.nextTag() != XMLStreamConstants.END_ELEMENT) { if (in.getLocalName().equals(ColonyTile.getXMLElementTagName())) { ColonyTile ct = updateFreeColGameObject(in, ColonyTile.class); colonyTiles.add(ct); } else if (in.getLocalName().equals(Building.getXMLElementTagName())) { Building building = updateFreeColGameObject(in, Building.class); addBuilding(building); } else if (in.getLocalName().equals(ExportData.getXMLElementTagName())) { ExportData data = new ExportData(); data.readFromXML(in); exportData.put(data.getId(), data); } else if (Modifier.getXMLElementTagName().equals(in.getLocalName())) { Modifier modifier = new Modifier(in, getSpecification()); getFeatureContainer().addModifier(modifier); } else if ("buildQueue".equals(in.getLocalName())) { // TODO: remove support for old format, move serialization to BuildQueue int size = getAttribute(in, ARRAY_SIZE, 0); if (size > 0) { for (int x = 0; x < size; x++) { String typeId = in.getAttributeValue(null, "x" + Integer.toString(x)); buildQueue.add(getSpecification().getType(typeId, BuildableType.class)); } } in.nextTag(); } else if (BUILD_QUEUE_TAG.equals(in.getLocalName())) { String id = in.getAttributeValue(null, ID_ATTRIBUTE_TAG); buildQueue.add(getSpecification().getType(id, BuildableType.class)); in.nextTag(); } else if (POPULATION_QUEUE_TAG.equals(in.getLocalName())) { String id = in.getAttributeValue(null, ID_ATTRIBUTE_TAG); populationQueue.add(getSpecification().getType(id, UnitType.class)); in.nextTag(); } else { super.readChild(in); } } // @compat 0.9.x if (populationQueue.isEmpty()) { for (UnitType unitType : getSpecification().getUnitTypesWithAbility(Ability.BORN_IN_COLONY)) { GoodsType food = getSpecification().getGoodsType("model.goods.food"); List<AbstractGoods> required = unitType.getGoodsRequired(); boolean found = false; for (AbstractGoods goods : required) { if (goods.getType() == food) { found = true; break; } } if (!found) { required.add(new AbstractGoods(food, FOOD_PER_COLONIST)); unitType.setGoodsRequired(required); } populationQueue.add(unitType); } } // end compatibility code // Hack to kick AI colonies when the population changes. if (owner.isAI() && getUnitCount() != oldUnitCount) { firePropertyChange(REARRANGE_WORKERS, true, false); } } /** * Partial writer, so that "remove" messages can be brief. * * @param out The target stream. * @param fields The fields to write. * @throws XMLStreamException If there are problems writing the stream. */ @Override protected void toXMLPartialImpl(XMLStreamWriter out, String[] fields) throws XMLStreamException { toXMLPartialByClass(out, getClass(), fields); } /** * Partial reader, so that "remove" messages can be brief. * * @param in The input stream with the XML. * @throws XMLStreamException If there are problems reading the stream. */ @Override protected void readFromXMLPartialImpl(XMLStreamReader in) throws XMLStreamException { readFromXMLPartialByClass(in, getClass()); } /** * Gets the tag name of the root element representing this object. * * @return "colony". */ public static String getXMLElementTagName() { return "colony"; } }