/**
* 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.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;
import org.freecolandroid.xml.stream.XMLStreamException;
import org.freecolandroid.xml.stream.XMLStreamReader;
import org.freecolandroid.xml.stream.XMLStreamWriter;
import net.sf.freecol.common.model.Player.NoClaimReason;
/**
* Represents a work location on a tile. Each ColonyTile except the
* colony center tile provides a work place for a single unit and
* produces a single type of goods. The colony center tile generally
* produces two different of goods, one food type and one new world
* raw material.
*/
public class ColonyTile extends WorkLocation implements Ownable {
@SuppressWarnings("unused")
private static final Logger logger = Logger.getLogger(ColonyTile.class.getName());
public static final String UNIT_CHANGE = "UNIT_CHANGE";
/**
* The maximum number of units a ColonyTile can hold.
*/
public static final int UNIT_CAPACITY = 1;
/** The tile to work. */
protected Tile workTile;
/** Is this colony tile at the center of the colony. */
protected boolean colonyCenterTile;
/**
* Constructor for ServerColonyTile.
*/
protected ColonyTile() {
// empty constructor
}
/**
* Constructor for ServerColonyTile.
*
* @param game The <code>Game</code> this object belongs to.
* @param colony The <code>Colony</code> this object belongs to.
* @param workTile The tile in which this <code>ColonyTile</code>
* represents a <code>WorkLocation</code> for.
*/
protected ColonyTile(Game game, Colony colony, Tile workTile) {
super(game);
setColony(colony);
this.workTile = workTile;
colonyCenterTile = (getTile() == workTile);
}
/**
* Initiates a new <code>ColonyTile</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 occured during parsing.
*/
public ColonyTile(Game game, XMLStreamReader in) throws XMLStreamException {
super(game, in);
readFromXML(in);
}
/**
* Initiates a new <code>ColonyTile</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 ColonyTile(Game game, String id) {
super(game, id);
}
/**
* Returns the (non-unique) name of this <code>ColonyTile</code>.
* @return The name of this ColonyTile.
*/
public StringTemplate getLocationName() {
String name = getColony().getName();
if (isColonyCenterTile()) {
return StringTemplate.name(name);
} else {
return StringTemplate.template("nearLocation")
.add("%direction%", "direction."
+ getTile().getDirection(workTile).toString())
.addName("%location%", name);
}
}
/**
* Returns a description of the tile, with the name of the tile
* and any improvements made to it (road/plow).
*
* @return The description label for this tile
*/
public StringTemplate getLabel() {
return workTile.getLabel();
}
/**
* Checks if this is the tile where the <code>Colony</code> is located.
*
* @return True if this is the colony center tile.
*/
public boolean isColonyCenterTile() {
return colonyCenterTile;
}
/**
* Gets the work tile.
*
* @return The tile in which this <code>ColonyTile</code> represents a
* <code>WorkLocation</code> for.
*/
public Tile getWorkTile() {
return workTile;
}
/**
* Gets the <code>Unit</code> currently working on this
* <code>ColonyTile</code>.
*
* @return The <code>Unit</code> or <i>null</i> if no unit is present.
* TODO: deprecate this in favour of using the unit list, as we are
* unnecessarily encoding the assumption that there can only be one unit.
*/
public Unit getUnit() {
return (isEmpty()) ? null : getUnitList().get(0);
}
/**
* {@inheritDoc}
*/
@Override
public int getUnitCapacity() {
return (isColonyCenterTile()) ? 0 : UNIT_CAPACITY;
}
/**
* {@inheritDoc}
*/
public NoAddReason getNoWorkReason() {
Tile tile = getWorkTile();
NoClaimReason claim;
return (isColonyCenterTile())
? NoAddReason.COLONY_CENTER
: (tile.getOccupyingUnit() != null)
? NoAddReason.OCCUPIED_BY_ENEMY
: (!getColony().hasAbility(Ability.PRODUCE_IN_WATER)
&& !tile.isLand())
? NoAddReason.MISSING_ABILITY
: (tile.getOwningSettlement() == getColony())
? NoAddReason.NONE
: ((claim = getOwner().canClaimForSettlementReason(tile))
== NoClaimReason.NONE)
? NoAddReason.CLAIM_REQUIRED
: (claim == NoClaimReason.TERRAIN
|| claim == NoClaimReason.RUMOUR
|| claim == NoClaimReason.WATER)
? NoAddReason.MISSING_ABILITY
: (claim == NoClaimReason.SETTLEMENT)
? ((tile.getSettlement().getOwner() == getOwner())
? NoAddReason.ANOTHER_COLONY
: NoAddReason.OWNED_BY_ENEMY)
: (claim == NoClaimReason.WORKED)
? NoAddReason.ANOTHER_COLONY
: (claim == NoClaimReason.EUROPEANS)
? NoAddReason.OWNED_BY_ENEMY
: (claim == NoClaimReason.NATIVES)
? NoAddReason.CLAIM_REQUIRED
: NoAddReason.WRONG_TYPE;
}
/**
* {@inheritDoc}
*/
@Override
public NoAddReason getNoAddReason(Locatable locatable) {
NoAddReason reason = getNoWorkReason();
return (reason != NoAddReason.NONE) ? reason
: super.getNoAddReason(locatable);
}
/**
* Add the specified locatable to this colony tile.
*
* @param locatable The <code>Locatable</code> to add.
*/
public boolean add(final Locatable locatable) {
NoAddReason reason = getNoAddReason(locatable);
if (reason != NoAddReason.NONE) {
throw new IllegalStateException("Can not add " + locatable
+ " to " + toString() + " because " + reason);
}
Unit unit = (Unit) locatable;
if (contains(unit)) return true;
if (super.add(unit)) {
unit.setState(Unit.UnitState.IN_COLONY);
// Choose a sensible work type only if none already specified.
if (unit.getWorkType() == null) {
AbstractGoods goods = workTile.getType().getPrimaryGoods();
if (goods == null) {
goods = workTile.getType().getSecondaryGoods();
if (goods == null
&& !workTile.getType().getProduction().isEmpty()) {
goods = workTile.getType().getProduction().get(0);
}
}
if (goods != null) unit.setWorkType(goods.getType());
}
getColony().invalidateCache();
return true;
}
return false;
}
/**
* Remove the specified locatable from this colony tile.
*
* @param locatable The <code>Locatable</code> to be removed.
*/
public boolean remove(final Locatable locatable) {
if (!(locatable instanceof Unit)) {
throw new IllegalStateException("Not a unit: " + locatable);
}
Unit unit = (Unit) locatable;
if (!contains(unit)) return true;
if (super.remove(unit)) {
unit.setState(Unit.UnitState.ACTIVE);
unit.setMovesLeft(0);
getColony().invalidateCache();
return true;
}
return false;
}
/**
* Relocates any worker on this <code>ColonyTile</code>.
* The workers are added to another {@link WorkLocation}
* within the {@link Colony}.
*/
public void relocateWorkers() {
for (Unit unit : getUnitList()) {
for (WorkLocation wl : getColony().getCurrentWorkLocations()) {
if (wl != this && wl.canAdd(unit)) {
unit.setLocation(wl);
break;
}
}
}
}
/**
* Returns the unit who is occupying the tile
* @return the unit who is occupying the tile
* @see #isOccupied()
*/
public Unit getOccupyingUnit() {
return workTile.getOccupyingUnit();
}
/**
* Checks whether there is a fortified enemy unit in the tile.
* Units can't produce in occupied tiles
* @return <code>true</code> if an fortified enemy unit is in the tile
*/
public boolean isOccupied() {
return workTile.isOccupied();
}
/**
* Returns a worktype for a unit.
*
* @param unit a <code>Unit</code> value
* @return a workType
*/
public GoodsType getWorkType(Unit unit) {
GoodsType workType = unit.getWorkType();
int amount = (workType == null) ? 0 : getProductionOf(unit, workType);
if (amount == 0) {
List<GoodsType> farmedGoodsTypes = getSpecification().getFarmedGoodsTypeList();
for(GoodsType farmedGoods : farmedGoodsTypes) {
int newAmount = getProductionOf(unit, farmedGoods);
if (newAmount > amount) {
amount = newAmount;
workType = farmedGoods;
}
}
}
return workType;
}
/**
* Returns the primary production of a colony center tile. In the
* standard rule sets, this is always some kind of food and all
* tile improvements contribute to the production.
*
* @return an <code>AbstractGoods</code> value
*/
private AbstractGoods getPrimaryProduction() {
if (workTile.getType().getPrimaryGoods() == null) {
return null;
} else {
AbstractGoods primaryProduction = new AbstractGoods(workTile.getType().getPrimaryGoods());
int potential = primaryProduction.getAmount();
if (workTile.getTileItemContainer() != null) {
potential = workTile.getTileItemContainer()
.getTotalBonusPotential(primaryProduction.getType(), null,
potential, false);
}
primaryProduction.setAmount(potential + Math.max(0, getColony().getProductionBonus()));
return primaryProduction;
}
}
/**
* Returns the secondary production of a colony center tile. Only
* natural tile improvements, such as rivers, contribute to the
* production. Artificial tile improvements, such as plowing, are
* ignored.
*
* @return an <code>int</code> value
*/
private AbstractGoods getSecondaryProduction() {
if (workTile.getType().getSecondaryGoods() == null) {
return null;
} else {
AbstractGoods secondaryProduction = new AbstractGoods(workTile.getType().getSecondaryGoods());
int potential = secondaryProduction.getAmount();
if (workTile.getTileItemContainer() != null) {
potential = workTile.getTileItemContainer()
.getTotalBonusPotential(secondaryProduction.getType(), null,
potential, true);
}
secondaryProduction.setAmount(potential + Math.max(0, getColony().getProductionBonus()));
return secondaryProduction;
}
}
public List<AbstractGoods> getProduction() {
List<AbstractGoods> result = new ArrayList<AbstractGoods>(2);
if (isColonyCenterTile()) {
AbstractGoods primaryProduction = getPrimaryProduction();
if (primaryProduction != null) {
result.add(primaryProduction);
}
AbstractGoods secondaryProduction = getSecondaryProduction();
if (secondaryProduction != null) {
result.add(secondaryProduction);
}
} else {
for (Unit unit : getUnitList()) {
GoodsType goodsType = unit.getWorkType();
result.add(new AbstractGoods(goodsType, getProductionOf(unit,
goodsType)));
}
}
return result;
}
/**
* Gets the production modifiers for the given type of goods and unit.
*
* @param goodsType The <code>GoodsType</code> to produce.
* @param unitType The <code>unitType</code> to produce them.
* @return A set of the applicable modifiers.
*/
public Set<Modifier> getProductionModifiers(GoodsType goodsType, UnitType unitType) {
if (goodsType == null) {
throw new IllegalArgumentException("GoodsType must not be 'null'.");
}
Set<Modifier> result = new HashSet<Modifier>();
for (Unit unit : getUnitList()) {
if (isColonyCenterTile()
&& (workTile.getType().isPrimaryGoodsType(goodsType)
|| workTile.getType().isSecondaryGoodsType(goodsType))) {
result.addAll(workTile.getProductionBonus(goodsType, null));
result.addAll(getColony().getFeatureContainer().getModifierSet(goodsType.getId()));
} else if (goodsType.equals(unit.getWorkType())) {
result.addAll(workTile.getProductionBonus(goodsType, unitType));
result.addAll(unit.getModifierSet(goodsType.getId()));
}
}
return result;
}
/**
* Returns the production of the given type of goods.
*
* @param goodsType a <code>GoodsType</code> value
* @return an <code>int</code> value
*/
public int getProductionOf(GoodsType goodsType) {
if (goodsType == null) {
throw new IllegalArgumentException("GoodsType must not be 'null'.");
}
if (isColonyCenterTile()) {
TileType type = workTile.getType();
return (type.getPrimaryGoods() != null
&& type.getPrimaryGoods().getType() == goodsType)
? getPrimaryProduction().getAmount()
: (type.getSecondaryGoods() != null
&& type.getSecondaryGoods().getType() == goodsType)
? getSecondaryProduction().getAmount()
: 0;
}
int production = 0;
for (Unit unit : getUnitList()) {
if (goodsType.equals(unit.getWorkType())) {
production += getProductionOf(unit, goodsType);
}
}
return production;
}
/**
* Returns the production of the given type of goods which would
* be produced by the given unit
*
* @param unit an <code>Unit</code> value
* @param goodsType a <code>GoodsType</code> value
* @return an <code>int</code> value
*/
public int getProductionOf(Unit unit, GoodsType goodsType) {
if (unit == null) {
throw new IllegalArgumentException("Unit must not be 'null'.");
} else if (workTile.isLand() || getColony().hasAbility(Ability.PRODUCE_IN_WATER)) {
Set<Modifier> modifiers = workTile.getProductionBonus(goodsType, unit.getType());
if (FeatureContainer.applyModifierSet(0f, getGame().getTurn(), modifiers) > 0) {
modifiers.addAll(unit.getModifierSet(goodsType.getId()));
modifiers.add(getColony().getProductionModifier(goodsType));
modifiers.addAll(getColony().getModifierSet(goodsType.getId()));
List<Modifier> modifierList = new ArrayList<Modifier>(modifiers);
Collections.sort(modifierList);
return Math.max(1, (int) FeatureContainer.applyModifiers(0, getGame().getTurn(), modifierList));
} else {
return 0;
}
} else {
return 0;
}
}
/**
* Gets the potential production of a given goods type from using
* a unit of a given type in this building.
*
* @param unitType The optional <code>UnitType</code> to check.
* @param goodsType The <code>GoodsType</code> to check.
* @return The amount of goods potentially produced.
*/
public int getPotentialProduction(UnitType unitType, GoodsType goodsType) {
int production = 0;
if (isColonyCenterTile()) {
TileType type = workTile.getType();
return (type.getPrimaryGoods() != null
&& type.getPrimaryGoods().getType() == goodsType)
? getPrimaryProduction().getAmount()
: (type.getSecondaryGoods() != null
&& type.getSecondaryGoods().getType() == goodsType)
? getSecondaryProduction().getAmount()
: 0;
}
if (workTile.isLand()
|| getColony().hasAbility(Ability.PRODUCE_IN_WATER)) {
Set<Modifier> modifiers = workTile.getProductionBonus(goodsType,
unitType);
if (FeatureContainer.applyModifierSet(0f, getGame().getTurn(),
modifiers) > 0) {
if (unitType != null) {
modifiers.addAll(unitType.getModifierSet(goodsType.getId()));
}
modifiers.add(getColony().getProductionModifier(goodsType));
modifiers.addAll(getColony().getModifierSet(goodsType.getId()));
List<Modifier> modifierList = new ArrayList<Modifier>(modifiers);
Collections.sort(modifierList);
production = Math.max((int) FeatureContainer.applyModifiers(0,
getGame().getTurn(), modifierList), 1);
}
}
return production;
}
/**
* Colony tiles can not auto-produce except for the center tile.
*
* @return True if this work location can produce goods without workers.
*/
public boolean canAutoProduce() {
return isColonyCenterTile();
}
/**
* 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.
*/
protected void toXMLImpl(XMLStreamWriter out, Player player,
boolean showAll, boolean toSavedGame)
throws XMLStreamException {
// Start
out.writeStartElement(getXMLElementTagName());
// Attributes
super.writeAttributes(out);
out.writeAttribute("workTile", workTile.getId());
// Children
super.writeChildren(out, player, showAll, toSavedGame);
// End
out.writeEndElement();
}
/**
* Initialize this object from an XML-representation of this object.
* @param in The input stream with the XML.
* @throws XMLStreamException if a problem was encountered
* during parsing.
*/
protected void readFromXMLImpl(XMLStreamReader in) throws XMLStreamException {
super.readAttributes(in);
workTile = getFreeColGameObject(in, "workTile", Tile.class);
colonyCenterTile = (getTile() == workTile);
super.readChildren(in);
}
/**
* 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());
}
/**
* Will return the position of the tile and the name of the colony in
* addition to the FreeColObject.toString().
*
* @return A representation of a colony-tile that can be used for
* debugging.
*/
public String toString() {
return "ColonyTile " + getWorkTile().getPosition().toString()
+ " in '" + getColony().getName() + "'";
}
/**
* Gets the tag name of the root element representing this object.
* @return "colonyTile".
*/
public static String getXMLElementTagName() {
return "colonyTile";
}
}