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