package games.strategy.triplea.ui.mapdata;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Image;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import javax.imageio.ImageIO;
import games.strategy.debug.ClientLogger;
import games.strategy.engine.data.GameData;
import games.strategy.engine.data.Territory;
import games.strategy.triplea.Constants;
import games.strategy.triplea.ResourceLoader;
import games.strategy.triplea.image.UnitImageFactory;
import games.strategy.ui.Util;
import games.strategy.util.PointFileReaderWriter;
import games.strategy.util.UrlStreams;
/**
* contains data about the territories useful for drawing.
*/
public class MapData implements Closeable {
public static final String PROPERTY_UNITS_SCALE = "units.scale";
public static final String PROPERTY_UNITS_WIDTH = "units.width";
public static final String PROPERTY_UNITS_HEIGHT = "units.height";
public static final String PROPERTY_SCREENSHOT_TITLE_ENABLED = "screenshot.title.enabled";
public static final String PROPERTY_SCREENSHOT_TITLE_X = "screenshot.title.x";
public static final String PROPERTY_SCREENSHOT_TITLE_Y = "screenshot.title.y";
public static final String PROPERTY_SCREENSHOT_TITLE_COLOR = "screenshot.title.color";
public static final String PROPERTY_SCREENSHOT_TITLE_FONT_SIZE = "screenshot.title.font.size";
public static final String PROPERTY_COLOR_PREFIX = "color.";
public static final String PROPERTY_UNITS_COUNTER_OFFSET_WIDTH = "units.counter.offset.width";
public static final String PROPERTY_UNITS_COUNTER_OFFSET_HEIGHT = "units.counter.offset.height";
public static final String PROPERTY_UNITS_STACK_SIZE = "units.stack.size";
public static final String PROPERTY_MAP_WIDTH = "map.width";
public static final String PROPERTY_MAP_HEIGHT = "map.height";
public static final String PROPERTY_MAP_SCROLLWRAPX = "map.scrollWrapX";
public static final String PROPERTY_MAP_SCROLLWRAPY = "map.scrollWrapY";
public static final String PROPERTY_MAP_HASRELIEF = "map.hasRelief";
public static final String PROPERTY_MAP_CURSOR_HOTSPOT_X = "map.cursor.hotspot.x";
public static final String PROPERTY_MAP_CURSOR_HOTSPOT_Y = "map.cursor.hotspot.y";
public static final String PROPERTY_MAP_SHOWCAPITOLMARKERS = "map.showCapitolMarkers";
public static final String PROPERTY_MAP_USETERRITORYEFFECTMARKERS = "map.useTerritoryEffectMarkers";
public static final String PROPERTY_MAP_SHOWTERRITORYNAMES = "map.showTerritoryNames";
public static final String PROPERTY_MAP_SHOWRESOURCES = "map.showResources";
public static final String PROPERTY_MAP_SHOWCOMMENTS = "map.showComments";
public static final String PROPERTY_MAP_SHOWSEAZONENAMES = "map.showSeaZoneNames";
public static final String PROPERTY_MAP_DRAWNAMESFROMTOPLEFT = "map.drawNamesFromTopLeft";
public static final String PROPERTY_MAP_USENATION_CONVOYFLAGS = "map.useNation_convoyFlags";
public static final String PROPERTY_DONT_DRAW_TERRITORY_NAMES = "dont_draw_territory_names";
public static final String PROPERTY_MAP_MAPBLENDS = "map.mapBlends";
public static final String PROPERTY_MAP_MAPBLENDMODE = "map.mapBlendMode";
public static final String PROPERTY_MAP_MAPBLENDALPHA = "map.mapBlendAlpha";
private static final String CENTERS_FILE = "centers.txt";
private static final String POLYGON_FILE = "polygons.txt";
private static final String PLACEMENT_FILE = "place.txt";
private static final String TERRITORY_EFFECT_FILE = "territory_effects.txt";
private static final String MAP_PROPERTIES = "map.properties";
private static final String CAPITAL_MARKERS = "capitols.txt";
private static final String CONVOY_MARKERS = "convoy.txt";
private static final String COMMENT_MARKERS = "comments.txt";
private static final String VC_MARKERS = "vc.txt";
private static final String BLOCKADE_MARKERS = "blockade.txt";
private static final String PU_PLACE_FILE = "pu_place.txt";
private static final String TERRITORY_NAME_PLACE_FILE = "name_place.txt";
private static final String KAMIKAZE_FILE = "kamikaze_place.txt";
private static final String DECORATIONS_FILE = "decorations.txt";
// default colour if none is defined.
private final List<Color> m_defaultColours = Arrays.asList(Color.RED, Color.MAGENTA, Color.YELLOW, Color.ORANGE,
Color.CYAN, Color.GREEN, Color.PINK, Color.GRAY);
// maps PlayerName as String to Color
private final Map<String, Color> m_playerColors = new HashMap<>();
// maps String -> List of points
private Map<String, List<Point>> m_place;
// maps String -> Collection of Polygons
private Map<String, List<Polygon>> m_polys;
// maps String -> Point
private Map<String, Point> m_centers;
// maps String -> Point
private Map<String, Point> m_vcPlace;
// maps String -> Point
private Map<String, Point> m_blockadePlace;
// maps String -> Point
private Map<String, Point> m_convoyPlace;
// maps String -> Point
private Map<String, Point> m_commentPlace;
// maps String -> Point
private Map<String, Point> m_PUPlace;
// maps String -> Point
private Map<String, Point> m_namePlace;
// maps String -> Point
private Map<String, Point> m_kamikazePlace;
// maps String -> Point
private Map<String, Point> m_capitolPlace;
// maps String -> List of String
private Map<String, List<String>> m_contains;
private Properties m_mapProperties;
private Map<String, List<Point>> m_territoryEffects;
// we shouldnt draw the names to these territories
private Set<String> m_undrawnTerritoriesNames;
private Map<Image, List<Point>> m_decorations;
private Map<String, Image> m_territoryNameImages;
private final Map<String, Image> m_effectImages = new HashMap<>();
private final ResourceLoader m_resourceLoader;
public boolean scrollWrapX() {
return Boolean.valueOf(m_mapProperties.getProperty(PROPERTY_MAP_SCROLLWRAPX, "true"));
}
public boolean scrollWrapY() {
return Boolean.valueOf(m_mapProperties.getProperty(PROPERTY_MAP_SCROLLWRAPY, "false"));
}
public MapData(final String mapNameDir) {
this(ResourceLoader.getMapResourceLoader(mapNameDir));
}
/**
* Constructor TerritoryData(java.lang.String)
* Sets the map directory for this instance of TerritoryData
*
* @param loader
* .lang.String
* mapNameDir the given map directory
*/
public MapData(final ResourceLoader loader) {
m_resourceLoader = loader;
try {
final String prefix = "";
m_place = PointFileReaderWriter.readOneToMany(loader.getResourceAsStream(prefix + PLACEMENT_FILE));
m_territoryEffects =
PointFileReaderWriter.readOneToMany(loader.getResourceAsStream(prefix + TERRITORY_EFFECT_FILE));
if (loader.getResourceAsStream(prefix + POLYGON_FILE) == null) {
throw new IllegalStateException(
"Error in resource loading. Unable to load expected resource: " + prefix + POLYGON_FILE + ", the error"
+ " is that either we did not find the correct path to load. Check the resource loader to make"
+ " sure the map zip or dir was added. Failing that, the path in this error message should be available"
+ " relative to the map folder, or relative to the root of the map zip");
}
m_polys = PointFileReaderWriter.readOneToManyPolygons(loader.getResourceAsStream(prefix + POLYGON_FILE));
m_centers = PointFileReaderWriter.readOneToOneCenters(loader.getResourceAsStream(prefix + CENTERS_FILE));
m_vcPlace = PointFileReaderWriter.readOneToOne(loader.getResourceAsStream(prefix + VC_MARKERS));
m_convoyPlace = PointFileReaderWriter.readOneToOne(loader.getResourceAsStream(prefix + CONVOY_MARKERS));
m_commentPlace = PointFileReaderWriter.readOneToOne(loader.getResourceAsStream(prefix + COMMENT_MARKERS));
m_blockadePlace = PointFileReaderWriter.readOneToOne(loader.getResourceAsStream(prefix + BLOCKADE_MARKERS));
m_capitolPlace = PointFileReaderWriter.readOneToOne(loader.getResourceAsStream(prefix + CAPITAL_MARKERS));
m_PUPlace = PointFileReaderWriter.readOneToOne(loader.getResourceAsStream(prefix + PU_PLACE_FILE));
m_namePlace = PointFileReaderWriter.readOneToOne(loader.getResourceAsStream(prefix + TERRITORY_NAME_PLACE_FILE));
m_kamikazePlace = PointFileReaderWriter.readOneToOne(loader.getResourceAsStream(prefix + KAMIKAZE_FILE));
m_mapProperties = new Properties();
m_decorations = loadDecorations();
m_territoryNameImages = territoryNameImages();
try {
final URL url = loader.getResource(prefix + MAP_PROPERTIES);
if (url == null) {
throw new IllegalStateException("No map.properties file defined");
}
final Optional<InputStream> inputStream = UrlStreams.openStream(url);
if (inputStream.isPresent()) {
m_mapProperties.load(inputStream.get());
}
} catch (final Exception e) {
System.out.println("Error reading map.properties:" + e);
}
initializeContains();
} catch (final IOException ex) {
ClientLogger.logQuietly(ex);
}
}
@Override
public void close() {
m_resourceLoader.close();
}
private Map<String, Image> territoryNameImages() {
if (!m_resourceLoader.hasPath("territoryNames/")) {
return new HashMap<>();
}
final Map<String, Image> territoryNameImages = new HashMap<>();
for (final String name : m_centers.keySet()) {
final Optional<Image> territoryNameImage = loadTerritoryNameImage(name);
if (territoryNameImage.isPresent()) {
territoryNameImages.put(name, territoryNameImage.get());
}
}
return territoryNameImages;
}
private Optional<Image> loadTerritoryNameImage(final String imageName) {
Optional<Image> img;
try {
// try first file names that have underscores instead of spaces
final String normalizedName = imageName.replace(' ', '_');
img = loadImage(constructTerritoryNameImagePath(normalizedName));
if (!img.isPresent()) {
img = loadImage(constructTerritoryNameImagePath(imageName));
}
return img;
} catch (final Exception e) {
// TODO: this is checking for IllegalStateException - we should bubble up the Optional image load and just
// check instead if the optional is empty.
ClientLogger.logQuietly("Image loading failed: " + imageName, e);
return Optional.empty();
}
}
private String constructTerritoryNameImagePath(final String baseName) {
return "territoryNames/" + baseName + ".png";
}
private Map<Image, List<Point>> loadDecorations() {
final URL decorationsFileUrl = m_resourceLoader.getResource(DECORATIONS_FILE);
if (decorationsFileUrl == null) {
return Collections.emptyMap();
}
final Map<Image, List<Point>> decorations = new HashMap<>();
final Optional<InputStream> inputStream = UrlStreams.openStream(decorationsFileUrl);
if (inputStream.isPresent()) {
final Map<String, List<Point>> points = PointFileReaderWriter.readOneToMany(inputStream.get());
for (final String name : points.keySet()) {
final Optional<Image> img = loadImage("misc/" + name);
if (img.isPresent()) {
decorations.put(img.get(), points.get(name));
}
}
}
return decorations;
}
public double getDefaultUnitScale() {
if (m_mapProperties.getProperty(PROPERTY_UNITS_SCALE) == null) {
return 1.0;
}
try {
return Double.parseDouble(m_mapProperties.getProperty(PROPERTY_UNITS_SCALE));
} catch (final NumberFormatException e) {
ClientLogger.logQuietly(e);
return 1.0;
}
}
/**
* Does not take account of any scaling.
*/
public int getDefaultUnitWidth() {
if (m_mapProperties.getProperty(PROPERTY_UNITS_WIDTH) == null) {
return UnitImageFactory.DEFAULT_UNIT_ICON_SIZE;
}
try {
return Integer.parseInt(m_mapProperties.getProperty(PROPERTY_UNITS_WIDTH));
} catch (final NumberFormatException e) {
ClientLogger.logQuietly(e);
return UnitImageFactory.DEFAULT_UNIT_ICON_SIZE;
}
}
/**
* Does not take account of any scaling.
*/
public int getDefaultUnitHeight() {
if (m_mapProperties.getProperty(PROPERTY_UNITS_HEIGHT) == null) {
return UnitImageFactory.DEFAULT_UNIT_ICON_SIZE;
}
try {
return Integer.parseInt(m_mapProperties.getProperty(PROPERTY_UNITS_HEIGHT));
} catch (final NumberFormatException e) {
ClientLogger.logQuietly(e);
return UnitImageFactory.DEFAULT_UNIT_ICON_SIZE;
}
}
/**
* Does not take account of any scaling.
*/
public int getDefaultUnitCounterOffsetWidth() {
// if it is not set, divide by 4 so that it is roughly centered
if (m_mapProperties.getProperty(PROPERTY_UNITS_COUNTER_OFFSET_WIDTH) == null) {
return getDefaultUnitWidth() / 4;
}
try {
return Integer.parseInt(m_mapProperties.getProperty(PROPERTY_UNITS_COUNTER_OFFSET_WIDTH));
} catch (final NumberFormatException e) {
ClientLogger.logQuietly(e);
return getDefaultUnitWidth() / 4;
}
}
/**
* Does not take account of any scaling.
*/
public int getDefaultUnitCounterOffsetHeight() {
// put at bottom of unit, if not set
if (m_mapProperties.getProperty(PROPERTY_UNITS_COUNTER_OFFSET_HEIGHT) == null) {
return getDefaultUnitHeight();
}
try {
return Integer.parseInt(m_mapProperties.getProperty(PROPERTY_UNITS_COUNTER_OFFSET_HEIGHT));
} catch (final NumberFormatException e) {
ClientLogger.logQuietly(e);
return getDefaultUnitHeight();
}
}
public int getDefaultUnitsStackSize() {
// zero = normal behavior
final String stack = m_mapProperties.getProperty(PROPERTY_UNITS_STACK_SIZE, "0");
return Math.max(0, Integer.parseInt(stack));
}
public boolean shouldDrawTerritoryName(final String territoryName) {
if (m_undrawnTerritoriesNames == null) {
final String property = m_mapProperties.getProperty(PROPERTY_DONT_DRAW_TERRITORY_NAMES, "");
m_undrawnTerritoriesNames = new HashSet<>(Arrays.asList(property.split(",")));
}
return !m_undrawnTerritoriesNames.contains(territoryName);
}
public boolean getHasRelief() {
return Boolean.valueOf(m_mapProperties.getProperty(PROPERTY_MAP_HASRELIEF, "true"));
}
public int getMapCursorHotspotX() {
return Math.max(0,
Math.min(256, Integer.parseInt(m_mapProperties.getProperty(PROPERTY_MAP_CURSOR_HOTSPOT_X, "0"))));
}
public int getMapCursorHotspotY() {
return Math.max(0,
Math.min(256, Integer.parseInt(m_mapProperties.getProperty(PROPERTY_MAP_CURSOR_HOTSPOT_Y, "0"))));
}
public boolean getHasMapBlends() {
return Boolean.valueOf(m_mapProperties.getProperty(PROPERTY_MAP_MAPBLENDS, "false"));
}
public String getMapBlendMode() {
return String.valueOf(m_mapProperties.getProperty(PROPERTY_MAP_MAPBLENDMODE, "normal"));
}
public float getMapBlendAlpha() {
return Float.valueOf(m_mapProperties.getProperty(PROPERTY_MAP_MAPBLENDALPHA, "0.5f"));
}
public boolean drawCapitolMarkers() {
return Boolean.valueOf(m_mapProperties.getProperty(PROPERTY_MAP_SHOWCAPITOLMARKERS, "true"));
}
public boolean drawTerritoryNames() {
return Boolean.valueOf(m_mapProperties.getProperty(PROPERTY_MAP_SHOWTERRITORYNAMES, "true"));
}
public boolean drawResources() {
return Boolean.valueOf(m_mapProperties.getProperty(PROPERTY_MAP_SHOWRESOURCES, "true"));
}
public boolean drawComments() {
return Boolean.valueOf(m_mapProperties.getProperty(PROPERTY_MAP_SHOWCOMMENTS, "true"));
}
public boolean drawSeaZoneNames() {
return Boolean.valueOf(m_mapProperties.getProperty(PROPERTY_MAP_SHOWSEAZONENAMES, "false"));
}
public boolean drawNamesFromTopLeft() {
return Boolean.valueOf(m_mapProperties.getProperty(PROPERTY_MAP_DRAWNAMESFROMTOPLEFT, "false"));
}
public boolean useNation_convoyFlags() {
return Boolean.valueOf(m_mapProperties.getProperty(PROPERTY_MAP_USENATION_CONVOYFLAGS, "false"));
}
public boolean useTerritoryEffectMarkers() {
return Boolean.valueOf(m_mapProperties.getProperty(PROPERTY_MAP_USETERRITORYEFFECTMARKERS, "false"));
}
private void initializeContains() {
m_contains = new HashMap<>();
final Iterator<String> seaIter = getTerritories().iterator();
while (seaIter.hasNext()) {
final List<String> contained = new ArrayList<>();
final String seaTerritory = seaIter.next();
if (!Util.isTerritoryNameIndicatingWater(seaTerritory)) {
continue;
}
final Iterator<String> landIter = getTerritories().iterator();
while (landIter.hasNext()) {
final String landTerritory = landIter.next();
if (Util.isTerritoryNameIndicatingWater(landTerritory)) {
continue;
}
final Polygon landPoly = getPolygons(landTerritory).iterator().next();
final Polygon seaPoly = getPolygons(seaTerritory).iterator().next();
if (seaPoly.contains(landPoly.getBounds())) {
contained.add(landTerritory);
}
}
if (!contained.isEmpty()) {
m_contains.put(seaTerritory, contained);
}
}
}
public boolean getBooleanProperty(final String propertiesKey) {
return Boolean.valueOf(m_mapProperties.getProperty(propertiesKey, "true"));
}
public Color getColorProperty(final String propertiesKey) throws IllegalStateException {
String colorString;
if (m_mapProperties.getProperty(propertiesKey) != null) {
colorString = m_mapProperties.getProperty(propertiesKey);
if (colorString.length() != 6) {
throw new IllegalStateException("Colors must be a 6 digit hex number, eg FF0011, not:" + colorString);
}
try {
return new Color(Integer.decode("0x" + colorString));
} catch (final NumberFormatException nfe) {
throw new IllegalStateException("Player colors must be a 6 digit hex number, eg FF0011");
}
}
return null;
}
public Color getPlayerColor(final String playerName) {
// already loaded, just return
if (m_playerColors.containsKey(playerName)) {
return m_playerColors.get(playerName);
}
// look in map.properties
final String propertiesKey = PROPERTY_COLOR_PREFIX + playerName;
Color color = null;
try {
color = getColorProperty(propertiesKey);
} catch (final Exception e) {
throw new IllegalStateException("Player colors must be a 6 digit hex number, eg FF0011");
}
if (color == null) {
System.out.println("No color defined for " + playerName + ". Edit map.properties in the map folder to set it");
color = m_defaultColours.remove(0);
}
// dont crash, use one of our default colors
// its ugly, but usable
m_playerColors.put(playerName, color);
return color;
}
/**
* returns the named property, or null.
*/
public String getProperty(final String propertiesKey) {
return m_mapProperties.getProperty(propertiesKey);
}
/**
* returns the color for impassable territories.
*/
public Color impassableColor() {
// just use getPlayerColor, since it parses the properties
return getPlayerColor(Constants.PLAYER_NAME_IMPASSABLE);
}
/**
* @return a Set of territory names as Strings. generally this shouldnt be
* used, instead you should use aGameData.getMap().getTerritories()
*/
public Set<String> getTerritories() {
return m_polys.keySet();
}
/**
* Does this territory have any territories contained within it.
*/
public boolean hasContainedTerritory(final String territoryName) {
return m_contains.containsKey(territoryName);
}
/**
* returns the name of the territory contained in the given territory. This
* applies to islands within sea zones.
*
* @return possiblly null
*/
public List<String> getContainedTerritory(final String territoryName) {
return m_contains.get(territoryName);
}
public void verify(final GameData data) {
verifyKeys(data, m_centers, "centers");
verifyKeys(data, m_polys, "polygons");
verifyKeys(data, m_place, "place");
}
private void verifyKeys(final GameData data, final Map<String, ?> aMap, final String dataTypeForErrorMessage)
throws IllegalStateException {
final StringBuilder errors = new StringBuilder();
final Iterator<String> iter = aMap.keySet().iterator();
while (iter.hasNext()) {
final String name = iter.next();
final Territory terr = data.getMap().getTerritory(name);
// allow loading saved games with missing territories; just ignore them
if (terr == null) {
iter.remove();
}
}
final Iterator<Territory> territories = data.getMap().getTerritories().iterator();
final Set<String> keySet = aMap.keySet();
while (territories.hasNext()) {
final Territory terr = territories.next();
if (!keySet.contains(terr.getName())) {
errors.append("No data of type ").append(dataTypeForErrorMessage).append(" for territory:")
.append(terr.getName()).append("\n");
}
}
if (errors.length() > 0) {
throw new IllegalStateException(errors.toString());
}
}
public List<Point> getPlacementPoints(final Territory terr) {
return m_place.get(terr.getName());
}
public List<Polygon> getPolygons(final String terr) {
return m_polys.get(terr);
}
public List<Polygon> getPolygons(final Territory terr) {
return getPolygons(terr.getName());
}
public Point getCenter(final String terr) {
if (m_centers.get(terr) == null) {
throw new IllegalStateException("Missing " + CENTERS_FILE + " data for " + terr);
}
return new Point(m_centers.get(terr));
}
public Point getCenter(final Territory terr) {
return getCenter(terr.getName());
}
public Point getCapitolMarkerLocation(final Territory terr) {
if (m_capitolPlace.containsKey(terr.getName())) {
return m_capitolPlace.get(terr.getName());
}
return getCenter(terr);
}
public Point getConvoyMarkerLocation(final Territory terr) {
if (m_convoyPlace.containsKey(terr.getName())) {
return m_convoyPlace.get(terr.getName());
}
return getCenter(terr);
}
public Optional<Point> getCommentMarkerLocation(final Territory terr) {
return Optional.ofNullable(m_commentPlace.get(terr.getName()));
}
public Point getKamikazeMarkerLocation(final Territory terr) {
if (m_kamikazePlace.containsKey(terr.getName())) {
return m_kamikazePlace.get(terr.getName());
}
return getCenter(terr);
}
public Point getVCPlacementPoint(final Territory terr) {
if (m_vcPlace.containsKey(terr.getName())) {
return m_vcPlace.get(terr.getName());
}
return getCenter(terr);
}
public Point getBlockadePlacementPoint(final Territory terr) {
if (m_blockadePlace.containsKey(terr.getName())) {
return m_blockadePlace.get(terr.getName());
}
return getCenter(terr);
}
public Optional<Point> getPUPlacementPoint(final Territory terr) {
return Optional.ofNullable(m_PUPlace.get(terr.getName()));
}
public Optional<Point> getNamePlacementPoint(final Territory terr) {
return Optional.ofNullable(m_namePlace.get(terr.getName()));
}
/**
* Get the territory at the x,y co-ordinates could be null.
*/
public String getTerritoryAt(final double x, final double y) {
String seaName = null;
// try to find a land territory.
// sea zones often surround a land territory
final Iterator<String> keyIter = m_polys.keySet().iterator();
while (keyIter.hasNext()) {
final String name = keyIter.next();
final Collection<Polygon> polygons = m_polys.get(name);
final Iterator<Polygon> polyIter = polygons.iterator();
while (polyIter.hasNext()) {
final Polygon poly = polyIter.next();
if (poly.contains(x, y)) {
if (Util.isTerritoryNameIndicatingWater(name)) {
seaName = name;
} else {
return name;
}
}
}
}
return seaName;
}
public Dimension getMapDimensions() {
final String widthProperty = m_mapProperties.getProperty(PROPERTY_MAP_WIDTH);
final String heightProperty = m_mapProperties.getProperty(PROPERTY_MAP_HEIGHT);
if (widthProperty == null || heightProperty == null) {
throw new IllegalStateException(
"Missing " + PROPERTY_MAP_WIDTH + " or " + PROPERTY_MAP_HEIGHT + " in " + MAP_PROPERTIES);
}
final int width = Integer.parseInt(widthProperty.trim());
final int height = Integer.parseInt(heightProperty.trim());
return new Dimension(width, height);
}
public Rectangle getBoundingRect(final Territory terr) {
final String name = terr.getName();
return getBoundingRect(name);
}
public Rectangle getBoundingRect(final String name) {
final List<Polygon> polys = m_polys.get(name);
if (polys == null) {
throw new IllegalStateException("No polygons found for:" + name + " All territories:" + m_polys.keySet());
}
final Iterator<Polygon> polyIter = polys.iterator();
final Rectangle bounds = polyIter.next().getBounds();
while (polyIter.hasNext()) {
bounds.add(polyIter.next().getBounds());
}
// if we have a territory that straddles the map divide, ie: which has polygons on both the left and right sides of
// the map,
// then the polygon's width or height could be almost equal to the map width or height
// this can cause lots of problems, like when we want to get the tiles for the territory we would end up getting all
// the tiles for the
// map (and a java heap space error)
final Dimension mapDimensions = getMapDimensions();
if ((scrollWrapX() && bounds.width > 1800 && bounds.width > mapDimensions.width * 0.9)
|| (scrollWrapY() && bounds.height > 1200 && bounds.height > mapDimensions.height * 0.9)) {
return getBoundingRectWithTranslate(polys, mapDimensions);
}
return bounds;
}
private Rectangle getBoundingRectWithTranslate(final List<Polygon> polys, final Dimension mapDimensions) {
Rectangle boundingRect = null;
final int mapWidth = mapDimensions.width;
final int mapHeight = mapDimensions.height;
final int closeToMapWidth = (int) (mapWidth * 0.9);
final int closeToMapHeight = (int) (mapHeight * 0.9);
final boolean scrollWrapX = this.scrollWrapX();
final boolean scrollWrapY = this.scrollWrapY();
for (final Polygon item : polys) {
// if our rectangle is on the right side (mapscrollx) then we push it to be on the negative left side, so that the
// bounds.x will be
// negative
// this solves the issue of maps that have a territory where polygons were on both sides of the map divide
// (so our bounds.x was 0, and our bounds.y would be the entire map width)
// (when in fact it should actually be bounds.x = -10 or something, and bounds.width = 20 or something)
// we use map dimensions.width * 0.9 because the polygon may not actually touch the side of the map (like if the
// territory borders are
// thick)
final Rectangle itemRect = item.getBounds();
if (scrollWrapX && itemRect.getMaxX() >= closeToMapWidth) {
itemRect.translate(-mapWidth, 0);
}
if (scrollWrapY && itemRect.getMaxY() >= closeToMapHeight) {
itemRect.translate(0, -mapHeight);
}
if (boundingRect == null) {
boundingRect = itemRect;
} else {
boundingRect.add(itemRect);
}
}
// if the polygon is completely negative, we can make translate it back to normal
if (boundingRect.x < 0 && boundingRect.getMaxX() <= 0) {
boundingRect.translate(mapWidth, 0);
}
if (boundingRect.y < 0 && boundingRect.getMaxY() <= 0) {
boundingRect.translate(0, mapHeight);
}
return boundingRect;
}
public Optional<Image> getVCImage() {
return loadImage("misc/vc.png");
}
public Optional<Image> getBlockadeImage() {
return loadImage("misc/blockade.png");
}
public Optional<Image> getErrorImage() {
return loadImage("misc/error.gif");
}
public Optional<Image> getWarningImage() {
return loadImage("misc/warning.gif");
}
public Optional<Image> getInfoImage() {
return loadImage("misc/information.gif");
}
public Optional<Image> getHelpImage() {
return loadImage("misc/help.gif");
}
private Optional<Image> loadImage(final String imageName) {
final URL url = m_resourceLoader.getResource(imageName);
if (url == null) {
// this is actually pretty common that we try to read images that are not there. Let the caller
// decide if this is an error or not.
return Optional.empty();
}
try {
return Optional.of(ImageIO.read(url));
} catch (final IOException e) {
ClientLogger.logQuietly(e);
throw new IllegalStateException(e.getMessage());
}
}
public Map<String, Image> getTerritoryNameImages() {
return Collections.unmodifiableMap(m_territoryNameImages);
}
public Map<Image, List<Point>> getDecorations() {
return Collections.unmodifiableMap(m_decorations);
}
public List<Point> getTerritoryEffectPoints(final Territory territory) {
if (m_territoryEffects.get(territory.getName()) == null) {
return Arrays.asList(getCenter(territory));
}
return m_territoryEffects.get(territory.getName());
}
public Optional<Image> getTerritoryEffectImage(final String m_effectName) {
// TODO: what does this cache buy us? should we still keep it?
if (m_effectImages.get(m_effectName) != null) {
return Optional.of(m_effectImages.get(m_effectName));
}
final Optional<Image> effectImage = loadImage("territoryEffects/" + m_effectName + ".png");
m_effectImages.put(m_effectName, effectImage.orElse(null));
return effectImage;
}
}