/**
* 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";
}
}