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