/**
* 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.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLStreamWriter;
import net.sf.freecol.common.util.RandomChoice;
public final class TileImprovementType extends FreeColGameObjectType {
private boolean natural;
private int magnitude;
private int addWorkTurns;
private TileImprovementType requiredImprovementType;
private Set<String> allowedWorkers = new HashSet<String>();
private EquipmentType expendedEquipmentType;
private int expendedAmount;
// @compat 0.10.4
private GoodsType deliverGoodsType;
private int deliverAmount;
// end @compat
private Map<TileType, TileTypeChange> tileTypeChanges
= new HashMap<TileType, TileTypeChange>();
/**
* The disasters that may strike this type of tile improvement.
*/
private List<RandomChoice<Disaster>> disasters
= new ArrayList<RandomChoice<Disaster>>();
private int movementCost;
private float movementCostFactor;
/**
* The layer a TileItem belongs to. Items with higher zIndex
* will be displayed above items with a lower zIndex. E.g. the
* LostCityRumour would be displayed above the Plow improvement.
*/
private int zIndex;
/**
* Can this improvement expose a resource when completed? This
* should only apply to improvement types that change the
* underlying tile type (e.g. clearing forests).
*/
private int exposeResourcePercent;
/**
* The scopes define which TileTypes support this improvement. An
* eligible TileType must match all scopes.
*/
private List<Scope> scopes = new ArrayList<Scope>();
// ------------------------------------------------------------ constructors
public TileImprovementType(String id, Specification specification) {
super(id, specification);
setModifierIndex(Modifier.IMPROVEMENT_PRODUCTION_INDEX);
}
// ------------------------------------------------------------ retrieval methods
public boolean isNatural() {
return natural;
}
public int getMagnitude() {
return magnitude;
}
public int getAddWorkTurns() {
return addWorkTurns;
}
/**
* Get the <code>ZIndex</code> value.
*
* @return an <code>int</code> value
*/
public int getZIndex() {
return zIndex;
}
/**
* Set the <code>ZIndex</code> value.
*
* @param newZIndex The new ZIndex value.
*/
public void setZIndex(final int newZIndex) {
this.zIndex = newZIndex;
}
public TileImprovementType getRequiredImprovementType() {
return requiredImprovementType;
}
public EquipmentType getExpendedEquipmentType() {
return expendedEquipmentType;
}
public int getExpendedAmount() {
return expendedAmount;
}
/**
* Returns the goods produced by applying this TileImprovementType
* to a Tile with the given TileType.
*
* @param from a <code>TileType</code> value
* @return an <code>AbstractGoods</code> value
*/
public AbstractGoods getProduction(TileType from) {
TileTypeChange change = tileTypeChanges.get(from);
return change == null ? null : change.getProduction();
}
/**
* Get the <code>Scopes</code> value.
*
* @return a <code>List<Scope></code> value
*/
public List<Scope> getScopes() {
return scopes;
}
/**
* Return an ID of an appropriate action.
*
* @return a <code>String</code> value
*/
public String getShortId() {
int index = getId().lastIndexOf('.') + 1;
return getId().substring(index);
}
public boolean isWorkerTypeAllowed(UnitType unitType) {
return allowedWorkers.isEmpty() || allowedWorkers.contains(unitType.getId());
}
/**
* Check if a given <code>Unit</code> can perform this TileImprovement.
* @return true if Worker UnitType is allowed and expended Goods are available
*/
public boolean isWorkerAllowed(Unit unit) {
if (!isWorkerTypeAllowed(unit.getType())) {
return false;
}
return (unit.getEquipment().getCount(expendedEquipmentType) >= expendedAmount);
}
/**
* This will check if in principle this type of improvement can be used on
* this kind of tile, disregarding the current state of an actual tile.
*
* If you want to find out if an improvement is allowed for a tile, call
* {@link #isTileAllowed(Tile)}.
*
* @param tileType The type of terrain
* @return true if improvement is possible
*/
public boolean isTileTypeAllowed(TileType tileType) {
for (Scope scope : scopes) {
if (!scope.appliesTo(tileType)) {
return false;
}
}
return true;
}
/**
* Check if a given <code>Tile</code> is valid for this TileImprovement.
*
* @return true if Tile TileType is valid and required Improvement (if any)
* is present.
*/
public boolean isTileAllowed(Tile tile) {
if (!isTileTypeAllowed(tile.getType())) {
return false;
}
if (requiredImprovementType != null && tile.findTileImprovementType(requiredImprovementType) == null) {
return false;
}
TileImprovement ti = tile.findTileImprovementType(this);
return ti == null || !ti.isComplete();
}
public int getBonus(GoodsType goodsType) {
Modifier result = getProductionModifier(goodsType);
if (result == null) {
return 0;
} else {
return (int) result.getValue();
}
}
public Modifier getProductionModifier(GoodsType goodsType) {
Set<Modifier> modifierSet = getModifierSet(goodsType.getId());
if (modifierSet == null || modifierSet.isEmpty()) {
return null;
} else {
if (modifierSet.size() > 1) {
logger.warning("Only one Modifier for " + goodsType.getId()
+ " expected!");
}
return modifierSet.iterator().next();
}
}
/**
* Returns true if this TileImprovementType changes the underlying
* tile type.
*
* @return a <code>boolean</code> value
*/
public boolean isChangeType() {
return !tileTypeChanges.isEmpty();
}
/**
* Returns the destination type of a tile type change (or null).
*
* @param tileType a <code>TileType</code> value
* @return a <code>TileType</code> value
*/
public TileType getChange(TileType tileType) {
TileTypeChange change = tileTypeChanges.get(tileType);
return change == null ? null : change.getTo();
}
/**
* Returns true if this TileImprovementType can change a tile type
* to the given tile type.
*
* @param tileType a <code>TileType</code> value
* @return a <code>boolean</code> value
*/
public boolean changeContainsTarget(TileType tileType) {
for (TileTypeChange change : tileTypeChanges.values()) {
if (change.getTo() == tileType) {
return true;
}
}
return false;
}
/**
* Performs reduction of the movement-cost.
* @param moveCost Original movement cost
* @return The movement cost after any change
*/
public int getMovementCost(int moveCost) {
int cost = moveCost;
if (movementCostFactor >= 0) {
float cost2 = cost * movementCostFactor;
cost = (int)cost2;
if (cost < cost2) {
cost++;
}
}
if (movementCost > 0) {
// Only >0 values are meaningful (see spec).
// Do not return zero from a movement costing routine or
// units get free moves!
if (movementCost < cost) {
cost = movementCost;
}
}
return cost;
}
/**
* Gets the percent chance that this tile improvement can expose a
* resource on the tile. This only applies to TileImprovementTypes
* that change the underlying tile type (e.g. clearing forests).
*
* @return The exposure chance.
*/
public int getExposeResourcePercent() {
return exposeResourcePercent;
}
/**
* Gets the increase in production of the given GoodsType
* this tile improvement type would yield at a specified tile.
*
* @param tile The <code>Tile</code> to be considered.
* @param goodsType A preferred <code>GoodsType</code> or <code>null</code>
* @return The increase in production
*/
public int getImprovementValue(Tile tile, GoodsType goodsType) {
int value = 0;
if (goodsType.isFarmed()) {
TileType newTileType = getChange(tile.getType());
if (newTileType == null) { // simple bonus
int production = tile.potential(goodsType, null);
if (production > 0) {
float chg = applyModifier(production, goodsType.getId());
value = (int)(chg - production);
}
} else { // tile type change
int chg = newTileType.getProductionOf(goodsType, null)
- tile.getType().getProductionOf(goodsType, null);
value = chg;
}
}
return value;
}
/**
* Return a weighted list of natural disasters than can strike
* this tile improvement type.
*
* @return a <code>List<RandomChoice<Disaster>></code> value
*/
public List<RandomChoice<Disaster>> getDisasters() {
return disasters;
}
/**
* Makes an XML-representation of this object.
*
* @param out The output stream.
* @throws XMLStreamException if there are any problems writing to the
* stream.
*/
public void toXMLImpl(XMLStreamWriter out) throws XMLStreamException {
super.toXML(out, getXMLElementTagName());
}
/**
* Write the attributes of this object to a stream.
*
* @param out The target stream.
* @throws XMLStreamException if there are any problems writing to
* the stream.
*/
@Override
protected void writeAttributes(XMLStreamWriter out)
throws XMLStreamException {
super.writeAttributes(out);
out.writeAttribute("natural", Boolean.toString(natural));
out.writeAttribute("add-work-turns", Integer.toString(addWorkTurns));
out.writeAttribute("movement-cost", Integer.toString(movementCost));
out.writeAttribute("magnitude", Integer.toString(magnitude));
out.writeAttribute("zIndex", Integer.toString(zIndex));
out.writeAttribute("exposeResourcePercent",
Integer.toString(exposeResourcePercent));
if (requiredImprovementType != null) {
out.writeAttribute("required-improvement",
requiredImprovementType.getId());
}
if (expendedEquipmentType != null) {
out.writeAttribute("expended-equipment-type",
expendedEquipmentType.getId());
out.writeAttribute("expended-amount",
Integer.toString(expendedAmount));
}
}
/**
* Write the children of this object to a stream.
*
* @param out The target stream.
* @throws XMLStreamException if there are any problems writing to
* the stream.
*/
@Override
protected void writeChildren(XMLStreamWriter out)
throws XMLStreamException {
super.writeChildren(out);
if (scopes != null) {
for (Scope scope : scopes) {
scope.toXMLImpl(out);
}
}
if (allowedWorkers != null) {
for (String id : allowedWorkers) {
out.writeStartElement("worker");
out.writeAttribute(ID_ATTRIBUTE_TAG, id);
out.writeEndElement();
}
}
if (tileTypeChanges != null) {
for (TileTypeChange change : tileTypeChanges.values()) {
change.toXML(out);
}
}
for (RandomChoice<Disaster> choice : disasters) {
out.writeStartElement("disaster");
out.writeAttribute("id", choice.getObject().getId());
out.writeAttribute("probability",
Integer.toString(choice.getProbability()));
out.writeEndElement();
}
}
/**
* Reads the attributes of this object from an XML stream.
*
* @param in The XML input stream.
* @throws XMLStreamException if a problem was encountered
* during parsing.
*/
@Override
protected void readAttributes(XMLStreamReader in)
throws XMLStreamException {
super.readAttributes(in);
natural = getAttribute(in, "natural", false);
addWorkTurns = getAttribute(in, "add-work-turns", 0);
movementCost = getAttribute(in, "movement-cost", 0);
movementCostFactor = -1;
magnitude = getAttribute(in, "magnitude", 1);
requiredImprovementType = getSpecification().getType(in,
"required-improvement", TileImprovementType.class, null);
zIndex = getAttribute(in, "zIndex", 0);
exposeResourcePercent = getAttribute(in, "exposeResourcePercent", 0);
expendedEquipmentType = getSpecification().getType(in,
"expended-equipment-type", EquipmentType.class, null);
expendedAmount = getAttribute(in, "expended-amount", 0);
// @compat 0.10.4
deliverGoodsType = getSpecification().getType(in,
"deliver-goods-type", GoodsType.class, null);
deliverAmount = getAttribute(in, "deliver-amount", 0);
// end @compat
}
/**
* Reads a child object.
*
* @param in The XML stream to read.
* @exception XMLStreamException if an error occurs
*/
@Override
protected void readChild(XMLStreamReader in) throws XMLStreamException {
String childName = in.getLocalName();
if ("scope".equals(childName)) {
scopes.add(new Scope(in));
} else if ("worker".equals(childName)) {
allowedWorkers.add(in.getAttributeValue(null, ID_ATTRIBUTE_TAG));
in.nextTag(); // close this element
} else if ("change".equals(childName)) {
TileTypeChange change = new TileTypeChange();
if (deliverGoodsType == null) {
change.readFromXML(in, getSpecification());
} else {
// @compat 0.10.4
change.setFrom(getSpecification().getTileType(in.getAttributeValue(null, "from")));
change.setTo(getSpecification().getTileType(in.getAttributeValue(null, "to")));
change.setProduction(new AbstractGoods(deliverGoodsType, deliverAmount));
// end @compat
in.nextTag(); // close this element
}
tileTypeChanges.put(change.getFrom(), change);
} else if ("disaster".equals(childName)) {
Disaster disaster = getSpecification().getDisaster(in.getAttributeValue(null, "id"));
int probability = getAttribute(in, "probability", 100);
disasters.add(new RandomChoice<Disaster>(disaster, probability));
in.nextTag(); // close this element
} else {
super.readChild(in);
}
}
/**
* Returns the tag name of the root element representing this object.
*
* @return "tileimprovement-type".
*/
public static String getXMLElementTagName() {
return "tileimprovement-type";
}
}