mirror of https://github.com/FreeCol/freecol.git
841 lines
27 KiB
Java
841 lines
27 KiB
Java
/**
|
|
* Copyright (C) 2002-2022 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.Collections;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.function.Function;
|
|
import java.util.logging.Logger;
|
|
import java.util.stream.Collectors;
|
|
import java.util.stream.Stream;
|
|
|
|
import javax.xml.stream.XMLStreamException;
|
|
|
|
import net.sf.freecol.common.io.FreeColXMLReader;
|
|
import net.sf.freecol.common.io.FreeColXMLWriter;
|
|
import static net.sf.freecol.common.model.Constants.*;
|
|
import net.sf.freecol.common.model.Map.Layer;
|
|
import net.sf.freecol.common.option.GameOptions;
|
|
import static net.sf.freecol.common.util.CollectionUtils.*;
|
|
import net.sf.freecol.common.util.LogBuilder;
|
|
import net.sf.freecol.common.util.RandomChoice;
|
|
|
|
|
|
/**
|
|
* Represents a tile improvement, such as a river or road.
|
|
*/
|
|
public class TileImprovement extends TileItem {
|
|
|
|
private static final Logger logger = Logger.getLogger(TileImprovement.class.getName());
|
|
|
|
public static final String TAG = "tileImprovement";
|
|
|
|
public static final String EMPTY_RIVER_STYLE = "0000";
|
|
public static final String EMPTY_ROAD_STYLE = "00000000";
|
|
|
|
/** River magnitudes */
|
|
public static final int NO_RIVER = 0;
|
|
public static final int SMALL_RIVER = 1;
|
|
public static final int LARGE_RIVER = 2;
|
|
public static final int FJORD_RIVER = 3;
|
|
|
|
|
|
/** The type of this improvement. */
|
|
private TileImprovementType type;
|
|
|
|
/** Turns remaining until the improvement is complete, if any. */
|
|
private int turnsToComplete;
|
|
|
|
/**
|
|
* The improvement magnitude. Default is type.getMagnitude(), but
|
|
* this will override.
|
|
*/
|
|
private int magnitude;
|
|
|
|
/** Image and overlay style information for the improvement. */
|
|
private TileImprovementStyle style;
|
|
|
|
/**
|
|
* Whether this is a virtual improvement granted by some structure
|
|
* on the tile (a Colony, for example). Virtual improvements will
|
|
* be removed along with the structure that granted them.
|
|
*/
|
|
private boolean virtual;
|
|
|
|
|
|
/**
|
|
* Creates a standard {@code TileImprovement}-instance.
|
|
*
|
|
* This constructor asserts that the game, tile and type are valid.
|
|
* Does not set the style.
|
|
*
|
|
* @param game The enclosing {@code Game}.
|
|
* @param tile The {@code Tile} on which this object sits.
|
|
* @param type The {@code TileImprovementType} of this
|
|
* improvement.
|
|
* @param style The {@code TileImprovementStyle} of this improvement.
|
|
*/
|
|
public TileImprovement(Game game, Tile tile, TileImprovementType type,
|
|
TileImprovementStyle style) {
|
|
super(game, tile);
|
|
if (type == null) {
|
|
throw new RuntimeException("Type must not be null: " + this);
|
|
}
|
|
this.type = type;
|
|
if (!type.isNatural()) {
|
|
this.turnsToComplete = tile.getType().getBasicWorkTurns()
|
|
+ type.getAddWorkTurns();
|
|
}
|
|
this.magnitude = type.getMagnitude();
|
|
this.style = style;
|
|
}
|
|
|
|
/**
|
|
* Create a new {@code TileImprovement} with the given identifier.
|
|
*
|
|
* The object should be initialized later.
|
|
*
|
|
* @param game The enclosing {@code Game}.
|
|
* @param id The object identifier.
|
|
*/
|
|
public TileImprovement(Game game, String id) {
|
|
super(game, id);
|
|
}
|
|
|
|
|
|
/**
|
|
* Gets the type of this tile improvement.
|
|
*
|
|
* @return The type of this improvement.
|
|
*/
|
|
public TileImprovementType getType() {
|
|
return type;
|
|
}
|
|
|
|
/**
|
|
* Is this tile improvement a river?
|
|
*
|
|
* @return True if this is a river improvement.
|
|
*/
|
|
public boolean isRiver() {
|
|
return "model.improvement.river".equals(type.getId());
|
|
}
|
|
|
|
/**
|
|
* Is this tile improvement a road?
|
|
*
|
|
* @return True if this is a road improvement.
|
|
*/
|
|
public boolean isRoad() {
|
|
return "model.improvement.road".equals(type.getId());
|
|
}
|
|
|
|
/**
|
|
* Gets the directions that a connection can form across for this
|
|
* this type of improvement.
|
|
*
|
|
* - For rivers, it is just the longSided directions.
|
|
* - For roads, it is all directions.
|
|
* - In other cases, no directions are relevant.
|
|
*
|
|
* @return An array of relevant directions, or null if none.
|
|
*/
|
|
public List<Direction> getConnectionDirections() {
|
|
return (isRoad()) ? Direction.allDirections
|
|
: (isRiver()) ? Direction.longSides
|
|
: null;
|
|
}
|
|
|
|
/**
|
|
* How many turns remain until this improvement is complete?
|
|
*
|
|
* @return The current turns to completion.
|
|
*/
|
|
public int getTurnsToComplete() {
|
|
return turnsToComplete;
|
|
}
|
|
|
|
/**
|
|
* Sets the turns required to complete the improvement.
|
|
*
|
|
* @param turns The new turns to completion.
|
|
*/
|
|
public void setTurnsToComplete(int turns) {
|
|
turnsToComplete = turns;
|
|
}
|
|
|
|
/**
|
|
* Gets the magnitude of this improvement.
|
|
*
|
|
* @return The magnitude of this immprovement.
|
|
*/
|
|
public int getMagnitude() {
|
|
return magnitude;
|
|
}
|
|
|
|
/**
|
|
* Sets the magnitude of this improvement.
|
|
*
|
|
* @param magnitude The new magnitude.
|
|
*/
|
|
public void setMagnitude(int magnitude) {
|
|
this.magnitude = magnitude;
|
|
}
|
|
|
|
/**
|
|
* Gets the style of this improvement.
|
|
*
|
|
* @return The style
|
|
*/
|
|
public TileImprovementStyle getStyle() {
|
|
return style;
|
|
}
|
|
|
|
/**
|
|
* Set the style of this improvement.
|
|
*
|
|
* @param style The new {@code TileImprovementStyle}.
|
|
*/
|
|
public void setStyle(TileImprovementStyle style) {
|
|
this.style = style;
|
|
}
|
|
|
|
/**
|
|
* Is this a virtual improvement?
|
|
*
|
|
* @return True if this is a virtual improvement.
|
|
*/
|
|
public final boolean getVirtual() {
|
|
return virtual;
|
|
}
|
|
|
|
/**
|
|
* Set the virtual status of this improvement.
|
|
* Used for the roads in a colony center tile.
|
|
*
|
|
* @param virtual The new virtual value.
|
|
*/
|
|
public final void setVirtual(final boolean virtual) {
|
|
this.virtual = virtual;
|
|
}
|
|
|
|
/**
|
|
* Get the magnitude of the river branch in the given direction.
|
|
* Precondition: This tile improvement is a river!
|
|
*
|
|
* @param direction The {@code Direction} to check.
|
|
* @return The magnitude of the river branch or 0 if there is none.
|
|
*/
|
|
public int getRiverConnection(Direction direction) {
|
|
int index = Direction.longSides.indexOf(direction);
|
|
if (index == -1 || style == null)
|
|
return 0;
|
|
int mag = Character.digit(style.getString().charAt(index), 10);
|
|
return (mag == -1) ? 0 : mag;
|
|
}
|
|
|
|
/**
|
|
* Is this tile improvement connected to a similar improvement on
|
|
* a neighbouring tile?
|
|
*
|
|
* Public for the test suite.
|
|
*
|
|
* @param direction The {@code Direction} to check.
|
|
* @return True if this improvement is connected.
|
|
*/
|
|
public boolean isConnectedTo(Direction direction) {
|
|
int index = isRoad() ? direction.ordinal()
|
|
: isRiver() ? Direction.longSides.indexOf(direction) : -1;
|
|
return (index == -1 || style == null) ? false
|
|
: style.getString().charAt(index) != '0';
|
|
}
|
|
|
|
/**
|
|
* Internal helper method to set the connection status in a given direction.
|
|
* There is no check for backwards connections on neighbouring tiles!
|
|
*
|
|
* @param direction The {@code Direction} to set.
|
|
* @param value The new status for the connection.
|
|
*/
|
|
private void setConnected(Direction direction, boolean value) {
|
|
if (style == null || isConnectedTo(direction) != value)
|
|
setConnected(direction, value, Integer.toString(magnitude));
|
|
}
|
|
|
|
private void setConnected(Direction direction, boolean value, String magnitude) {
|
|
if (style == null) {
|
|
style = TileImprovementStyle.getInstance(EMPTY_ROAD_STYLE);
|
|
}
|
|
String old = style.toString();
|
|
List<Direction> directions = getConnectionDirections();
|
|
int end = directions.size();
|
|
StringBuilder updated = new StringBuilder();
|
|
for(int index = 0; index != end; ++index) {
|
|
if(directions.get(index) == direction)
|
|
updated.append(value ? magnitude : "0");
|
|
else
|
|
updated.append(old.charAt(index));
|
|
}
|
|
style = TileImprovementStyle.getInstance(updated.toString());
|
|
}
|
|
|
|
/**
|
|
* Gets a map of connection-direction to magnitude.
|
|
*
|
|
* @return A map of the connections.
|
|
*/
|
|
public Map<Direction, Integer> getConnections() {
|
|
final List<Direction> dirns = getConnectionDirections();
|
|
return (dirns == null) ? Collections.<Direction, Integer>emptyMap()
|
|
: transform(dirns, d -> isConnectedTo(d),
|
|
Function.<Direction>identity(),
|
|
Collectors.toMap(Function.<Direction>identity(),
|
|
d -> magnitude));
|
|
}
|
|
|
|
/**
|
|
* Gets a Modifier for the production bonus this improvement provides
|
|
* for a given type of goods.
|
|
*
|
|
* @param goodsType The {@code GoodsType} to test.
|
|
* @return A production {@code Modifier}, or null if none applicable.
|
|
*/
|
|
private Modifier getProductionModifier(GoodsType goodsType) {
|
|
final Modifier modifier = (isComplete()) ? type.getProductionModifier(goodsType) : null;
|
|
if (modifier == null || modifier.getValue() == 0 || magnitude <= 0) {
|
|
return modifier;
|
|
}
|
|
|
|
final Modifier modifierWithMagnitudeBonus = Modifier.makeModifier(modifier);
|
|
modifierWithMagnitudeBonus.setValue(modifierWithMagnitudeBonus.getValue() * magnitude);
|
|
return modifierWithMagnitudeBonus;
|
|
}
|
|
|
|
/**
|
|
* Calculates the movement cost on the basis of connected tile
|
|
* improvements.
|
|
*
|
|
* @param direction The {@code Direction} to move.
|
|
* @param moveCost The original movement cost.
|
|
* @return The movement cost with this improvement.
|
|
*/
|
|
public int getMoveCost(Direction direction, int moveCost) {
|
|
return (isComplete() && isConnectedTo(direction))
|
|
? type.getMoveCost(moveCost)
|
|
: moveCost;
|
|
}
|
|
|
|
/**
|
|
* What type of tile does this improvement change a given type to?
|
|
*
|
|
* @param tileType The original {@code TileType}.
|
|
* @return The {@code TileType} that results from completing this
|
|
* improvement, or null if nothing changes.
|
|
*/
|
|
public TileType getChange(TileType tileType) {
|
|
return (isComplete()) ? type.getChange(tileType) : null;
|
|
}
|
|
|
|
/**
|
|
* Can a unit build this improvement?
|
|
*
|
|
* @param unit A {@code Unit} to do the building.
|
|
* @return True if the supplied unit can build this improvement.
|
|
*/
|
|
public boolean isWorkerAllowed(Unit unit) {
|
|
return (unit == null || isComplete()) ? false
|
|
: type.isWorkerAllowed(unit);
|
|
}
|
|
|
|
/**
|
|
* Updates the connections from the current style.
|
|
*
|
|
* Public for the test suite.
|
|
*
|
|
* @return The connections implied by the current style.
|
|
*/
|
|
public final long getConnectionsFromStyle() {
|
|
long conn = 0L;
|
|
if (style != null) {
|
|
List<Direction> directions = getConnectionDirections();
|
|
if (directions != null) {
|
|
String mask = style.getMask();
|
|
for (int i = 0; i < directions.size(); i++) {
|
|
if (mask.charAt(i) != '0') {
|
|
conn |= 1L << directions.get(i).ordinal();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return conn;
|
|
}
|
|
|
|
/**
|
|
* Sets the river style to be as close as possible to the requested
|
|
* style, even when it has to create neighbouring rivers to prevent
|
|
* broken connections or change the magnitude.
|
|
*
|
|
* @param conns The river style to set.
|
|
*/
|
|
public void setRiverStyle(String conns) {
|
|
if (!isRiver()) return;
|
|
final Tile tile = getTile();
|
|
int i = 0;
|
|
int[] counts = {0, 0};
|
|
for (Direction d : Direction.longSides) {
|
|
Direction dReverse = d.getReverseDirection();
|
|
Tile t = tile.getNeighbourOrNull(d);
|
|
TileImprovement river = (t == null) ? null : t.getRiver();
|
|
String c = (conns == null) ? "0" : conns.substring(i, i+1);
|
|
|
|
if ("0".equals(c)) {
|
|
if (river != null) {
|
|
river.setConnected(dReverse, false);
|
|
}
|
|
setConnected(d, false);
|
|
} else {
|
|
int mag = Integer.parseInt(c);
|
|
if (river != null) {
|
|
river.setConnected(dReverse, true);
|
|
setConnected(d, true, c);
|
|
counts[mag-1]++;
|
|
} else if (t != null) {
|
|
if (!t.getType().isWater()) {
|
|
t.addRiver(mag, "0000");
|
|
t.getRiver().setConnected(dReverse, true);
|
|
}
|
|
setConnected(d, true, c);
|
|
counts[mag-1]++;
|
|
}
|
|
}
|
|
i++;
|
|
}
|
|
magnitude = counts[1] >= counts[0] ? 2 : 1;
|
|
}
|
|
|
|
/**
|
|
* Updates the connections from/to this river improvement on the basis
|
|
* of the expected encoded river style, as long as this would not
|
|
* create broken connections.
|
|
* Uses magnitude, not the connection strengths inside conns, when
|
|
* adding new connections.
|
|
*
|
|
* @param conns The encoded river connections, or null to disconnect.
|
|
* @return The actual encoded connections found.
|
|
*/
|
|
public String updateRiverConnections(String conns) {
|
|
// FIXME: Consider checking conns for incompatible length and content
|
|
// to prevent more bugs.
|
|
if (!isRiver()) return null;
|
|
final Tile tile = getTile();
|
|
int i = 0;
|
|
for (Direction d : Direction.longSides) {
|
|
Direction dReverse = d.getReverseDirection();
|
|
Tile t = tile.getNeighbourOrNull(d);
|
|
TileImprovement river = (t == null) ? null : t.getRiver();
|
|
String c = (conns == null) ? "0" : conns.substring(i, i+1);
|
|
|
|
if ("0".equals(c)) {
|
|
if (river != null) {
|
|
river.setConnected(dReverse, false);
|
|
}
|
|
setConnected(d, false);
|
|
} else {
|
|
if (river != null) {
|
|
river.setConnected(dReverse, true);
|
|
setConnected(d, true);
|
|
} else if (t != null && t.getType().isWater()) {
|
|
setConnected(d, true);
|
|
}
|
|
}
|
|
i++;
|
|
}
|
|
return (style == null) ? null : style.getString();
|
|
}
|
|
|
|
/**
|
|
* Updates the connections from/to this road improvement.
|
|
*
|
|
* @param connect If true, add connections, otherwise remove them.
|
|
* @return A string encoding of the correct connections for this
|
|
* improvement.
|
|
*/
|
|
public String updateRoadConnections(boolean connect) {
|
|
if (!isRoad() || !isComplete()) return null;
|
|
final Tile tile = getTile();
|
|
for (Direction d : Direction.values()) {
|
|
Tile t = tile.getNeighbourOrNull(d);
|
|
TileImprovement road = (t == null) ? null : t.getRoad();
|
|
if (road != null && road.isComplete()) {
|
|
road.setConnected(d.getReverseDirection(), connect);
|
|
setConnected(d, connect);
|
|
} else {
|
|
setConnected(d, false);
|
|
}
|
|
}
|
|
return (this.style == null) ? null : this.style.getString();
|
|
}
|
|
|
|
/**
|
|
* Get the disaster choices available for this tile improvement.
|
|
*
|
|
* @return A stream of {@code Disaster} choices.
|
|
*/
|
|
public Stream<RandomChoice<Disaster>> getDisasterChoices() {
|
|
return (this.type == null)
|
|
? Stream.<RandomChoice<Disaster>>empty()
|
|
: this.type.getDisasterChoices();
|
|
}
|
|
|
|
|
|
// Interface Named
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public String getNameKey() {
|
|
return (type == null) ? null : type.getNameKey();
|
|
}
|
|
|
|
|
|
// Interface TileItem
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public final int getZIndex() {
|
|
return type.getZIndex();
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public boolean isTileTypeAllowed(TileType tileType) {
|
|
return type.isTileTypeAllowed(tileType);
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public int applyBonus(GoodsType goodsType, UnitType unitType,
|
|
int potential) {
|
|
// Applies the production bonuses of this tile improvement to
|
|
// the given base potential. Currently, the unit type
|
|
// argument is ignored and is only provided for the sake of
|
|
// consistency. The bonuses of future improvements might
|
|
// depend on the unit type, however.
|
|
|
|
int result = potential;
|
|
// do not apply any bonuses if the base tile does not produce
|
|
// any goods, and don't apply bonuses for incomplete
|
|
// improvements (such as roads)
|
|
if (potential > 0 && isComplete()) {
|
|
result += getBonus(goodsType);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private int getBonus(GoodsType goodsType) {
|
|
final Modifier result = getProductionModifier(goodsType);
|
|
if (result == null) {
|
|
return 0;
|
|
} else {
|
|
return (int) result.getValue();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public boolean canProduce(GoodsType goodsType, UnitType unitType) {
|
|
// TileImprovements provide bonuses, but do *not* allow a tile
|
|
// that can not produce some goods to produce due to the bonus.
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public Stream<Modifier> getProductionModifiers(GoodsType goodsType, UnitType unitType) {
|
|
if (goodsType == null || !isComplete()) {
|
|
return Stream.<Modifier>empty();
|
|
}
|
|
|
|
final Specification spec = getSpecification();
|
|
|
|
if (unitType == null
|
|
&& isNatural()
|
|
&& goodsType.isFoodType()) {
|
|
return Stream.<Modifier>empty();
|
|
}
|
|
|
|
if (unitType == null
|
|
&& !isNatural()
|
|
&& !goodsType.isFoodType()
|
|
&& spec.getBoolean(GameOptions.ONLY_NATURAL_IMPROVEMENTS)) {
|
|
return Stream.<Modifier>empty();
|
|
}
|
|
|
|
Modifier m = getProductionModifier(goodsType);
|
|
if (m == null) {
|
|
return Stream.<Modifier>empty();
|
|
}
|
|
|
|
if (unitType != null
|
|
&& unitType.getExpertProduction() != null
|
|
&& unitType.getExpertProduction().equals(goodsType)) {
|
|
final Stream<Modifier> expertMultiplicativeModifier = unitType.getModifiers(goodsType.getId(), tile.getType(), null)
|
|
.filter(mod -> mod.getType() == Modifier.ModifierType.MULTIPLICATIVE);
|
|
|
|
final float expertBonus = FeatureContainer.applyModifiers(m.getValue(), null, expertMultiplicativeModifier);
|
|
m = Modifier.makeModifier(m);
|
|
m.setValue(expertBonus);
|
|
}
|
|
|
|
return Stream.of(m);
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public boolean isNatural() {
|
|
return type.isNatural();
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public boolean isComplete() {
|
|
return turnsToComplete <= 0;
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public Layer getLayer() {
|
|
return Layer.RIVERS;
|
|
}
|
|
|
|
|
|
// Override FreeColGameObject
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public IntegrityType checkIntegrity(boolean fix, LogBuilder lb) {
|
|
IntegrityType result = super.checkIntegrity(fix, lb);
|
|
Tile tile = getTile();
|
|
if (isRiver()) {
|
|
// @compat 0.11.5 Prevent NPE, TileItemContainer rechecks this.
|
|
if (style == null) {
|
|
lb.add("\n Broken null style river at ", tile);
|
|
return result.fail();
|
|
}
|
|
// end @compat 0.11.5
|
|
|
|
// @compat 0.11.6 There could be broken river connections, without
|
|
// the neighbouring tile having a corresponding connection or
|
|
// a water tile.
|
|
// These could at least be added using the map editor.
|
|
String conns = style.getString();
|
|
int i = 0;
|
|
for (Direction d : Direction.longSides) {
|
|
Direction dReverse = d.getReverseDirection();
|
|
Tile t = tile.getNeighbourOrNull(d);
|
|
TileImprovement river = (t == null) ? null : t.getRiver();
|
|
if (conns.charAt(i) != '0') {
|
|
if (river != null) {
|
|
if (!river.isConnectedTo(dReverse)) {
|
|
if (fix) {
|
|
setConnected(d, false);
|
|
lb.add("\n Removed broken river connection to ",
|
|
d, " at ", tile);
|
|
result = result.fix();
|
|
} else {
|
|
lb.add("\n Broken river connection to ", d,
|
|
" at ", tile);
|
|
result = result.fail();
|
|
}
|
|
}
|
|
} else if (t == null || !t.getType().isWater()) {
|
|
if (fix) {
|
|
setConnected(d, false);
|
|
lb.add("\n Removed broken river connection to ",
|
|
d, " at ", tile);
|
|
result = result.fix();
|
|
} else {
|
|
lb.add("\n Broken river connection to ", d,
|
|
" at ", tile);
|
|
result = result.fail();
|
|
}
|
|
}
|
|
}
|
|
i++;
|
|
}
|
|
// end @compat 0.11.6
|
|
} else if (isRoad() && isComplete()) {
|
|
// @compat 0.11.6 Roads on tiles never having another adjacent
|
|
// road tile had null styles, because updateRoadConnections
|
|
// forgot to set one for roads without a connection.
|
|
if (fix) {
|
|
TileImprovementStyle oldStyle = style;
|
|
updateRoadConnections(true);
|
|
if (style != oldStyle) {
|
|
lb.add("\n Bad road style from ", oldStyle,
|
|
" to ", style, " fixed at ", tile);
|
|
result = result.fix();
|
|
}
|
|
}
|
|
if (style == null) {
|
|
lb.add("\n Broken road with null style at ", tile);
|
|
result = result.fail();
|
|
}
|
|
// end @compat 0.11.6
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
// Override FreeColObject
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public <T extends FreeColObject> boolean copyIn(T other) {
|
|
TileImprovement o = copyInCast(other, TileImprovement.class);
|
|
if (o == null || !super.copyIn(o)) return false;
|
|
this.type = o.getType();
|
|
this.turnsToComplete = o.getTurnsToComplete();
|
|
this.magnitude = o.getMagnitude();
|
|
this.style = o.getStyle();
|
|
this.virtual = o.getVirtual();
|
|
return true;
|
|
}
|
|
|
|
|
|
// Serialization
|
|
|
|
private static final String MAGNITUDE_TAG = "magnitude";
|
|
private static final String STYLE_TAG = "style";
|
|
private static final String TILE_TAG = "tile";
|
|
private static final String TURNS_TAG = "turns";
|
|
private static final String TYPE_TAG = "type";
|
|
private static final String VIRTUAL_TAG = "virtual";
|
|
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
protected void writeAttributes(FreeColXMLWriter xw) throws XMLStreamException {
|
|
super.writeAttributes(xw);
|
|
|
|
xw.writeAttribute(TILE_TAG, getTile());
|
|
|
|
xw.writeAttribute(TYPE_TAG, getType());
|
|
|
|
xw.writeAttribute(TURNS_TAG, turnsToComplete);
|
|
|
|
xw.writeAttribute(MAGNITUDE_TAG, magnitude);
|
|
|
|
if (style != null) xw.writeAttribute(STYLE_TAG, style);
|
|
|
|
if (virtual) xw.writeAttribute(VIRTUAL_TAG, virtual);
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
protected void readAttributes(FreeColXMLReader xr) throws XMLStreamException {
|
|
super.readAttributes(xr);
|
|
|
|
final Specification spec = getSpecification();
|
|
final Game game = getGame();
|
|
|
|
tile = xr.makeFreeColObject(game, TILE_TAG, Tile.class, true);
|
|
|
|
type = xr.getType(spec, TYPE_TAG, TileImprovementType.class,
|
|
(TileImprovementType)null);
|
|
|
|
turnsToComplete = xr.getAttribute(TURNS_TAG, 0);
|
|
|
|
magnitude = xr.getAttribute(MAGNITUDE_TAG, 0);
|
|
|
|
virtual = xr.getAttribute(VIRTUAL_TAG, false);
|
|
|
|
style = null;
|
|
String str = xr.getAttribute(STYLE_TAG, (String)null);
|
|
List<Direction> dirns = getConnectionDirections();
|
|
if (dirns == null) {
|
|
if (str != null && !str.isEmpty())
|
|
logger.warning("At " + tile + " ignored nonempty style for "
|
|
+ type + ": " + str);
|
|
} else if (str == null) {
|
|
// Null style OK for incomplete roads. Virtual roads used
|
|
// to be null, but we are fixing that. Some cached tiles
|
|
// were wrongly getting null style. Do not bother
|
|
// complaining about these as they will get fixed and
|
|
// logged in checkIntegrity().
|
|
} else if (str.length() != dirns.size()) {
|
|
logger.warning("At " + tile + " ignored bogus style for "
|
|
+ type + ": " + str);
|
|
} else {
|
|
style = TileImprovementStyle.getInstance(str);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
public String getXMLTagName() { return TAG; }
|
|
|
|
|
|
// Override Object
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public String toString() {
|
|
StringBuilder sb = new StringBuilder(64);
|
|
TileImprovementType ty = getType();
|
|
sb.append('[').append((ty == null) ? "NOTYPE" : ty.getId());
|
|
if (turnsToComplete > 0) {
|
|
sb.append(" (").append(turnsToComplete).append(" turns left)");
|
|
}
|
|
if (style != null) sb.append(' ').append(style.getString());
|
|
sb.append(']');
|
|
return sb.toString();
|
|
}
|
|
}
|