/** * 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.HashMap; import java.util.Map.Entry; import java.util.logging.Logger; import org.freecolandroid.xml.stream.XMLStreamConstants; import org.freecolandroid.xml.stream.XMLStreamException; import org.freecolandroid.xml.stream.XMLStreamReader; import org.freecolandroid.xml.stream.XMLStreamWriter; import net.sf.freecol.common.model.Unit.UnitState; /** * Represents Europe in the game. Each <code>Player</code> has it's * own <code>Europe</code>. * * <br><br> * * <p>In Europe, you can recruit, train and purchase new units. You * can also equip units, as well as sell and buy goods. */ public class Europe extends UnitLocation implements Ownable, Named { private static final Logger logger = Logger.getLogger(Europe.class.getName()); private static final int RECRUIT_PRICE_INITIAL = 200; private static final int LOWER_CAP_INITIAL = 80; public static final String UNIT_CHANGE = "unitChange"; /** * This array represents the types of the units that can be recruited in * Europe. They correspond to the slots that can be seen in the gui and that * can be used to communicate with the server/client. The array holds * exactly 3 elements and element 0 corresponds to recruit slot 1. */ private UnitType[] recruitables = { null, null, null }; public static final int RECRUIT_COUNT = 3; public static enum MigrationType { NORMAL, // Unit decided to migrate RECRUIT, // Player is paying FOUNTAIN // As a result of a Fountain of Youth discovery } protected java.util.Map<UnitType, Integer> unitPrices = new HashMap<UnitType, Integer>(); private int recruitPrice; private int recruitLowerCap; private Player owner; private FeatureContainer featureContainer; /** * Constructor for ServerEurope. */ protected Europe() { // empty constructor } /** * Constructor for ServerEurope. * * @param game The <code>Game</code> in which this object belong. * @param owner The <code>Player</code> that will be using this object of * <code>Europe</code>. */ protected Europe(Game game, Player owner) { super(game); this.owner = owner; recruitPrice = RECRUIT_PRICE_INITIAL; recruitLowerCap = LOWER_CAP_INITIAL; } /** * Initializes this object from an XML-representation of this object. * * @param game The <code>Game</code> in which this object belong. * @param in The input stream containing the XML. * @throws XMLStreamException if an error occurred during parsing. */ public Europe(Game game, XMLStreamReader in) throws XMLStreamException { super(game, in); readFromXML(in); } /** * Initiates a new <code>Europe</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 Europe(Game game, String id) { super(game, id); } /** * Describe <code>hasAbility</code> method here. * * @param id a <code>String</code> value * @return a <code>boolean</code> value */ @Override public boolean hasAbility(String id) { return featureContainer.hasAbility(id); } /** * Returns the FeatureContainer. * * @return a <code>FeatureContainer</code> value */ public FeatureContainer getFeatureContainer() { return featureContainer; } /** * Sets the FeatureContainer. * * @param container a <code>FeatureContainer</code> value */ protected void setFeatureContainer(FeatureContainer container) { featureContainer = container; } /** * Checks if there is a useable carrier unit with a specified * minimum amount of space available docked in this European port. * * @param space The amount of space to require. * @return True if there is a suitable unit present. * @see Unit#isCarrier */ public boolean hasCarrierWithSpace(int space) { for (Unit u : getUnitList()) { if (u.isCarrier() && !u.isUnderRepair() && u.getSpaceLeft() >= space) return true; } return false; } /** * Return true if this Europe could build at least one item of the * given EquipmentType. * * @param equipmentType an <code>EquipmentType</code> value * @return a <code>boolean</code> value */ public boolean canBuildEquipment(EquipmentType equipmentType) { for (AbstractGoods requiredGoods : equipmentType.getGoodsRequired()) { GoodsType goodsType = requiredGoods.getType(); if (!(getOwner().canTrade(goodsType) && getOwner().checkGold(getOwner().getMarket().getBidPrice(goodsType, requiredGoods.getAmount())))) { return false; } } return true; } /** * Returns true if not all recruitables are of the same type. * * @return a <code>boolean</code> value */ public boolean recruitablesDiffer() { return !(recruitables[0].equals(recruitables[1]) && recruitables[0].equals(recruitables[2])); } /** * Gets the type of the recruitable in Europe at the given slot. * * @param slot The slot of the recruitable whose type needs to be returned. * Should be 0, 1 or 2. NOTE - used to be 1, 2 or 3 and was * called with 1-3 by some classes and 0-2 by others, the method * itself expected 0-2. * @return The type of the recruitable in Europe at the given slot. * @exception IllegalArgumentException if the given <code>slot</code> does * not exist. */ public UnitType getRecruitable(int slot) { if ((slot >= 0) && (slot < RECRUIT_COUNT)) { return recruitables[slot]; } throw new IllegalArgumentException("Wrong recruitement slot: " + slot); } /** * Sets the type of the recruitable in Europe at the given slot to the given * type. * * @param slot The slot of the recruitable whose type needs to be set. * Should be 0, 1 or 2. NOTE - changed in order to match * getRecruitable above! * @param type The new type for the unit at the given slot in Europe. Should * be a valid unit type. */ public void setRecruitable(int slot, UnitType type) { // Note - changed in order to match getRecruitable if (slot >= 0 && slot < RECRUIT_COUNT) { recruitables[slot] = type; } else { logger.warning("setRecruitable: invalid slot(" + slot + ") given."); } } /** * Adds a <code>Locatable</code> to this Location. * * @param locatable The <code>Locatable</code> to add to this Location. */ public boolean add(Locatable locatable) { boolean result = super.add(locatable); if (result && locatable instanceof Unit) { Unit unit = (Unit) locatable; unit.setState((unit.canCarryUnits()) ? UnitState.ACTIVE : UnitState.SENTRY); } return result; } /** * Checks whether or not the specified locatable may be added to this * <code>Location</code>. * * @param locatable The <code>Locatable</code> to test the addabillity of. * @return <i>true</i>. */ public boolean canAdd(Locatable locatable) { if (locatable instanceof Goods) { return true; } else { return super.canAdd(locatable); } } /** * Returns the price of a unit in Europe. * * @param unitType The type of unit of which you need the price. * @return The price of this unit when trained in Europe. * 'UNDEFINED' is returned in case the unit cannot be * bought. */ public int getUnitPrice(UnitType unitType) { Integer price = unitPrices.get(unitType); if (price != null) { return price.intValue(); } else { return unitType.getPrice(); } } /** * Gets the current price for a recruit. * * @return The current price of the recruit in this <code>Europe</code>. */ public int getRecruitPrice() { int required = owner.getImmigrationRequired(); int immigration = owner.getImmigration(); int difference = Math.max(required - immigration, 0); return Math.max((recruitPrice * difference) / required, recruitLowerCap); } /** * Increases the base price and lower cap for recruits. * Only called from the server side. */ public void increaseRecruitmentDifficulty() { Specification spec = getSpecification(); recruitPrice += spec.getIntegerOption("model.option.recruitPriceIncrease").getValue(); recruitLowerCap += spec.getIntegerOption("model.option.lowerCapIncrease").getValue(); } /** * Gets the <code>Player</code> using this <code>Europe</code>. */ public Player getOwner() { return owner; } /** * Sets the owner of this <code>Ownable</code>. * * @param p The <code>Player</code> that should take ownership of this * {@link Ownable}. * @exception UnsupportedOperationException is always thrown by this method. */ public void setOwner(Player p) { throw new UnsupportedOperationException(); } /** * Returns the name of this location. * * @return The name of this location. */ public StringTemplate getLocationName() { return StringTemplate.key(getNameKey()); } /** * Returns the name of the owner's home port. * * @return The name of this location. */ public String getNameKey() { return getOwner().getEuropeNameKey(); } /** * 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. */ protected void toXMLImpl(XMLStreamWriter out, Player player, boolean showAll, boolean toSavedGame) throws XMLStreamException { // Start element: out.writeStartElement(getXMLElementTagName()); super.writeAttributes(out); for (int index = 0; index < recruitables.length; index++) { if (recruitables[index] != null) { out.writeAttribute("recruit" + index, recruitables[index].getId()); } } out.writeAttribute("recruitPrice", Integer.toString(recruitPrice)); out.writeAttribute("recruitLowerCap", Integer.toString(recruitLowerCap)); out.writeAttribute("owner", owner.getId()); for (Entry<UnitType, Integer> entry : unitPrices.entrySet()) { out.writeStartElement("unitPrice"); out.writeAttribute("unitType", entry.getKey().getId()); out.writeAttribute("price", entry.getValue().toString()); out.writeEndElement(); } super.writeChildren(out, player, showAll, toSavedGame); 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. */ protected void readFromXMLImpl(XMLStreamReader in) throws XMLStreamException { setId(in.getAttributeValue(null, ID_ATTRIBUTE)); Specification spec = getSpecification(); // @compat 0.10.0 if (featureContainer == null) { featureContainer = new FeatureContainer(); featureContainer.addAbility(new Ability("model.ability.dressMissionary")); } // end compatibility code for (int index = 0; index < recruitables.length; index++) { String unitTypeId = in.getAttributeValue(null, "recruit" + index); if (unitTypeId != null) { recruitables[index] = spec.getUnitType(unitTypeId); } } owner = getFreeColGameObject(in, "owner", Player.class); recruitPrice = getAttribute(in, "recruitPrice", RECRUIT_PRICE_INITIAL); recruitLowerCap = getAttribute(in, "recruitLowerCap", LOWER_CAP_INITIAL); unitPrices.clear(); readChildren(in); } protected void readChild(XMLStreamReader in) throws XMLStreamException { if (in.getLocalName().equals(UNITS_TAG_NAME)) { Unit unit; while (in.nextTag() != XMLStreamConstants.END_ELEMENT) { if (in.getLocalName().equals(Unit.getXMLElementTagName())) { unit = updateFreeColGameObject(in, Unit.class); // @compat 0.10.1 if (unit.getLocation() == null) { // sometimes units in a Europe element have a missing // location. it should always be this Europe instance. unit.setLocationNoUpdate(this); } // end compatibility code add(unit); } } } else if (in.getLocalName().equals("unitPrice")) { String unitTypeId = in.getAttributeValue(null, "unitType"); Integer price = new Integer(in.getAttributeValue(null, "price")); unitPrices.put(getSpecification().getUnitType(unitTypeId), price); in.nextTag(); // close "unitPrice" tag } else { super.readChild(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()); } /** * Returns a suitable name. */ public String toString() { return "Europe"; } /** * Gets the tag name of the root element representing this object. * * @return "europe". */ public static String getXMLElementTagName() { return "europe"; } }