/**
* 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.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;
/**
* Represents a building in a colony.
*/
public class Building extends WorkLocation implements Named, Comparable<Building>, Consumer {
@SuppressWarnings("unused")
private static final Logger logger = Logger.getLogger(Building.class.getName());
public static final String UNIT_CHANGE = "UNIT_CHANGE";
/** The type of building. */
protected BuildingType buildingType;
/**
* Constructor for ServerBuilding.
*/
protected Building() {
// empty constructor
}
/**
* Constructor for ServerBuilding.
*
* @param game The <code>Game</code> this object belongs to.
* @param colony The <code>Colony</code> in which this building is located.
* @param type The <code>BuildingType</code> of building.
*/
protected Building(Game game, Colony colony, BuildingType type) {
super(game);
setColony(colony);
this.buildingType = type;
}
/**
* Initiates a new <code>Building</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 a problem was encountered during parsing.
*/
public Building(Game game, XMLStreamReader in) throws XMLStreamException {
super(game, in);
readFromXML(in);
}
/**
* Initiates a new <code>Building</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 Building(Game game, String id) {
super(game, id);
}
/**
* Gets the type of this building.
*
* @return The building type.
*/
public BuildingType getType() {
return buildingType;
}
/**
* Changes the type of the Building. The type of a building may
* change when it is upgraded or damaged.
*
* @param newBuildingType
* @see #upgrade
* @see #damage
*/
private void setType(final BuildingType newBuildingType) {
// remove features from current type
Colony colony = getColony();
colony.removeFeatures(buildingType);
if (newBuildingType != null) {
buildingType = newBuildingType;
// add new features and abilities from new type
colony.addFeatures(buildingType);
// Colonists which can't work here must be put outside
for (Unit unit : getUnitList()) {
if (!canAddType(unit.getType())) {
unit.setLocation(colony.getTile());
}
}
}
// Colonists exceding units limit must be put outside
if (getUnitCount() > getUnitCapacity()) {
for (Unit unit : getUnitList().subList(getUnitCapacity(),
getUnitCount())) {
unit.setLocation(colony.getTile());
}
}
}
/**
* Is an ability present in this Building?
* Delegate to the type.
*
* @param id The id of the ability to test.
* @param fcgot An optional <code>FreeColGameObjectType</code> the
* ability applies to.
* @param turn An optional applicable <code>Turn</code>.
* @return True if the ability is present.
*/
@Override
public boolean hasAbility(String id, FreeColGameObjectType type,
Turn turn) {
return getType().hasAbility(id);
}
/**
* Gets the set of modifiers with the given Id from this Building.
* Delegate to the type.
*
* @param id The id of the modifier to test.
* @param fcgot An optional <code>FreeColGameObjectType</code> the
* modifier applies to.
* @param turn An optional applicable <code>Turn</code>.
* @return A set of modifiers.
*/
@Override
public Set<Modifier> getModifierSet(String id, FreeColGameObjectType fcgot,
Turn turn) {
return getType().getModifierSet(id);
}
/**
* {@inheritDoc}
*/
public String getNameKey() {
return getType().getNameKey();
}
/**
* Returns the level of this building.
*
* @return an <code>int</code> value
*/
public int getLevel() {
return getType().getLevel();
}
/**
* Gets the name of the improved building of the same type. An improved
* building is a building of a higher level.
*
* @return The name of the improved building or <code>null</code> if the
* improvement does not exist.
*/
public String getNextNameKey() {
final BuildingType next = getType().getUpgradesTo();
return next == null ? null : next.getNameKey();
}
/**
* Checks if this building can have a higher level.
*
* @return If this <code>Building</code> can have a higher level, that
* {@link FoundingFather Adam Smith} is present for manufactoring
* factory level buildings and that the <code>Colony</code>
* containing this <code>Building</code> has a sufficiently high
* population.
*/
public boolean canBuildNext() {
return getColony().canBuild(getType().getUpgradesTo());
}
/**
* Returns whether this building can be damaged
*
* @return <code>true</code> if can be damaged
* @see #damage
*/
public boolean canBeDamaged() {
return !getType().isAutomaticBuild()
&& !getColony().isAutomaticBuild(getType());
}
/**
* Reduces this building to previous level (is set to UpgradesFrom
* attribute in BuildingType) or is destroyed if it's the first level
*
* @return True if the building was damaged.
*/
public boolean damage() {
if (canBeDamaged()) {
setType(getType().getUpgradesFrom());
getColony().invalidateCache();
return true;
}
return false;
}
/**
* Upgrades this building to next level (is set to UpgradesTo
* attribute in BuildingType)
*
* @return True if the upgrade succeeds.
*/
public boolean upgrade() {
if (canBuildNext()) {
setType(getType().getUpgradesTo());
getColony().invalidateCache();
return true;
}
return false;
}
/**
* Returns the unit type being an expert in this <code>Building</code>.
*
* @return The UnitType.
*/
public UnitType getExpertUnitType() {
return getSpecification().getExpertForProducing(getGoodsOutputType());
}
/**
* Can a particular type of unit be added to this building?
*
* @param unitType The <code>UnitType</code> to check.
* @return True if unit type can be added to this building.
*/
public boolean canAddType(UnitType unitType) {
return canBeWorked() && getType().canAdd(unitType);
}
/**
* Returns the type of goods this <code>Building</code> produces,
* or <code>null</code> if the Building does not produce any
* goods.
*
* @return The type of goods this <code>Building</code> produces
*/
public GoodsType getGoodsOutputType() {
return getType().getProducedGoodsType();
}
/**
* Returns the type of goods this building needs for input, or
* <code>null</code> if the Building does not consume any goods.
*
* @return The type of goods this <code>Building</code> requires as input
* in order to produce it's {@link #getGoodsOutputType output}.
*/
public GoodsType getGoodsInputType() {
return getType().getConsumedGoodsType();
}
/**
* Gets the maximum productivity of a unit working in this work location,
* considering *only* the contribution of the unit, exclusive of
* that of the work location.
*
* Used below, only public for the test suite.
*
* @param unit The <code>Unit</code> to check.
* @return The maximum return from this unit.
*/
public int getUnitConsumption(Unit unit) {
if (getGoodsOutputType() == null || unit == null) return 0;
int productivity = getType().getBasicProduction();
if (productivity > 0) {
productivity += getColony().getProductionBonus();
productivity = (int)unit.getType()
.applyModifier(Math.max(1, productivity),
getGoodsOutputType().getId());
}
return Math.max(0, productivity);
}
/**
* Gets the production information for this building taking account
* of the available input and output goods.
*
* @param output The output goods already available in the colony,
* necessary in order to avoid excess production.
* @param input The input goods available.
* @return The production information.
* @see ProductionCache#update
*/
public ProductionInfo getAdjustedProductionInfo(AbstractGoods output,
List<AbstractGoods> input) {
ProductionInfo result = new ProductionInfo();
GoodsType outputType = getGoodsOutputType();
GoodsType inputType = getGoodsInputType();
if (outputType != null && outputType != output.getType()) {
throw new IllegalArgumentException("Wrong output type: " + output.getType()
+ " should have been: " + outputType);
}
int capacity = getColony().getWarehouseCapacity();
if (getType().hasAbility(Ability.AVOID_EXCESS_PRODUCTION)
&& output.getAmount() >= capacity) {
// warehouse is already full: produce nothing
return result;
}
int availableInput = 0;
if (inputType != null) {
boolean found = false;
for (AbstractGoods goods : input) {
if (goods.getType() == inputType) {
availableInput = goods.getAmount();
found = true;
break;
}
}
if (!found) {
throw new IllegalArgumentException("No input goods of type "
+ inputType + " available.");
}
}
if (outputType != null) {
int maximumInput = 0;
if (inputType != null && canAutoProduce()) {
int available = getColony().getGoodsCount(outputType);
if (available >= outputType.getBreedingNumber()) {
// we need at least these many horses/animals to breed
int divisor = (int)getType().applyModifier(0f,
"model.modifier.breedingDivisor");
int factor = (int)getType().applyModifier(0f,
"model.modifier.breedingFactor");
maximumInput = ((available - 1) / divisor + 1) * factor;
}
} else {
for (Unit u : getUnitList()) {
maximumInput += getUnitConsumption(u);
}
maximumInput = Math.max(0, maximumInput);
}
Turn turn = getGame().getTurn();
List<Modifier> productionModifiers = getProductionModifiers(getGoodsOutputType(), null);
int maxProd = (int)FeatureContainer.applyModifiers(maximumInput,
turn, productionModifiers);
int actualInput = (inputType == null)
? maximumInput
: Math.min(maximumInput, availableInput);
// experts in factory level buildings may produce a
// certain amount of goods even when no input is available
if (availableInput < maximumInput
&& getType().hasAbility(Ability.EXPERTS_USE_CONNECTIONS)
&& getSpecification().getBoolean(GameOptions.EXPERTS_HAVE_CONNECTIONS)) {
int minimumGoodsInput = 0;
for (Unit unit: getUnitList()) {
if (unit.getType() == getExpertUnitType()) {
// TODO: put magic number in specification
minimumGoodsInput += 4;
}
}
if (minimumGoodsInput > availableInput) {
actualInput = minimumGoodsInput;
}
}
// output is the same as input, plus production bonuses
int prod = (int)FeatureContainer.applyModifiers(actualInput, turn,
productionModifiers);
if (prod > 0) {
if (getType().hasAbility(Ability.AVOID_EXCESS_PRODUCTION)) {
int total = output.getAmount() + prod;
while (total > capacity) {
if (actualInput <= 0) {
// produce nothing
return result;
} else {
actualInput--;
}
prod = (int)FeatureContainer.applyModifiers(actualInput,
turn, productionModifiers);
total = output.getAmount() + prod;
// in this case, maximum production does not
// exceed actual production
maximumInput = actualInput;
maxProd = prod;
}
}
prod = Math.max(0, prod);
maxProd = Math.max(0, maxProd);
result.addProduction(new AbstractGoods(outputType, prod));
if (maxProd > prod) {
result.addMaximumProduction(new AbstractGoods(outputType, maxProd));
}
if (inputType != null) {
result.addConsumption(new AbstractGoods(inputType, actualInput));
if (maximumInput > actualInput) {
result.addMaximumConsumption(new AbstractGoods(inputType, maximumInput));
}
}
}
}
return result;
}
// Interface Comparable
/**
* {@inheritDoc}
*/
public int compareTo(Building other) {
return getType().compareTo(other.getType());
}
// Interface Location
/**
* {@inheritDoc}
*/
@Override
public StringTemplate getLocationName() {
return StringTemplate.template("inLocation")
.add("%location%", getNameKey());
}
/**
* {@inheritDoc}
*/
@Override
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);
unit.setWorkType(getGoodsOutputType());
getColony().invalidateCache();
return true;
}
return false;
}
/**
* {@inheritDoc}
*/
@Override
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) {
if (!(locatable instanceof Unit)) return NoAddReason.WRONG_TYPE;
NoAddReason reason = getNoWorkReason();
Unit unit = (Unit) locatable;
BuildingType type = getType();
return (reason != NoAddReason.NONE) ? reason
: !type.canAdd(unit.getType()) ? NoAddReason.MISSING_SKILL
: super.getNoAddReason(locatable);
}
/**
* {@inheritDoc}
*/
@Override
public int getUnitCapacity() {
return getType().getWorkPlaces();
}
// Interface WorkLocation
/**
* {@inheritDoc}
*/
public NoAddReason getNoWorkReason() {
return NoAddReason.NONE;
}
/**
* {@inheritDoc}
*/
public boolean canAutoProduce() {
return getType().hasAbility(Ability.AUTO_PRODUCTION);
}
/**
* {@inheritDoc}
*/
public int getProductionOf(Unit unit, GoodsType goodsType) {
if (unit == null) {
throw new IllegalArgumentException("Null unit.");
}
int result = (getGoodsOutputType() == null
|| getGoodsOutputType() != goodsType) ? 0
: getPotentialProduction(goodsType, unit.getType());
return Math.max(0, result);
}
/**
* {@inheritDoc}
*/
public int getPotentialProduction(GoodsType goodsType, UnitType unitType) {
int production = 0;
if (getGoodsOutputType() == goodsType
&& getType().getBasicProduction() > 0) {
production = (int)FeatureContainer.applyModifiers(0f,
getGame().getTurn(),
getProductionModifiers(goodsType, unitType));
}
return Math.max(0, production);
}
/**
* {@inheritDoc}
*/
public List<Modifier> getProductionModifiers(GoodsType goodsType,
UnitType unitType) {
List<Modifier> mods = new ArrayList<Modifier>();
if (goodsType != null && goodsType == getGoodsOutputType()) {
final BuildingType type = getType();
final String id = goodsType.getId();
final Turn turn = getGame().getTurn();
final Player owner = getOwner();
// This is fragile. The colony contains all the buildings,
// including this one, so as long as buildings produce
// distinct goods, this works.
mods.addAll(getColony().getModifierSet(id, type, turn));
if (unitType != null) {
mods.add(getColony().getProductionModifier(goodsType));
mods.add(type.getProductionModifier());
mods.addAll(unitType.getModifierSet(id, type, turn));
// If a unit is present add unspecific owner bonuses
// (which includes things like the Building national
// advantage).
if (owner != null) {
mods.addAll(owner.getModifierSet(id, null, turn));
}
} else {
// If a unit is not present add only the owner bonuses
// specific to the building (such as the Paine bells bonus).
if (owner != null) {
mods.addAll(owner.getModifierSet(id, type, turn));
}
}
}
return mods;
}
/**
* {@inheritDoc}
*/
public GoodsType getBestWorkType(Unit unit) {
return getGoodsOutputType();
}
// Omitted getClaimTemplate, buildings do not need to be claimed.
// Interface Consumer
/**
* Returns true if this Consumer consumes the given GoodsType.
*
* @param goodsType a <code>GoodsType</code> value
* @return a <code>boolean</code> value
*/
public boolean consumes(GoodsType goodsType) {
return goodsType == getGoodsInputType();
}
/**
* Returns a list of GoodsTypes this Consumer consumes.
*
* @return a <code>List</code> value
*/
public List<AbstractGoods> getConsumedGoods() {
List<AbstractGoods> result = new ArrayList<AbstractGoods>();
GoodsType inputType = getGoodsInputType();
if (inputType != null) {
result.add(new AbstractGoods(inputType, 0));
}
return result;
}
/**
* The priority of this Consumer. The higher the priority, the
* earlier will the Consumer be allowed to consume the goods it
* requires.
*
* @return an <code>int</code> value
*/
public int getPriority() {
return getType().getPriority();
}
// 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.
*/
@Override
protected void toXMLImpl(XMLStreamWriter out, Player player,
boolean showAll, boolean toSavedGame)
throws XMLStreamException {
// Start
out.writeStartElement(getXMLElementTagName());
// Attributes
super.writeAttributes(out);
out.writeAttribute("buildingType", buildingType.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.
*/
@Override
public void readAttributes(XMLStreamReader in) throws XMLStreamException {
super.readAttributes(in);
buildingType = getSpecification().getBuildingType(in.getAttributeValue(null, "buildingType"));
}
/**
* 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());
}
/**
* String converter for debugging.
*
* @return The name of the building.
*/
@Override
public String toString() {
return getType().getId() + " [" + getColony().getName() + "]";
}
/**
* Gets the tag name of the root element representing this object.
*
* @return "building".
*/
public static String getXMLElementTagName() {
return "building";
}
}