/** * 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.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.Map.Direction; /** * Represents a tile improvement, such as a river or road. */ public class TileImprovement extends TileItem implements Named { private static Logger logger = Logger.getLogger(TileImprovement.class.getName()); private TileImprovementType type; private int turnsToComplete; /** * Default is type.getMagnitude(), but this will override. */ private int magnitude; /** * River magnitudes */ public static final int NO_RIVER = 0; public static final int SMALL_RIVER = 1; public static final int LARGE_RIVER = 2; public static final int FJORD_RIVER = 3; /** * To store the style of multi-image TileImprovements (eg. rivers) * Rivers have 4 directions {NE=1, SE=3, SW=9, NW=27}, and 3 levels (see above) * @see Map * @see net.sf.freecol.server.generator.River */ private int style; /** * Whether this is a virtual improvement granted by some structure * on the tile (a Colony, for example). Virtual improvements will * be removed along with the structure that granted them. */ private boolean virtual; // ------------------------------------------------------------ constructor /** * Creates a standard <code>TileImprovement</code>-instance. * * This constructor asserts that the game, tile and type are valid. * * @param game The <code>Game</code> in which this object belongs. * @param tile The <code>Tile</code> on which this object sits. * @param type The <code>TileImprovementType</code> of this TileImprovement. */ public TileImprovement(Game game, Tile tile, TileImprovementType type) { super(game, tile); if (type == null) { throw new IllegalArgumentException("Parameter 'type' must not be 'null'."); } this.type = type; if (!type.isNatural()) { this.turnsToComplete = tile.getType().getBasicWorkTurns() + type.getAddWorkTurns(); } this.magnitude = type.getMagnitude(); } public TileImprovement(Game game, XMLStreamReader in) throws XMLStreamException { super(game, in); readFromXML(in); } /** * Initiates a new <code>TileImprovement</code> with the given ID. The object * should later be initialized by calling either * {@link #readFromXML(XMLStreamReader)} or * {@link #readFromXMLElement(Element)}. * * @param game The <code>Game</code> in which this object belong. * @param id The unique identifier for this object. */ public TileImprovement(Game game, String id) { super(game, id); } // ------------------------------------------------------------ retrieval methods public TileImprovementType getType() { return type; } public int getMagnitude() { return magnitude; } public void setMagnitude(int magnitude) { this.magnitude = magnitude; } /** * Get the <code>Virtual</code> value. * * @return a <code>boolean</code> value */ public final boolean isVirtual() { return virtual; } /** * Set the <code>Virtual</code> value. * * @param newVirtual The new Virtual value. */ public final void setVirtual(final boolean newVirtual) { this.virtual = newVirtual; } /** * Is this <code>TileImprovement</code> a road? * @return a <code>boolean</code> value */ public boolean isRoad() { return getType().getId().equals("model.improvement.road"); } /** * Is this <code>TileImprovement</code> a river? * @return a <code>boolean</code> value */ public boolean isRiver() { return getType().getId().equals("model.improvement.river"); } public String getNameKey() { return getType().getNameKey(); } /** * Returns a textual representation of this object. * @return A <code>String</code> of either: * <ol> * <li>NAME (#TURNS turns left) (eg. Road (2 turns left) ) if it is under construction * <li>NAME (eg. Road) if it is complete * </ol> */ public String toString() { if (turnsToComplete > 0) { return getType().getId() + " (" + Integer.toString(turnsToComplete) + " turns left)"; } else { return getType().getId(); } } /** * @return the current turns to completion. */ public int getTurnsToComplete() { return turnsToComplete; } /** * Update the turns required to complete the improvement. * * @param turns an <code>int</code> value */ public void setTurnsToComplete(int turns) { turnsToComplete = turns; } /** * Get the <code>ZIndex</code> value. * * @return an <code>int</code> value */ public final int getZIndex() { return type.getZIndex(); } public boolean isComplete() { return turnsToComplete <= 0; } public EquipmentType getExpendedEquipmentType() { return type.getExpendedEquipmentType(); } public int getExpendedAmount() { return type.getExpendedAmount(); } /** * Returns the bonus (if any). * @param goodsType a <code>GoodsType</code> value * @return an <code>int</code> value */ public int getBonus(GoodsType goodsType) { if (!isComplete()) { return 0; } return type.getBonus(goodsType); } /** * Returns the bonus Modifier (if any). * @param goodsType a <code>GoodsType</code> value * @return a <code>Modifier</code> value */ public Modifier getProductionModifier(GoodsType goodsType) { if (!isComplete()) { return null; } return type.getProductionModifier(goodsType); } /** * Performs reduction of the movement-cost. * @param moveCost Original movement cost * @return The movement cost after any change */ public int getMovementCost(int moveCost, Tile fromTile) { if (!isComplete()) { return moveCost; } String typeId = type.getId(); if (typeId == null) { // No checking for matching type return type.getMovementCost(moveCost); } // Find matching type for (TileImprovement improvement : fromTile.getTileImprovements()) { if (improvement.getType().getId().equals(typeId)) { // Matched return type.getMovementCost(moveCost); } } // No match return moveCost; } /** * Returns any change of TileType * @return The new TileType. */ public TileType getChange(TileType tileType) { if (!isComplete()) { return null; } return type.getChange(tileType); } /** * Returns the Style of this Improvement - used for Rivers * @return The style */ public int getStyle() { return style; } /** * Sets the Style of this Improvement - used for Rivers * @param style The style */ public void setStyle(int style) { this.style = style; } /** * Returns an int[NUMBER_OF_DIRECTIONS] array based on the * baseNumber and the 'active' directions given. * * @param directions An int[] that gives the active directions eg * {Map.N, Map.NE, Map.E, Map.SE, Map.S, Map.SW, Map.W, Map.NW}, * or {Map.E, Map.SW}; * @param baseNumber The base to be used to create the base array. * @return A base array that can create unique identifiers for any * combination */ public static int[] getBase(Direction[] directions, int baseNumber) { Direction[] allDirections = Direction.values(); int[] base = new int[allDirections.length]; int n = 1; for (int i = 0; i < allDirections.length; i++) { base[i] = 0; for (Direction direction : directions) { if (direction == allDirections[i]) { base[i] = n; n *= baseNumber; break; } } } return base; } /** * Breaks the Style of this Improvement into 8 directions - used for Rivers (at the moment) * @param directions An int[] that gives the active directions * eg {Map.N, Map.NE, Map.E, Map.SE, Map.S, Map.SW, Map.W, Map.NW}, * or {Map.E, Map.SW}; * @param baseNumber The base to be used to create the base array. * @return An int[] with the magnitude in each direction. */ public int[] getStyleBreakdown(Direction[] directions, int baseNumber) { return getStyleBreakdown(getBase(directions, baseNumber)); } /** * Breaks the Style of this Improvement into 8 directions - used for Rivers (at the moment) * Possible TODO: Modify this later should we modify the usage of Style. * @param base Use {@link #getBase} * @return An int[] with the magnitude in each direction. */ public int[] getStyleBreakdown(int[] base) { int[] result = new int[8]; int tempStyle = style; for (int i = base.length - 1; i >= 0; i--) { if (base[i] == 0) { continue; // Skip this direction } result[i] = tempStyle / base[i]; // Get an integer value 0-2 for a direction tempStyle -= result[i] * base[i]; // Remove the component of this direction } return result; } public void compileStyleBreakdown(int[] base, int[] breakdown) { if (base.length != breakdown.length) { logger.warning("base.length != breakdown.length"); return; } style = 0; for (int i = 0; i < base.length; i++) { style += base[i] * breakdown[i]; } } /** * Checks if a given worker can work at this Improvement */ public boolean isWorkerAllowed(Unit unit) { if (unit == null) { return false; } if (isComplete()) { return false; } return type.isWorkerAllowed(unit); } /** * {@inheritDoc} */ public boolean isTileTypeAllowed(TileType tileType) { return type.isTileTypeAllowed(tileType); } /** * 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 { // Start element: out.writeStartElement(getXMLElementTagName()); // Add attributes: out.writeAttribute(ID_ATTRIBUTE, getId()); out.writeAttribute("tile", getTile().getId()); out.writeAttribute("type", getType().getId()); out.writeAttribute("turns", Integer.toString(turnsToComplete)); out.writeAttribute("magnitude", Integer.toString(magnitude)); out.writeAttribute("style", Integer.toString(style)); if (virtual) { out.writeAttribute("virtual", Boolean.toString(virtual)); } // End element: 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. */ @Override protected void readFromXMLImpl(XMLStreamReader in) throws XMLStreamException { setId(in.getAttributeValue(null, ID_ATTRIBUTE)); tile = (Tile) getGame().getFreeColGameObject(in.getAttributeValue(null, "tile")); if (tile == null) { tile = new Tile(getGame(), in.getAttributeValue(null, "tile")); } type = getSpecification().getTileImprovementType(in.getAttributeValue(null, "type")); turnsToComplete = Integer.parseInt(in.getAttributeValue(null, "turns")); magnitude = Integer.parseInt(in.getAttributeValue(null, "magnitude")); style = Integer.parseInt(in.getAttributeValue(null, "style")); virtual = getAttribute(in, "virtual", false); in.nextTag(); } /** * Gets the tag name of the root element representing this object. * * @return "tileImprovement". */ public static String getXMLElementTagName() { return "tileimprovement"; } }