/**
* 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 org.freecolandroid.xml.stream.XMLStreamException;
import org.freecolandroid.xml.stream.XMLStreamReader;
import org.freecolandroid.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);
}
/**
* {@inheritDoc}
*/
public String getNameKey() {
return buildingType.getNameKey();
}
/**
* Returns the level of this building.
*
* @return an <code>int</code> value
*/
public int getLevel() {
return buildingType.getLevel();
}
/**
* Returns the name of this location.
*
* @return The name of this location.
*/
public StringTemplate getLocationName() {
return StringTemplate.template("inLocation")
.add("%location%", getNameKey());
}
/**
* 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 = buildingType.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(buildingType.getUpgradesTo());
}
/**
* Gets the type of this building.
*
* @return The type.
*/
public BuildingType getType() {
return buildingType;
}
/**
* {@inheritDoc}
*/
public boolean hasAbility(String id) {
return getType().hasAbility(id);
}
/**
* Returns whether this building can be damaged
*
* @return <code>true</code> if can be damaged
* @see #damage
*/
public boolean canBeDamaged() {
return !buildingType.isAutomaticBuild()
&& !getColony().isAutomaticBuild(buildingType);
}
/**
* 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(buildingType.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(buildingType.getUpgradesTo());
getColony().invalidateCache();
return true;
}
return false;
}
/**
* 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.getFeatureContainer().remove(buildingType.getFeatureContainer());
if (newBuildingType != null) {
buildingType = newBuildingType;
// add new features and abilities from new type
colony.getFeatureContainer().add(buildingType.getFeatureContainer());
// Colonists which can't work here must be put outside
for (Unit unit : getUnitList()) {
if (!canAdd(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());
}
}
}
/**
* {@inheritDoc}
*/
@Override
public int getUnitCapacity() {
return buildingType.getWorkPlaces();
}
/**
* Returns the unit type being an expert in this <code>Building</code>.
*
* @return The UnitType.
*/
public UnitType getExpertUnitType() {
return getSpecification().getExpertForProducing(getGoodsOutputType());
}
/**
* {@inheritDoc}
*/
public NoAddReason getNoWorkReason() {
return NoAddReason.NONE;
}
/**
* {@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);
}
/**
* Checks if the specified <code>UnitType</code> may be added to this
* <code>WorkLocation</code>.
*
* @param unitType the <code>UnitType</code>.
* @return <i>true</i> if the <i>UnitType</i> may be added and <i>false</i>
* otherwise.
*/
public boolean canAdd(final UnitType unitType) {
return canBeWorked() && buildingType.canAdd(unitType);
}
/**
* Adds the specified locatable to this building.
*
* @param locatable The <code>Locatable</code> to add.
*/
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;
}
/**
* Removes the specified locatable from this building.
*
* @param locatable The <code>Locatable</code> to remove.
*/
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;
}
/**
* 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();
}
/**
* Returns the ProductionInfo for this Building from the Colony's
* cache.
*
* @return a <code>ProductionInfo</code> object
*/
public ProductionInfo getProductionInfo() {
return getColony().getProductionInfo(this);
}
/**
* Returns the ProductionInfo for this Building.
*
* @param output the output goods already available in the colony,
* necessary in order to avoid excess production
* @param input the input goods available
* @return a <code>ProductionInfo</code> object
*/
public ProductionInfo getProductionInfo(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 (buildingType.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().getFeatureContainer()
.applyModifier(0, "model.modifier.breedingDivisor");
int factor = (int) getType().getFeatureContainer()
.applyModifier(0, "model.modifier.breedingFactor");
maximumInput = ((available - 1) / divisor + 1) * factor;
}
} else {
maximumInput = getProductivity();
}
List<Modifier> productionModifiers = getProductionModifiers();
int maximumProduction = (int) FeatureContainer
.applyModifiers(maximumInput, getGame().getTurn(),
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
&& buildingType.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 production = (int) FeatureContainer
.applyModifiers(actualInput, getGame().getTurn(),
productionModifiers);
if (production > 0) {
if (buildingType.hasAbility(Ability.AVOID_EXCESS_PRODUCTION)) {
int total = output.getAmount() + production;
while (total > capacity) {
if (actualInput <= 0) {
// produce nothing
return result;
} else {
actualInput--;
}
production = (int) FeatureContainer
.applyModifiers(actualInput, getGame().getTurn(),
productionModifiers);
total = output.getAmount() + production;
// in this case, maximum production does not
// exceed actual production
maximumInput = actualInput;
maximumProduction = production;
}
}
result.addProduction(new AbstractGoods(outputType, production));
if (maximumProduction > production) {
result.addMaximumProduction(new AbstractGoods(outputType, maximumProduction));
}
if (inputType != null) {
result.addConsumption(new AbstractGoods(inputType, actualInput));
if (maximumInput > actualInput) {
result.addMaximumConsumption(new AbstractGoods(inputType, maximumInput));
}
}
}
}
return result;
}
/**
* Returns the actual production of this building.
*
* @return The amount of goods being produced by this <code>Building</code>
* the current turn. The type of goods being produced is given by
* {@link #getGoodsOutputType}.
* @see #getMaximumProduction
*/
public int getProduction() {
ProductionInfo info = getProductionInfo();
if (info == null) return 0;
List<AbstractGoods> production = info.getProduction();
if (production == null || production.isEmpty()) {
return 0;
} else {
return production.get(0).getAmount();
}
}
/**
* Returns the maximum production of this building.
*
* @return The production of this building, with the current amount of
* workers, when there is enough "input goods".
*/
public int getMaximumProduction() {
ProductionInfo info = getProductionInfo();
if (info == null) return 0;
List<AbstractGoods> production = info.getMaximumProduction();
if (production == null || production.isEmpty()) {
return getProduction();
} else {
return production.get(0).getAmount();
}
}
/**
* Returns the additional production of new <code>Unit</code> at
* this building for next turn.
*
* TODO: Make this work properly. In the past, the method never
* worked correctly anyway, since it did not take the possible
* decrease in the production of input goods into account that
* might be caused by moving a unit to this Building from another
* WorkLocation. To do this right, it would be necessary to
* re-calculate the production for the whole getColony().
*
* @return The production of this building the next turn.
*/
public int getAdditionalProductionNextTurn(Unit addUnit) {
return getUnitProductivity(addUnit);
}
/**
* Returns true if this building can produce goods without workers.
*
* @return a <code>boolean</code> value
*/
public boolean canAutoProduce() {
return buildingType.hasAbility(Ability.AUTO_PRODUCTION);
}
/**
* Returns the maximum productivity of worker/s currently working
* in this building.
*
* @param additionalUnits units to add before calculating result
* @return The maximum returns from workers in this building,
* assuming enough "input goods".
*/
private int getProductivity(Unit... additionalUnits) {
if (getGoodsOutputType() == null) {
return 0;
}
int productivity = 0;
for (Unit unit : getUnitList()) {
productivity += getUnitProductivity(unit);
}
for (Unit unit : additionalUnits) {
if (canAdd(unit)) {
productivity += getUnitProductivity(unit);
}
}
return productivity;
}
/**
* Returns the maximum productivity of a unit working in this building.
*
* @return The maximum returns from this unit if in this <code>Building</code>,
* assuming enough "input goods".
*/
public int getUnitProductivity(Unit prodUnit) {
if (getGoodsOutputType() == null || prodUnit == null) {
return 0;
}
int productivity = buildingType.getBasicProduction();
if (productivity > 0) {
productivity += getColony().getProductionBonus();
return (int) prodUnit.getType().getFeatureContainer()
.applyModifier(Math.max(1, productivity),
getGoodsOutputType().getId());
} else {
return 0;
}
}
/**
* Returns the production of the given type of goods.
*
* @param goodsType The type of goods to get the production for.
* @return the production og the given goods this turn. This method will
* return the same as {@link #getProduction} if the given type of
* goods is the same as {@link #getGoodsOutputType} and
* <code>0</code> otherwise.
*/
public int getProductionOf(GoodsType goodsType) {
if (goodsType == getGoodsOutputType()) {
return getProduction();
}
return 0;
}
/**
* Gets the production of the given type of goods produced by a unit.
*
* @param unit The unit to do the work.
* @param goodsType The type of goods to get the production of.
* @return The production of the given type of goods.
*/
public int getProductionOf(Unit unit, GoodsType goodsType) {
return (unit == null
|| getGoodsOutputType() == null
|| getGoodsOutputType() != goodsType) ? 0
: getUnitProductivity(unit);
}
/**
* Gets the potential productivity of a given goods type from using
* a unit of a given type in this building.
*
* @param unitType The optional <code>UnitType</code> to check.
* @param goodsType The <code>GoodsType</code> to check.
* @return The amount of goods potentially produced.
*/
public int getPotentialProduction(UnitType unitType, GoodsType goodsType) {
int production = 0;
if (getGoodsOutputType() == goodsType) {
production += buildingType.getBasicProduction();
if (production > 0) {
production += getColony().getProductionBonus();
if (unitType != null) {
unitType.getFeatureContainer()
.applyModifier(Math.max(1, production),
getGoodsOutputType().getId());
}
production = (int) FeatureContainer
.applyModifiers(production, getGame().getTurn(),
getProductionModifiers());
}
}
return production;
}
/**
* Returns a List of all Modifiers that influence the total
* production of the Building. In particular, the method does not
* return any Modifiers that influence of the productivity of
* individual units present in the Building, such as the colony
* production bonus.
*
* @return a List of all Modifiers that influence the total
* production of the Building
*/
public List<Modifier> getProductionModifiers() {
List<Modifier> modifiers = new ArrayList<Modifier>();
GoodsType goodsOutputType = getGoodsOutputType();
if (goodsOutputType != null) {
modifiers.addAll(getColony().getFeatureContainer().
getModifierSet(goodsOutputType.getId(), buildingType, getGame().getTurn()));
if (getOwner() != null) {
modifiers.addAll(getOwner().getFeatureContainer().
getModifierSet(goodsOutputType.getId(), buildingType, getGame().getTurn()));
}
Collections.sort(modifiers);
}
return modifiers;
}
/**
* {@inheritDoc}
*/
public int compareTo(Building other) {
return getType().compareTo(other.getType());
}
// 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 buildingType.getPriority();
}
/**
* {@inheritDoc}
*/
public Set<Modifier> getModifierSet(String id) {
return buildingType.getModifierSet(id);
}
/**
* 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
protected void readFromXMLImpl(XMLStreamReader in) throws XMLStreamException {
super.readAttributes(in);
buildingType = getSpecification().getBuildingType(in.getAttributeValue(null, "buildingType"));
super.readChildren(in);
}
/**
* 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
protected 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";
}
}