/** * 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.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map.Entry; import java.util.Random; import java.util.Set; 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.Map.Direction; import net.sf.freecol.common.model.Map.Position; import net.sf.freecol.common.model.Player.Stance; /** * Represents a single tile on the <code>Map</code>. * * @see Map */ public final class Tile extends UnitLocation implements Named, Ownable { private static final Logger logger = Logger.getLogger(Tile.class.getName()); // This must be distinct from ColonyTile/Building.UNIT_CHANGE or // the colony panel can get confused. public static final String UNIT_CHANGE = "TILE_UNIT_CHANGE"; /** * The maximum distance that will still be considered "near" when * determining the location name. * * @see #getLocationName */ public static final int NEAR_RADIUS = 8; private TileType type; private int x, y; /** The player that consider this tile to be their land. */ private Player owner; /** * A pointer to the settlement located on this tile or 'null' if there is no * settlement on this tile. */ private Settlement settlement; /** * Stores all Improvements and Resources (if any) */ private TileItemContainer tileItemContainer; /** * Indicates which colony or Indian settlement that owns this tile ('null' * indicates no owner). A colony owns the tile it is located on, and every * tile with a worker on it. Note that while units and settlements are owned * by a player, a tile is owned by a settlement. */ private Settlement owningSettlement; /** * Stores each player's image of this tile. Only initialized when needed. */ private java.util.Map<Player, PlayerExploredTile> playerExploredTiles; /** * Describe region here. */ private Region region; /** * Whether this tile is connected to Europe. */ private boolean connected = false; /** * Does this tile have an explicit moveToEurope state. If null, * just use the defaults (usually not, unless water and on map edge), * otherwise use the explicit value provided here. */ private Boolean moveToEurope; /** * The style of this Tile, as determined by adjacent tiles. */ private int style; /** * A constructor to use. * * @param game The <code>Game</code> this <code>Tile</code> belongs to. * @param type The type. * @param locX The x-position of this tile on the map. * @param locY The y-position of this tile on the map. */ public Tile(Game game, TileType type, int locX, int locY) { super(game); this.type = type; x = locX; y = locY; owningSettlement = null; settlement = null; if (!isViewShared()) { playerExploredTiles = new HashMap<Player, PlayerExploredTile>(); } } /** * Initialize this object from an XML-representation of this object. * * @param game The <code>Game</code> this <code>Tile</code> should be * created in. * @param in The input stream containing the XML. * @throws XMLStreamException if a problem was encountered during parsing. */ public Tile(Game game, XMLStreamReader in) throws XMLStreamException { super(game, in); if (!isViewShared()) { playerExploredTiles = new HashMap<Player, PlayerExploredTile>(); } readFromXML(in); } /** * Initiates a new <code>Tile</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 Tile(Game game, String id) { super(game, id); if (!isViewShared()) { playerExploredTiles = new HashMap<Player, PlayerExploredTile>(); } } // ------------------------------------------------------------ static methods /** * Creates a temporary copy of this tile for planning purposes. * The copy is identical except: * - it is not present on the map * - it has no owner, owning settlement or settlement * The latter is not a problem as the main use of this routine * is by Colony.getScratchColony() which needs to change these fields * anyway. * Note that the following fields are shared--- do not mutate them! * + The tile item container. * + The player explored tiles. * Colony.getCorrespondingWorkLocation() depends on the tics being shared. * * @return A scratch version of this tile. */ public Tile getScratchTile() { Game game = getGame(); Tile scratch = new Tile(game, type, x, y); scratch.owner = null; scratch.settlement = null; scratch.owningSettlement = null; if (tileItemContainer == null) { tileItemContainer = new TileItemContainer(getGame(), this); } scratch.tileItemContainer = tileItemContainer; scratch.playerExploredTiles = playerExploredTiles; scratch.region = region; scratch.connected = connected; scratch.moveToEurope = moveToEurope; scratch.style = style; return scratch; } /** * Special handling on dispose to avoid mutating the shared fields. */ public void disposeScratchTile() { tileItemContainer = null; playerExploredTiles = null; dispose(); } public boolean isViewShared() { return (getGame().getViewOwner() != null); } /** * Get the <code>Region</code> value. * * @return a <code>Region</code> value */ public Region getRegion() { return region; } /** * Set the <code>Region</code> value. * * @param newRegion The new Region value. */ public void setRegion(final Region newRegion) { this.region = newRegion; } /** * Return the discoverable Region of this Tile, or * <code>null</code> if there is none. * * @return a <code>Region</code> value */ public Region getDiscoverableRegion() { if (region == null) { return null; } else { return region.getDiscoverableRegion(); } } /** * Gets the name of this tile, or shows "unexplored" if not explored by player. * * @return The name as a <code>String</code>. */ public String getNameKey() { if (isViewShared()) { if (isExplored()) { return getType().getNameKey(); } else { return "unexplored"; } } else { Player player = getGame().getCurrentPlayer(); if (player != null) { PlayerExploredTile pet = getPlayerExploredTile(player); return (pet != null) ? getType().getNameKey() : "unexplored"; } else { logger.warning("player == null"); return ""; } } } /** * Returns a description of the <code>Tile</code>, with the name of the tile * and any improvements on it (road/plow/etc) from <code>TileItemContainer</code>. * @return The description label for this tile */ public StringTemplate getLabel() { StringTemplate label = StringTemplate.key(type.getNameKey()); if (tileItemContainer != null) { List<String> keys = new ArrayList<String>(); for (TileItem item : tileItemContainer.getTileItems()) { if (item instanceof Resource) { keys.add(((Resource) item).getType().getNameKey()); } else if (item instanceof TileImprovement && ((TileImprovement) item).isComplete()) { keys.add(((TileImprovement) item).getType().getNameKey()); } } if (!keys.isEmpty()) { label = StringTemplate.label("/") .add(type.getNameKey()); for (String key : keys) { label.add(key); } } } return label; } /** * Returns the name of this location. * * @return The name of this location. */ public StringTemplate getLocationName() { if (settlement == null) { Settlement nearSettlement = null; for (Tile tile: getSurroundingTiles(NEAR_RADIUS)) { nearSettlement = tile.getSettlement(); if (nearSettlement != null) { int x = getX() - tile.getX(); int y = getY() - tile.getY(); double theta = Math.atan2(y, x) + Math.PI/2 + Math.PI/8; if (theta < 0) { theta += 2*Math.PI; } Direction direction = Direction.values()[(int) Math.floor(theta / (Math.PI/4))]; return StringTemplate.template("nameLocation") .add("%name%", type.getNameKey()) .addStringTemplate("%location%", StringTemplate.template("nearLocation") .add("%direction%", "direction." + direction.toString()) .addName("%location%", nearSettlement.getName())); } } if (region != null && region.getName() != null) { return StringTemplate.template("nameLocation") .add("%name%", type.getNameKey()) .add("%location%", region.getNameKey()); } else { return StringTemplate.key(type.getNameKey()); } } else { return settlement.getLocationName(); } } /** * Returns the name of this location for a particular player. * * @param player The <code>Player</code> to prepare the location name for. * @return The name of this location. */ public StringTemplate getLocationNameFor(Player player) { return (settlement == null) ? getLocationName() : settlement.getLocationNameFor(player); } /** * Get the <code>Style</code> value. * * @return an <code>int</code> value */ public int getStyle() { return style; } /** * Set the <code>Style</code> value. * * @param newStyle The new Style value. */ public void setStyle(final int newStyle) { this.style = newStyle; } /** * Are two locations non-null and either the same or at the same tile. * * @param l1 The first <code>Location</code>. * @param l2 The second <code>Location</code>. * @return True if this location is the same or at the same tile. */ public static boolean isSameTile(Location l1, Location l2) { return (l1 == null || l2 == null) ? false : (l1 == l2) ? true : l1.getTile() == l2.getTile(); } /** * Gets the distance in tiles between this <code>Tile</code> and the * specified one. * * @param tile The <code>Tile</code> to check the distance to. * @return Distance */ public int getDistanceTo(Tile tile) { return getPosition().getDistance(tile.getPosition()); } /** * Gets the direction to a neighbouring tile from this one. * * @param tile The other <code>Tile</code>. * @return The direction to the other tile, or null if not a neighbour. */ public Direction getDirection(Tile tile) { return getMap().getDirection(this, tile); } /** * Returns the <code>TileItemContainer</code>. * * @return a <code>TileItemContainer</code> value */ public TileItemContainer getTileItemContainer() { return tileItemContainer; } /** * Sets the <code>TileItemContainer</code>. * * @param newTileItemContainer a <code>TileItemContainer</code> value */ public void setTileItemContainer(TileItemContainer newTileItemContainer) { tileItemContainer = newTileItemContainer; } /** * Returns a List of <code>TileImprovements</code>. * * @return a List of <code>TileImprovements</code> */ public List<TileImprovement> getTileImprovements() { if (tileItemContainer == null) { return Collections.emptyList(); } else { return tileItemContainer.getImprovements(); } } /** * Returns a List of completed <code>TileImprovements</code>. * * @return a List of <code>TileImprovements</code> */ public List<TileImprovement> getCompletedTileImprovements() { if (tileItemContainer == null) { return Collections.emptyList(); } else { List<TileImprovement> result = new ArrayList<TileImprovement>(); for (TileImprovement improvement : tileItemContainer.getImprovements()) { if (improvement.getTurnsToComplete() == 0) { result.add(improvement); } } return result; } } /** * Gets the <code>Unit</code> that is currently defending this * <code>Tile</code>. * <p>If this tile has a settlement, the units inside the settlement * are also considered as potential defenders. * <p>As this method is quite expensive, it should not be used to test * for the presence of enemy units. * * @param attacker The target that would be attacking this tile. * @return The <code>Unit</code> that has been chosen to defend this * tile. */ public Unit getDefendingUnit(Unit attacker) { CombatModel cm = getGame().getCombatModel(); Unit defender = null; float defenderPower = -1.0f; float power; // Check the units on the tile... for (Unit u : getUnitList()) { if (isLand() != u.isNaval()) { // On land, ships are normally docked in port and // cannot defend. Except if beached (see below). // On ocean tiles, land units behave as ship cargo and // cannot defend power = cm.getDefencePower(attacker, u); if (Unit.betterDefender(defender, defenderPower, u, power)) { defender = u; defenderPower = power; } } } // ...then a settlement defender if any... if ((defender == null || !defender.isDefensiveUnit()) && getSettlement() != null) { Unit u = null; try { // HACK: The AI is prone to removing all units in a // settlement which causes Colony.getDefendingUnit() // to throw. u = settlement.getDefendingUnit(attacker); } catch (IllegalStateException e) { logger.warning("Empty settlement: " + settlement.getName()); } // This routine can be called on the client for the pre-combat // popup where enemy settlement defenders are not visible, // thus u == null is valid. if (u != null) { power = cm.getDefencePower(attacker, u); if (Unit.betterDefender(defender, defenderPower, u, power)) { defender = u; defenderPower = power; } } } // ...finally, if we have failed to find a valid defender // for a land tile, allow a beached naval unit to defend (and // lose) as a last resort. if (defender == null && isLand()) defender = getFirstUnit(); return defender; } public void dispose() { if (settlement != null) { settlement.dispose(); } if (tileItemContainer != null) { tileItemContainer.dispose(); } super.dispose(); } /** * Gets the first <code>Unit</code> on this tile. * * @return The first <code>Unit</code> on this tile. */ public Unit getFirstUnit() { return isEmpty() ? null : getUnitList().get(0); } /** * Gets the last <code>Unit</code> on this tile. * * @return The last <code>Unit</code> on this tile. */ public Unit getLastUnit() { return isEmpty() ? null : getUnitList().get(getUnitCount() - 1); } /** * Returns the total amount of Units at this Location. This also includes * units in a carrier * * @return The total amount of Units at this Location. */ public int getTotalUnitCount() { int result = 0; for (Unit unit : getUnitList()) { result++; result += unit.getUnitCount(); } return result; } /** * Checks if this <code>Tile</code> contains the specified * <code>Locatable</code>. * * @param locatable The <code>Locatable</code> to test the presence of. * @return * <ul> * <li><i>true</i> if the specified <code>Locatable</code> is * on this <code>Tile</code> and * <li><i>false</i> otherwise. * </ul> */ public boolean contains(Locatable locatable) { if (locatable instanceof TileItem) { return tileItemContainer != null && tileItemContainer.contains((TileItem) locatable); } else { return super.contains(locatable); } } /** * Gets the <code>Map</code> in which this <code>Tile</code> belongs. * * @return The <code>Map</code>. */ public Map getMap() { return getGame().getMap(); } /** * Whether this tile is connected to Europe. * * @return a <code>boolean</code> value */ public boolean isConnected() { return (connected || (type != null && type.isConnected())); } /** * Set the <code>Connected</code> value. * * @param newConnected The new Connected value. */ public void setConnected(final boolean newConnected) { this.connected = newConnected; } /** * Get the move-to-Europe state of the tile. * * @return The move-to-Europe state of the tile. */ public Boolean getMoveToEurope() { return moveToEurope; } /** * Set the move-to-Europe state of the tile. * * @param moveToEurope The new move-to-Europe state for the tile. */ public void setMoveToEurope(Boolean moveToEurope) { this.moveToEurope = moveToEurope; } /** * Can a unit move to Europe from this tile? * * @return True if a unit can move to Europe from this tile. */ public boolean canMoveToEurope() { return (getMoveToEurope() != null) ? getMoveToEurope() : (type == null) ? false : (type.hasAbility("model.ability.moveToEurope")) ? true // TODO: remove this when we are confident all the maps have // appropriate moveToEurope overrides. : isAdjacentToMapEdge() && type.isWater(); } /** * Check if the tile has been explored. * * @return true if tile is known. */ public boolean isExplored() { return type != null; } /** * Is this tile in the polar regions? * * @return True if the tile is polar. */ public boolean isPolar() { return getMap().isPolar(this); } /** * Returns <code>true</code> if this Tile is a land Tile, 'false' otherwise. * * @return <code>true</code> if this Tile is a land Tile, 'false' otherwise. */ public boolean isLand() { return type != null && !type.isWater(); } /** * Returns <code>true</code> if this Tile is forested. * * @return <code>true</code> if this Tile is forested. */ public boolean isForested() { return type != null && type.isForested(); } /** * Returns <code>true</code> if this Tile has a River. * * @return <code>true</code> if this Tile has a River. */ public boolean hasRiver() { return tileItemContainer != null && getTileItemContainer().getRiver() != null; } /** * Returns <code>true</code> if this Tile has a resource on it. * * @return <code>true</code> if this Tile has a resource on it. */ public boolean hasResource() { return tileItemContainer != null && getTileItemContainer().getResource() != null; } /** * Returns <code>true</code> if this Tile has a lostCityRumour on it. * * @return <code>true</code> if this Tile has a lostCityRumour on it. */ public boolean hasLostCityRumour() { return tileItemContainer != null && getTileItemContainer().getLostCityRumour() != null; } /** * Returns <code>true</code> if this Tile has a road. * * @return <code>true</code> if this Tile has a road. */ public boolean hasRoad() { return tileItemContainer != null && getTileItemContainer().getRoad() != null; } /** * Returns the road on this tile, if there is one, and * <code>null</code> otherwise. * * @return a <code>TileImprovement</code> value */ public TileImprovement getRoad() { if (tileItemContainer == null) { return null; } else { return getTileItemContainer().getRoad(); } } /** * Returns the type of this Tile. Returns UNKNOWN if the type of this Tile * is unknown. * * @return The type of this Tile. */ public TileType getType() { return type; } /** * The nation that consider this tile to be their property. * * @return The player owning this tile. */ public Player getOwner() { return owner; } /** * Sets the nation that should consider this tile to be their property. * * @param owner The player, new owner of this tile. * @see #getOwner */ public void setOwner(Player owner) { this.owner = owner; } /** * Puts a <code>Settlement</code> on this <code>Tile</code>. A * <code>Tile</code> can only have one <code>Settlement</code> located * on it. The <code>Settlement</code> will also become the owner of this * <code>Tile</code>. * * @param s The <code>Settlement</code> that shall be located on this * <code>Tile</code>. * @see #getSettlement */ public void setSettlement(Settlement s) { settlement = s; changeOwningSettlement(s); } /** * Gets the <code>Settlement</code> located on this <code>Tile</code>. * * @return The <code>Settlement</code> that is located on this * <code>Tile</code> or <i>null</i> if no <code>Settlement</code> * apply. * @see #setSettlement */ public Settlement getSettlement() { return settlement; } /** * Gets the <code>Colony</code> located on this <code>Tile</code>. Only * a convenience method for {@link #getSettlement} that makes sure that * the settlement is a colony. * * @return The <code>Colony</code> that is located on this * <code>Tile</code> or <i>null</i> if none found. * @see #getSettlement */ public Colony getColony() { return (settlement != null && settlement instanceof Colony) ? (Colony) settlement : null; } /** * Gets the <code>IndianSettlement</code> located on this * <code>Tile</code>. Only a convenience method for {@link * #getSettlement} that makes sure that the settlement is a native * settlement. * * @return The <code>IndianSettlement</code> that is located on this * <code>Tile</code> or <i>null</i> if none found. * @see #getSettlement */ public IndianSettlement getIndianSettlement() { return (settlement != null && settlement instanceof IndianSettlement) ? (IndianSettlement) settlement : null; } /** * Gets the owning settlement for this tile. * * @return The <code>Settlement</code> that owns this tile. * @see #setOwner */ public Settlement getOwningSettlement() { return owningSettlement; } /** * Sets the settlement that owns this tile. * * @param owner The <code>Settlement</code> to own this tile. * @see #getOwner */ public void setOwningSettlement(Settlement owner) { this.owningSettlement = owner; } /** * Changes the owning settlement for this tile. * * @param settlement The new owning <code>Settlement</code> for this tile. */ public void changeOwningSettlement(Settlement settlement) { if (owningSettlement != null) { owningSettlement.removeTile(this); } setOwningSettlement(settlement); if (settlement != null) { settlement.addTile(this); } } /** * Change the tile ownership. Also change the owning settlement * as the two are commonly related. * * @param player The <code>Player</code> to own the tile. * @param settlement The <code>Settlement</code> to own the tile. */ public void changeOwnership(Player player, Settlement settlement) { Player old = getOwner(); setOwner(player); changeOwningSettlement(settlement); updatePlayerExploredTiles(old); } /** * Is this tile under active use? * * @return True if a colony is using this tile. */ public boolean isInUse() { return getOwningSettlement() instanceof Colony && ((Colony) getOwningSettlement()).isTileInUse(this); } /** * Adds a tile item to this tile. * * @param item The <code>TileItem</code> to add. */ private void addTileItem(TileItem item) { if (tileItemContainer == null) { tileItemContainer = new TileItemContainer(getGame(), this); } tileItemContainer.addTileItem(item); updatePlayerExploredTiles(); } /** * Gets the lost city rumour on this <code>Tile</code> if any. * * @return The <code>LostCityRumour</code> on this tile, or null if none. */ public LostCityRumour getLostCityRumour() { return (tileItemContainer == null) ? null : tileItemContainer.getLostCityRumour(); } /** * Adds a lost city rumour to this tile. * * @param rumour The <code>LostCityRumour</code> to add. */ public void addLostCityRumour(LostCityRumour rumour) { addTileItem(rumour); } /** * Removes the lost city rumour from this <code>Tile</code> if there * is one. */ public void removeLostCityRumour() { if (tileItemContainer != null) { tileItemContainer.removeAll(LostCityRumour.class); updatePlayerExploredTiles(); } } /** * Returns the river on this <code>Tile</code> if any * @return River <code>TileImprovement</code> */ public TileImprovement getRiver() { if (tileItemContainer == null) { return null; } else { return tileItemContainer.getRiver(); } } /** * Returns the style of a river <code>TileImprovement</code> on this <code>Tile</code>. * * @return an <code>int</code> value */ public int getRiverStyle() { if (tileItemContainer == null) { return 0; } else { TileImprovement river = tileItemContainer.getRiver(); if (river == null) { return 0; } else { return river.getStyle(); } } } /** * Returns the neighbouring Tile of the given Tile in the given direction. * * @param direction * The direction in which the neighbour tile is located. * @return The neighbouring Tile of the given Tile in the given direction. */ public Tile getNeighbourOrNull(Direction direction) { Position position = getPosition(); if (getMap().isValid(position)) { Position neighbourPosition = position.getAdjacent(direction); return getMap().getTile(neighbourPosition); } else { return null; } } /** * Determine whether this tile has adjacent tiles that are unexplored. * * @return true if at least one neighbouring tiles is unexplored, otherwise false */ public boolean hasUnexploredAdjacent() { for (Tile t: getSurroundingTiles(1)) { if (!t.isExplored()) { return true; } } return false; } /** * Returns true if this tile has at least one adjacent land tile (if water), * or at least one adjacent water tile (if land). * * @return a <code>boolean</code> value */ public boolean isCoast() { for (Direction direction : Direction.values()) { Tile otherTile = getNeighbourOrNull(direction); if (otherTile != null && otherTile.isLand()!=this.isLand()) { return true; } } return false; } /** * Adds a <code>Resource</code> to this <code>Tile</code>. * * @param resource The <code>Resource</code> to add. */ public void addResource(Resource resource) { if (resource == null) return; addTileItem(resource); } /** * Sets the type for this Tile. * * @param t The new TileType for this Tile. */ public void setType(TileType t) { if (t == null) { throw new IllegalArgumentException("Tile type must not be null"); } type = t; if (tileItemContainer != null) { tileItemContainer.removeIncompatibleImprovements(); } if (!isLand()) { settlement = null; } updatePlayerExploredTiles(); } /** * Returns the x-coordinate of this Tile. * * @return The x-coordinate of this Tile. */ public int getX() { return x; } /** * Returns the y-coordinate of this Tile. * * @return The y-coordinate of this Tile. */ public int getY() { return y; } /** * Gets the <code>Position</code> of this <code>Tile</code>. * * @return The <code>Position</code> of this <code>Tile</code>. */ public Position getPosition() { return new Position(x, y); } // TODO: this is used only to update false Tiles, a practice that // should be killed with fire. public void setPosition(int x, int y) { this.x = x; this.y = y; } /** * Gets a <code>Unit</code> that can become active. This is preferably a * <code>Unit</code> not currently performing any work. * * @return A <code>Unit</code> with <code>movesLeft > 0</code> or * <i>null</i> if no such <code>Unit</code> is located on this * <code>Tile</code>. */ public Unit getMovableUnit() { if (getFirstUnit() != null) { Iterator<Unit> unitIterator = getUnitIterator(); while (unitIterator.hasNext()) { Unit u = unitIterator.next(); Iterator<Unit> childUnitIterator = u.getUnitIterator(); while (childUnitIterator.hasNext()) { Unit childUnit = childUnitIterator.next(); if (childUnit.getMovesLeft() > 0 && childUnit.getState() == Unit.UnitState.ACTIVE) { return childUnit; } } if (u.getMovesLeft() > 0 && u.getState() == Unit.UnitState.ACTIVE) { return u; } } } else { return null; } Iterator<Unit> unitIterator = getUnitIterator(); while (unitIterator.hasNext()) { Unit u = unitIterator.next(); Iterator<Unit> childUnitIterator = u.getUnitIterator(); while (childUnitIterator.hasNext()) { Unit childUnit = childUnitIterator.next(); if ((childUnit.getMovesLeft() > 0)) { return childUnit; } } if (u.getMovesLeft() > 0) { return u; } } return null; } /** * Gets the <code>Tile</code> where this <code>Location</code> is * located or null if no <code>Tile</code> applies. * * @return This <code>Tile</code>. */ public Tile getTile() { return this; } /** * Adds a <code>Locatable</code> to this Location. * * @param locatable The <code>Locatable</code> to add to this Location. */ public boolean add(Locatable locatable) { if (locatable instanceof TileItem) { addTileItem((TileItem) locatable); return true; } else if (locatable instanceof Unit) { if (super.add(locatable)) { ((Unit)locatable).setState(Unit.UnitState.ACTIVE); return true; } return false; } else { return super.add(locatable); } } /** * Removes a <code>Locatable</code> from this Location. * * @param locatable The <code>Locatable</code> to remove from this * Location. */ public boolean remove(Locatable locatable) { if (locatable instanceof TileItem) { Player old = getOwner(); tileItemContainer.addTileItem((TileItem) locatable); updatePlayerExploredTiles(old); return true; } else { return super.remove(locatable); } } /** * Checks whether or not the specified locatable may be added to this * <code>Location</code>. * * @param locatable a <code>Locatable</code> value * @return a <code>boolean</code> value */ public boolean canAdd(Locatable locatable) { if (locatable instanceof Unit) { Unit unit = (Unit) locatable; if (unit.isNaval()) { return isLand() ? getSettlement() != null : true; } else { return isLand(); } } else if (locatable instanceof TileImprovement) { return ((TileImprovement) locatable).getType().isTileTypeAllowed(getType()); } else { return false; } } /** * The potential of this tile to produce a certain type of goods. * * @param goodsType The type of goods to check the potential for. * @param unitType an <code>UnitType</code> value * @return The normal potential of this tile to produce that amount of * goods. */ public int potential(GoodsType goodsType, UnitType unitType) { return getTileTypePotential(getType(), goodsType, getTileItemContainer(), unitType); } /** * Gets the maximum potential for producing the given type of goods. The * maximum potential is the potential of a tile after the tile has been * plowed/built road on. * * @param goodsType The type of goods. * @param unitType an <code>UnitType</code> value * @return The maximum potential. */ public int getMaximumPotential(GoodsType goodsType, UnitType unitType) { // If we consider maximum potential to the effect of having // all possible improvements done, iterate through the // improvements and get the bonuses of all related ones. If // there are options to change tiletype using an improvement, // consider that too. List<TileType> tileTypes = new ArrayList<TileType>(); tileTypes.add(getType()); // Add to the list the various possible tile type changes for (TileImprovementType impType : getSpecification().getTileImprovementTypeList()) { if (impType.getChange(getType()) != null) { // There is an option to change TileType tileTypes.add(impType.getChange(getType())); } } int maxProduction = 0; for (TileType tileType : tileTypes) { float potential = tileType.getProductionOf(goodsType, unitType); if (tileType == getType() && hasResource()) { for (TileItem item : tileItemContainer.getTileItems()) { if (item instanceof Resource) { potential = ((Resource) item).getBonus(goodsType, unitType, (int) potential); } } } for (TileImprovementType impType : getSpecification().getTileImprovementTypeList()) { if (impType.isNatural() || !impType.isTileTypeAllowed(tileType)) { continue; } else if (impType.getBonus(goodsType) > 0) { potential = impType.getProductionModifier(goodsType).applyTo(potential); } } maxProduction = Math.max((int) potential, maxProduction); } return maxProduction; } /** * Describe <code>getProductionBonus</code> method here. * * @param goodsType a <code>GoodsType</code> value * @return a <code>Modifier</code> value */ public Set<Modifier> getProductionBonus(GoodsType goodsType, UnitType unitType) { Set<Modifier> result = new HashSet<Modifier>(); result.addAll(type.getProductionBonus(goodsType)); if (tileItemContainer != null) { Resource resource = tileItemContainer.getResource(); if (resource != null) { result.addAll(resource.getType().getProductionModifier(goodsType, unitType)); } if (!result.isEmpty()) { result.addAll(tileItemContainer.getProductionBonus(goodsType, unitType)); } } return result; } /** * Checks whether this <code>Tile</code> can have a road or not. This * method will return <code>false</code> if a road has already been built. * * @return The result. */ public boolean canGetRoad() { return isLand() && (tileItemContainer == null || tileItemContainer.getRoad() == null); } /** * Finds the TileImprovement of a given Type, or null if there is no match. */ public TileImprovement findTileImprovementType(TileImprovementType type) { if (tileItemContainer == null) { return null; } else { return tileItemContainer.findTileImprovementType(type); } } /** * Will check whether this tile has a completed improvement of the given * type. * * Useful for checking whether the tile for instance has a road or is * plowed. * * @param type * The type to check for. * @return Whether the tile has the improvement and the improvement is * completed. */ public boolean hasImprovement(TileImprovementType type) { if (type.changeContainsTarget(getType())) { return true; } else if (tileItemContainer != null) { return tileItemContainer.hasImprovement(type); } return false; } /** * Calculates the potential of a certain <code>GoodsType</code>. * * @param tileType * The <code>TileType</code>. * @param goodsType * The <code>GoodsType</code> to check the potential for. * @param tiContainer * The <code>TileItemContainer</code> with any TileItems to * give bonuses. * @param unitType an <code>UnitType</code> value * The Bonus Fish to be considered if valid * @return The amount of goods. */ public static int getTileTypePotential(TileType tileType, GoodsType goodsType, TileItemContainer tiContainer, UnitType unitType) { if (tileType == null || goodsType == null || !goodsType.isFarmed()) { return 0; } // Get tile potential + bonus if any int potential = tileType.getProductionOf(goodsType, unitType); if (tiContainer != null) { potential = tiContainer.getTotalBonusPotential(goodsType, unitType, potential, false); } return potential; } /** * Sorts GoodsTypes according to potential based on TileType, * TileItemContainer if any. * * @return The sorted GoodsTypes. */ public List<AbstractGoods> getSortedPotential() { return getSortedPotential(null, null); } /** * Sorts GoodsTypes according to potential based on TileType, * TileItemContainer if any. * * @param unit the <code>Unit</code> to work on this Tile * * @return The sorted GoodsTypes. */ public List<AbstractGoods> getSortedPotential(Unit unit) { return getSortedPotential(unit.getType(), unit.getOwner()); } /** * Sorts GoodsTypes according to potential based on TileType, * TileItemContainer if any. * * @param unitType the <code>UnitType</code> to work on this Tile * @param owner the <code>Player</code> owning the unit * * @return The sorted GoodsTypes. */ public List<AbstractGoods> getSortedPotential(UnitType unitType, Player owner) { List<AbstractGoods> goodsTypeList = new ArrayList<AbstractGoods>(); if (getType() != null) { // It is necessary to consider all farmed goods, since the // tile might have a resource that produces goods not // produced by the tile type. for (GoodsType goodsType : getSpecification().getFarmedGoodsTypeList()) { int potential = potential(goodsType, unitType); if (potential > 0) { goodsTypeList.add(new AbstractGoods(goodsType, potential)); } } if (owner == null || owner.getMarket() == null) { Collections.sort(goodsTypeList, new Comparator<AbstractGoods>() { public int compare(AbstractGoods o, AbstractGoods p) { return p.getAmount() - o.getAmount(); } }); } else { final Market market = owner.getMarket(); Collections.sort(goodsTypeList, new Comparator<AbstractGoods>() { public int compare(AbstractGoods o, AbstractGoods p) { return market.getSalePrice(p.getType(), p.getAmount()) - market.getSalePrice(o.getType(), o.getAmount()); } }); } } return goodsTypeList; } /** * This method is called only when a new turn is beginning. It * will reduce the quantity of the bonus <code>Resource</code> * that is on the tile, if any and if applicable. * * @return The resource if it is exhausted by this call (so it can * be used in a message), otherwise null. * @see ResourceType */ public Resource expendResource(GoodsType goodsType, UnitType unitType, Settlement settlement) { if (hasResource() && tileItemContainer.getResource().getQuantity() != -1) { Resource resource = tileItemContainer.getResource(); // Potential of this Tile and Improvements // TODO: review int potential = getTileTypePotential(getType(), goodsType, tileItemContainer, unitType); for (TileItem item : tileItemContainer.getTileItems()) { if (item instanceof TileImprovement) { potential += ((TileImprovement) item).getBonus(goodsType); } } if (resource.useQuantity(goodsType, unitType, potential) == 0) { tileItemContainer.removeTileItem(resource); updatePlayerExploredTiles(); return resource; } } return null; } /** * Updates the <code>PlayerExploredTile</code> for each player. This * update will only be performed if the player * {@link Player#canSee(Tile) can see} this <code>Tile</code>. */ public void updatePlayerExploredTiles() { updatePlayerExploredTiles(null); } /** * Updates the <code>PlayerExploredTile</code> for each player. This * update will only be performed if the player * {@link Player#canSee(Tile) can see} this <code>Tile</code>. * * @param oldPlayer The optional <code>Player</code> that formerly * had visibility of this tile and should see the change. */ public void updatePlayerExploredTiles(Player oldPlayer) { if (playerExploredTiles == null || getGame().getViewOwner() != null) { return; } for (Player player : getGame().getLiveEuropeanPlayers()) { if (player == oldPlayer || player.canSee(this)) { updatePlayerExploredTile(player, false); } } } /** * Gets the <code>PlayerExploredTile</code> for the given * <code>Player</code>. * * @param player The <code>Player</code>. * @see PlayerExploredTile */ public PlayerExploredTile getPlayerExploredTile(Player player) { return (playerExploredTiles == null) ? null : playerExploredTiles.get(player); } /** * Updates the information about this <code>Tile</code> for the given * <code>Player</code>. * * @param player The <code>Player</code>. * @param full If true, also update any hidden information specific to a * settlement present on the tile. */ public void updatePlayerExploredTile(Player player, boolean full) { if (playerExploredTiles == null || getGame().getViewOwner() != null || !player.isEuropean()) { return; } PlayerExploredTile pet = playerExploredTiles.get(player); if (pet == null) { pet = new PlayerExploredTile(getGame(), player, this); playerExploredTiles.put(player, pet); } pet.update(full); } /** * Checks if this <code>Tile</code> has been explored by the given * <code>Player</code>. * * @param player The <code>Player</code>. * @return <code>true</code> if this <code>Tile</code> has been explored * by the given <code>Player</code> and <code>false</code> * otherwise. */ public boolean isExploredBy(Player player) { if (!player.isEuropean()) return true; if (!isExplored()) return false; return getPlayerExploredTile(player) != null; } /** * Sets this <code>Tile</code> to be explored by the given * <code>Player</code>. * * @param player The <code>Player</code>. * @param explored <code>true</code> if this <code>Tile</code> should be * explored by the given <code>Player</code> and * <code>false</code> otherwise. */ public void setExploredBy(Player player, boolean explored) { if (!player.isEuropean()) return; if (explored) { updatePlayerExploredTile(player, false); } else { if (playerExploredTiles != null) { playerExploredTiles.remove(player); } } } /** * Returns the number of turns it takes for a non-expert pioneer to perform * the given <code>TileImprovementType</code>. It will check if it is valid * for this <code>TileType</code>. * * @param workType The <code>TileImprovementType</code> * * @return The number of turns it should take a non-expert pioneer to finish * the work. */ public int getWorkAmount(TileImprovementType workType) { if (workType == null) { return -1; } if (!workType.isTileAllowed(this)) { return -1; } // Return the basic work turns + additional work turns return (getType().getBasicWorkTurns() + workType.getAddWorkTurns()); } /** * Returns the unit who is occupying the tile * @return the unit who is occupying the tile * @see #isOccupied() */ public Unit getOccupyingUnit() { Unit unit = getFirstUnit(); Player owner = null; if (getOwningSettlement() != null) { owner = getOwningSettlement().getOwner(); } if (owner != null && unit != null && unit.getOwner() != owner && owner.getStance(unit.getOwner()) != Stance.ALLIANCE) { for(Unit enemyUnit : getUnitList()) { if (enemyUnit.isOffensiveUnit() && enemyUnit.getState() == Unit.UnitState.FORTIFIED) { return enemyUnit; } } } return null; } /** * Checks whether there is a fortified enemy unit in the tile. * Units can't produce in occupied tiles * @return <code>true</code> if an fortified enemy unit is in the tile */ public boolean isOccupied() { return getOccupyingUnit() != null; } /** * Determines whether this tile is adjacent to the specified tile. * * @param tile A potentially adjacent <code>Tile</code>. * @return <code>true</code> if the tile is adjacent to this tile */ public boolean isAdjacent(Tile tile) { return (tile == null) ? false : this.getDistanceTo(tile) == 1; } /** * Gets the position adjacent Tile to a given Tile, in a given * direction. * * @param direction The direction (N, NE, E, etc.) * @return Adjacent tile */ public Tile getAdjacentTile(Direction direction) { int x = getX() + ((getY() & 1) != 0 ? direction.getOddDX() : direction.getEvenDX()); int y = getY() + ((getY() & 1) != 0 ? direction.getOddDY() : direction.getEvenDY()); return getMap().getTile(x, y); } /** * Returns all the tiles surrounding this tile within the * given range. This tile is not included. * * @param range * How far away do we need to go starting from this. * @return The tiles surrounding this tile. */ public Iterable<Tile> getSurroundingTiles(final int range) { return new Iterable<Tile>() { public Iterator<Tile> iterator() { final Iterator<Position> m = (range == 1) ? getMap().getAdjacentIterator(getPosition()) : getMap().getCircleIterator(getPosition(), true, range); return new Iterator<Tile>() { public boolean hasNext() { return m.hasNext(); } public Tile next() { return getMap().getTile(m.next()); } public void remove() { m.remove(); } }; } }; } /** * Returns all the tiles surrounding this tile within the * given inclusive upper and lower bounds. * getSurroundingTiles(r) is equivalent to getSurroundingTiles(1, r), * thus this tile is included if rangeMin is zero. * * @param rangeMin The inclusive minimum distance from this tile. * @param rangeMax The inclusive maximum distance from this tile. * @return A list of the tiles surrounding this tile. */ public List<Tile> getSurroundingTiles(int rangeMin, int rangeMax) { List<Tile> result = new ArrayList<Tile>(); if (rangeMin > rangeMax || rangeMin < 0) return result; if (rangeMin == 0) result.add(this); if (rangeMax > 0) { for (Tile t : getSurroundingTiles(rangeMax)) { // add all tiles up to rangeMax result.add(t); } } if (rangeMin > 1) { for (Tile t : getSurroundingTiles(rangeMin - 1)) { // remove the tiles closer than rangeMin result.remove(t); } } return result; } /** * Checks if the given <code>Tile</code> is adjacent to the * east or west edge of the map. * * @return <code>true</code> if the given tile is at the edge of the map. */ public boolean isAdjacentToVerticalMapEdge() { if ((getNeighbourOrNull(Direction.E) == null) || (getNeighbourOrNull(Direction.W) == null)) { return true; } return false; } /** * Checks if the given <code>Tile</code> is adjacent to the edge of the * map. * * @return <code>true</code> if the given tile is at the edge of the map. */ public boolean isAdjacentToMapEdge() { for (Direction direction : Direction.values()) { if (getNeighbourOrNull(direction) == null) { return true; } } return false; } /** * Finds the nearest settlement to this tile. * * @param owner If non-null, the settlement should be owned by this player. * @param radius The maximum radius of the search. * @return The nearest settlement, or null if none. */ public Settlement getNearestSettlement(Player owner, int radius) { if (radius <= 0) radius = INFINITY; Map map = getGame().getMap(); Iterator<Position> iter = map.getCircleIterator(getPosition(), true, radius); while (iter.hasNext()) { Tile t = map.getTile(iter.next()); if (t == this) continue; Settlement settlement = t.getSettlement(); if (settlement != null && (owner == null || settlement.getOwner() == owner)) { return settlement; } } return null; } /** * Finds a safe tile to put a unit on, near to this one. * Useful on return from Europe. * * @param player The owner of the unit to place (may be null). * @param random An optional pseudo-random number source. * @return A vacant tile near this one. */ public Tile getSafeTile(Player player, Random random) { if ((getFirstUnit() == null || getFirstUnit().getOwner() == player) && (getSettlement() == null || getSettlement().getOwner() == player)) { return this; } for (int r = 1; true; r++) { List<Tile> tiles = getSurroundingTiles(r, r); if (random != null) Collections.shuffle(tiles, random); for (Tile t : tiles) { if ((t.getFirstUnit() == null || t.getFirstUnit().getOwner() == player) && (t.getSettlement() == null || t.getSettlement().getOwner() == player)) { return t; } } } } /** * Write a minimal version of the tile. Useful if the player * has not explored the tile. * * @param out The target stream. */ public void toXMLMinimal(XMLStreamWriter out) throws XMLStreamException { out.writeStartElement(getXMLElementTagName()); super.writeAttributes(out); out.writeAttribute("x", Integer.toString(x)); out.writeAttribute("y", Integer.toString(y)); out.writeAttribute("style", Integer.toString(style)); if (moveToEurope != null) { out.writeAttribute("moveToEurope", Boolean.toString(moveToEurope)); } out.writeEndElement(); } /** * 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 { if (!showAll) { if (toSavedGame) { logger.warning("toSavedGame is true, but showAll is false"); } if (player == null) { logger.warning("player is null, but showAll is false"); } } PlayerExploredTile pet = (showAll || toSavedGame) ? null : getPlayerExploredTile(player); // Start element: out.writeStartElement(getXMLElementTagName()); super.writeAttributes(out); out.writeAttribute("x", Integer.toString(x)); out.writeAttribute("y", Integer.toString(y)); out.writeAttribute("style", Integer.toString(style)); writeAttribute(out, "type", getType()); writeAttribute(out, "region", getRegion()); if (moveToEurope != null) { out.writeAttribute("moveToEurope", Boolean.toString(moveToEurope)); } if (connected && !type.isConnected()) { out.writeAttribute("connected", Boolean.toString(true)); } if (showAll || toSavedGame || player.canSee(this)) { if (owner != null) { out.writeAttribute("owner", owner.getId()); } if (owningSettlement != null) { out.writeAttribute("owningSettlement", owningSettlement.getId()); } } else if (pet != null) { if (pet.getOwner() != null) { out.writeAttribute("owner", pet.getOwner().getId()); } if (pet.getOwningSettlement() != null) { out.writeAttribute("owningSettlement", pet.getOwningSettlement().getId()); } } // End of attributes if (showAll || toSavedGame || player.canSee(this)) { if (settlement != null) { settlement.toXML(out, player, showAll, toSavedGame); } // Show enemy units if there is no enemy settlement. if ((showAll || toSavedGame || settlement == null || settlement.getOwner() == player) && !isEmpty()) { super.writeChildren(out, player, showAll, toSavedGame); } } else if (pet != null) { // Only display the settlement if we know it owns the tile // and we have a useful level of information about it. // This is a compromise, but something more precise is too // complex for the present. if (settlement != null && settlement == pet.getOwningSettlement() && settlement.getOwner() == pet.getOwner() && !(settlement instanceof Colony && pet.getColonyUnitCount() <= 0)) { settlement.toXML(out, player, showAll, toSavedGame); } } if (tileItemContainer != null) { tileItemContainer.toXML(out, player, showAll, toSavedGame); } // Save the pets. if (toSavedGame && playerExploredTiles != null) { for (Entry<Player, PlayerExploredTile> entry : playerExploredTiles.entrySet()) { entry.getValue().toXML(out, entry.getKey(), showAll, toSavedGame); } } out.writeEndElement(); } /** * {@inheritDoc} */ protected void readAttributes(XMLStreamReader in) throws XMLStreamException { super.readAttributes(in); x = Integer.parseInt(in.getAttributeValue(null, "x")); y = Integer.parseInt(in.getAttributeValue(null, "y")); style = getAttribute(in, "style", 0); String typeString = in.getAttributeValue(null, "type"); if (typeString != null) { type = getSpecification().getTileType(typeString); } connected = getAttribute(in, "connected", false); owner = getFreeColGameObject(in, "owner", Player.class, null); region = getFreeColGameObject(in, "region", Region.class, null); moveToEurope = (in.getAttributeValue(null, "moveToEurope") == null) ? null : getAttribute(in, "moveToEurope", false); final String owningSettlementStr = in.getAttributeValue(null, "owningSettlement"); Settlement newOwningSettlement = null; if (owningSettlementStr != null) { newOwningSettlement = (Settlement) getGame().getFreeColGameObject(owningSettlementStr); if (newOwningSettlement == null) { if (owningSettlementStr.startsWith(IndianSettlement.getXMLElementTagName())) { newOwningSettlement = new IndianSettlement(getGame(), owningSettlementStr); } else if (owningSettlementStr.startsWith(Colony.getXMLElementTagName())) { newOwningSettlement = new Colony(getGame(), owningSettlementStr); } else { logger.warning("Unknown type of Settlement."); } } } else { newOwningSettlement = null; } changeOwningSettlement(newOwningSettlement); } /** * {@inheritDoc} */ protected void readChildren(XMLStreamReader in) throws XMLStreamException { Settlement oldSettlement = settlement; Player oldSettlementOwner = (settlement == null) ? null : settlement.getOwner(); settlement = null; super.readChildren(in); // Player settlement list is not passed in player updates // so do it here. TODO: something better. Player settlementOwner = (settlement == null) ? null : settlement.getOwner(); if (settlement == null && oldSettlement != null) { // Settlement disappeared oldSettlement.setOwner(null); oldSettlementOwner.removeSettlement(oldSettlement); } else if (settlement != null && oldSettlement == null) { // Settlement appeared settlementOwner.addSettlement(settlement); owner = settlementOwner; } else if (settlementOwner != oldSettlementOwner) { // Settlement changed owner oldSettlement.setOwner(null); oldSettlementOwner.removeSettlement(oldSettlement); settlement.setOwner(settlementOwner); settlementOwner.addSettlement(settlement); owner = settlementOwner; } if (getColony() != null && getColony().isTileInUse(this)) { getColony().invalidateCache(); } } /** * {@inheritDoc} */ protected void readChild(XMLStreamReader in) throws XMLStreamException { if (in.getLocalName().equals(Colony.getXMLElementTagName())) { settlement = updateFreeColGameObject(in, Colony.class); } else if (in.getLocalName().equals(IndianSettlement.getXMLElementTagName())) { settlement = updateFreeColGameObject(in, IndianSettlement.class); } else if (in.getLocalName().equals(UNITS_TAG_NAME)) { // @compat 0.10.1 while (in.nextTag() != XMLStreamConstants.END_ELEMENT) { if (in.getLocalName().equals(Unit.getXMLElementTagName())) { add(updateFreeColGameObject(in, Unit.class)); } } // end compatibility code } else if (in.getLocalName().equals(TileItemContainer.getXMLElementTagName())) { tileItemContainer = (TileItemContainer) getGame().getFreeColGameObject(in.getAttributeValue(null, ID_ATTRIBUTE)); if (tileItemContainer != null) { tileItemContainer.readFromXML(in); } else { tileItemContainer = new TileItemContainer(getGame(), this, in); } } else if (in.getLocalName().equals(PlayerExploredTile.getXMLElementTagName())) { // Only from a savegame: Player player = (Player) getGame().getFreeColGameObject(in.getAttributeValue(null, "player")); PlayerExploredTile pet = getPlayerExploredTile(player); if (pet == null) { pet = new PlayerExploredTile(getGame(), in); playerExploredTiles.put(player, pet); } else { pet.readFromXML(in); } } else { super.readChild(in); } } /** * Fixes visible pets where there is a settlement present but the * tile is not owned correctly as ownership was not implemented in * 0.9.x. * Need to do this after reading the game so that canSee() is valid. * TODO: remove when 0.9.x is not supported. */ public void fixup09x() { if (playerExploredTiles == null) return; for (Entry<Player, PlayerExploredTile> e : playerExploredTiles.entrySet()) { Player p = e.getKey(); PlayerExploredTile pet = e.getValue(); if (settlement != null) { if (pet.getOwner() == null || pet.getOwningSettlement() == null) { if (p.canSee(this)) { // Correct with an ordinary update pet.update(false); } else if (settlement instanceof Colony) { if (pet.getColonyUnitCount() > 0) { // Have seen the colony, update the ownership // and the stockade level but not the unit count // as that is the one that was seen. pet.setOwner(settlement.getOwner()); pet.setOwningSettlement(settlement); pet.setColonyStockadeKey(((Colony) settlement) .getStockadeKey()); } } else if (settlement instanceof IndianSettlement) { // Unclear what has been seen, update just the ownership pet.setOwner(settlement.getOwner()); pet.setOwningSettlement(settlement); } } } else { if (pet.getOwningSettlement() != null && pet.getOwner() == null) { pet.setOwner(pet.getOwningSettlement().getOwner()); } } } } /** * Returns a String representation of this Tile. * * @return A String representation of this Tile. */ @Override public String toString() { return "Tile(" + x + "," + y +"):" + ((type == null) ? "unknown" : type.getId()); } /** * Returns the tag name of the root element representing this object. * * @return "tile". */ public static String getXMLElementTagName() { return "tile"; } }