/** * 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.HashSet; import java.util.List; import java.util.Set; import java.util.logging.Logger; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; import javax.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. This is accessed through getWorkTile(). * Beware! Do not confuse this with getTile(), which returns * the colony center tile (because every work location belongs to * the enclosing colony). */ protected Tile workTile; /** * 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; } /** * 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 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 getWorkTile() == getTile(); } /** * Gets the work tile. * * @return The tile in which this <code>ColonyTile</code> represents a * <code>WorkLocation</code> for. */ public Tile getWorkTile() { return workTile; } /** * 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 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; } } /** * Gets the basic production information for the colony tile, * ignoring any colony limited (which for now, should be irrelevant). * * @return The raw production of this colony tile. * @see ProductionCache#update */ public ProductionInfo getBasicProductionInfo() { ProductionInfo pi = new ProductionInfo(); if (isColonyCenterTile()) { AbstractGoods primaryProduction = getPrimaryProduction(); if (primaryProduction != null) { pi.addProduction(primaryProduction); } AbstractGoods secondaryProduction = getSecondaryProduction(); if (secondaryProduction != null) { pi.addProduction(secondaryProduction); } } else { for (Unit unit : getUnitList()) { GoodsType goodsType = unit.getWorkType(); pi.addProduction(new AbstractGoods(goodsType, getProductionOf(unit, goodsType))); } } return pi; } // Interface Location /** * {@inheritDoc} */ 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); } } // Omit getLocationNameFor /** * {@inheritDoc} */ 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; } /** * {@inheritDoc} */ 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; } // Interface UnitLocation /** * {@inheritDoc} */ @Override public NoAddReason getNoAddReason(Locatable locatable) { NoAddReason reason = getNoWorkReason(); return (reason != NoAddReason.NONE) ? reason : super.getNoAddReason(locatable); } /** * {@inheritDoc} */ @Override public int getUnitCapacity() { return (isColonyCenterTile()) ? 0 : UNIT_CAPACITY; } // Interface WorkLocation /** * {@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} */ public boolean canAutoProduce() { return isColonyCenterTile(); } /** * {@inheritDoc} */ public int getProductionOf(Unit unit, GoodsType goodsType) { if (unit == null) { throw new IllegalArgumentException("Null unit."); } return getPotentialProduction(goodsType, unit.getType()); } /** * {@inheritDoc} */ public int getPotentialProduction(GoodsType goodsType, UnitType unitType) { int production = 0; TileType tileType = workTile.getType(); if (isColonyCenterTile()) { production = (unitType != null) ? 0 : (tileType.getPrimaryGoods() != null && tileType.getPrimaryGoods().getType() == goodsType) ? getPrimaryProduction().getAmount() : (tileType.getSecondaryGoods() != null && tileType.getSecondaryGoods().getType() == goodsType) ? getSecondaryProduction().getAmount() : 0; } else if (workTile.isLand() || getColony().hasAbility(Ability.PRODUCE_IN_WATER)) { List<Modifier> mods = getProductionModifiers(goodsType, unitType); if (!mods.isEmpty()) { production = (int)FeatureContainer.applyModifiers(0f, getGame().getTurn(), mods); } } return Math.max(0, production); } /** * {@inheritDoc} */ public List<Modifier> getProductionModifiers(GoodsType goodsType, UnitType unitType) { if (goodsType == null) { throw new IllegalArgumentException("Null GoodsType."); } List<Modifier> result = new ArrayList<Modifier>(); final Colony colony = getColony(); final Player owner = colony.getOwner(); final TileType tileType = getWorkTile().getType(); final String id = goodsType.getId(); final Turn turn = getGame().getTurn(); if (isColonyCenterTile()) { if (tileType.isPrimaryGoodsType(goodsType) || tileType.isSecondaryGoodsType(goodsType)) { result.addAll(workTile.getProductionModifiers(goodsType, null)); result.addAll(colony.getModifierSet(id, null, turn)); result.add(colony.getProductionModifier(goodsType)); if (owner != null) { result.addAll(owner.getModifierSet(id, null, turn)); } } } else { result.addAll(workTile.getProductionModifiers(goodsType, unitType)); if (FeatureContainer.applyModifiers(0f, turn, result) > 0) { result.addAll(colony.getModifierSet(id, null, turn)); if (unitType != null) { result.add(colony.getProductionModifier(goodsType)); result.addAll(unitType.getModifierSet(id, tileType, turn)); if (owner != null) { result.addAll(owner.getModifierSet(id, null, turn)); } } else { if (owner != null) { result.addAll(owner.getModifierSet(id, tileType, turn)); } } } } return result; } /** * {@inheritDoc} */ public GoodsType getBestWorkType(Unit unit) { GoodsType workType = unit.getWorkType(); int amount = 0; for (GoodsType g : getSpecification().getFarmedGoodsTypeList()) { int newAmount = getPotentialProduction(g, unit.getType()); if (newAmount > amount) { amount = newAmount; workType = g; } } return workType; } /** * {@inheritDoc} */ public StringTemplate getClaimTemplate() { return (isColonyCenterTile()) ? super.getClaimTemplate() : StringTemplate.template("workClaimColonyTile") .add("%direction%", "direction." + getTile().getDirection(workTile).toString()); } // 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. */ 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. */ public void readAttributes(XMLStreamReader in) throws XMLStreamException { super.readAttributes(in); workTile = getFreeColGameObject(in, "workTile", Tile.class); } /** * 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 public 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"; } }