package games.strategy.triplea.ui.screen;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
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.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;
import games.strategy.engine.data.GameData;
import games.strategy.engine.data.PlayerID;
import games.strategy.engine.data.Territory;
import games.strategy.engine.data.TerritoryEffect;
import games.strategy.engine.data.Unit;
import games.strategy.triplea.attachments.TerritoryAttachment;
import games.strategy.triplea.delegate.TerritoryEffectHelper;
import games.strategy.triplea.ui.IUIContext;
import games.strategy.triplea.ui.mapdata.MapData;
import games.strategy.triplea.ui.screen.TerritoryOverLayDrawable.OP;
import games.strategy.triplea.ui.screen.drawable.BaseMapDrawable;
import games.strategy.triplea.ui.screen.drawable.BattleDrawable;
import games.strategy.triplea.ui.screen.drawable.BlockadeZoneDrawable;
import games.strategy.triplea.ui.screen.drawable.CapitolMarkerDrawable;
import games.strategy.triplea.ui.screen.drawable.ConvoyZoneDrawable;
import games.strategy.triplea.ui.screen.drawable.DecoratorDrawable;
import games.strategy.triplea.ui.screen.drawable.DrawableComparator;
import games.strategy.triplea.ui.screen.drawable.IDrawable;
import games.strategy.triplea.ui.screen.drawable.IDrawable.OptionalExtraBorderLevel;
import games.strategy.triplea.ui.screen.drawable.KamikazeZoneDrawable;
import games.strategy.triplea.ui.screen.drawable.LandTerritoryDrawable;
import games.strategy.triplea.ui.screen.drawable.MapTileDrawable;
import games.strategy.triplea.ui.screen.drawable.OptionalExtraTerritoryBordersDrawable;
import games.strategy.triplea.ui.screen.drawable.ReliefMapDrawable;
import games.strategy.triplea.ui.screen.drawable.SeaZoneOutlineDrawable;
import games.strategy.triplea.ui.screen.drawable.TerritoryEffectDrawable;
import games.strategy.triplea.ui.screen.drawable.TerritoryNameDrawable;
import games.strategy.triplea.ui.screen.drawable.VCDrawable;
import games.strategy.triplea.util.UnitCategory;
import games.strategy.triplea.util.UnitSeperator;
import games.strategy.ui.Util;
import games.strategy.util.Tuple;
public class TileManager {
private static final Logger s_logger = Logger.getLogger(TileManager.class.getName());
public static final int TILE_SIZE = 256;
private List<Tile> m_tiles = new ArrayList<>();
private final Lock m_lock = new ReentrantLock();
private final Map<String, IDrawable> m_territoryOverlays = new HashMap<>();
// maps territoryname - collection of drawables
private final Map<String, Set<IDrawable>> m_territoryDrawables = new HashMap<>();
// maps territoryname - collection of tiles where the territory is drawn
private final Map<String, Set<Tile>> m_territoryTiles = new HashMap<>();
private final Collection<UnitsDrawer> m_allUnitDrawables = new ArrayList<>();
private final IUIContext m_uiContext;
public TileManager(final IUIContext uiContext) {
m_uiContext = uiContext;
}
/**
* Selects tiles which fall into rectangle bounds.
*
* @param bounds
* rectangle for selection
* @return tiles which fall into the rectangle
*/
public List<Tile> getTiles(final Rectangle2D bounds) {
// if the rectangle exceeds the map dimensions we to do shift the rectangle and check for each shifted rectangle as
// well as the original
// rectangle
final MapData mapData = m_uiContext.getMapData();
final Dimension mapDimensions = mapData.getMapDimensions();
final boolean testXshift =
(mapData.scrollWrapX() && (bounds.getMaxX() > mapDimensions.width || bounds.getMinX() < 0));
final boolean testYshift =
(mapData.scrollWrapY() && (bounds.getMaxY() > mapDimensions.height || bounds.getMinY() < 0));
Rectangle2D boundsXshift = null;
if (testXshift) {
if (bounds.getMinX() < 0) {
boundsXshift = new Rectangle((int) bounds.getMinX() + mapDimensions.width, (int) bounds.getMinY(),
(int) bounds.getWidth(), (int) bounds.getHeight());
} else {
boundsXshift = new Rectangle((int) bounds.getMinX() - mapDimensions.width, (int) bounds.getMinY(),
(int) bounds.getWidth(), (int) bounds.getHeight());
}
}
Rectangle2D boundsYshift = null;
if (testYshift) {
if (bounds.getMinY() < 0) {
boundsYshift = new Rectangle((int) bounds.getMinX(), (int) bounds.getMinY() + mapDimensions.height,
(int) bounds.getWidth(), (int) bounds.getHeight());
} else {
boundsYshift = new Rectangle((int) bounds.getMinX(), (int) bounds.getMinY() - mapDimensions.height,
(int) bounds.getWidth(), (int) bounds.getHeight());
}
}
acquireLock();
try {
final List<Tile> rVal = new ArrayList<>();
for (final Tile tile : m_tiles) {
final Rectangle tileBounds = tile.getBounds();
if (bounds.contains(tileBounds) || tileBounds.intersects(bounds)) {
rVal.add(tile);
}
}
if (boundsXshift != null) {
for (final Tile tile : m_tiles) {
final Rectangle tileBounds = tile.getBounds();
if (boundsXshift.contains(tileBounds) || tileBounds.intersects(boundsXshift)) {
rVal.add(tile);
}
}
}
if (boundsYshift != null) {
for (final Tile tile : m_tiles) {
final Rectangle tileBounds = tile.getBounds();
if (boundsYshift.contains(tileBounds) || tileBounds.intersects(boundsYshift)) {
rVal.add(tile);
}
}
}
return rVal;
} finally {
releaseLock();
}
}
private void acquireLock() {
Tile.S_TILE_LOCKUTIL.acquireLock(m_lock);
}
private void releaseLock() {
Tile.S_TILE_LOCKUTIL.releaseLock(m_lock);
}
public Collection<UnitsDrawer> getUnitDrawables() {
acquireLock();
try {
return new ArrayList<>(m_allUnitDrawables);
} finally {
releaseLock();
}
}
public void createTiles(final Rectangle bounds, final GameData data, final MapData mapData) {
acquireLock();
try {
// create our tiles
m_tiles = new ArrayList<>();
for (int x = 0; (x) * TILE_SIZE < bounds.width; x++) {
for (int y = 0; (y) * TILE_SIZE < bounds.height; y++) {
m_tiles.add(new Tile(new Rectangle(x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE), x, y,
m_uiContext.getScale()));
}
}
} finally {
releaseLock();
}
}
public void resetTiles(final GameData data, final MapData mapData) {
data.acquireReadLock();
try {
acquireLock();
try {
final Iterator<Tile> allTiles = m_tiles.iterator();
while (allTiles.hasNext()) {
final Tile tile = allTiles.next();
tile.clear();
final int x = tile.getBounds().x / TILE_SIZE;
final int y = tile.getBounds().y / TILE_SIZE;
tile.addDrawable(new BaseMapDrawable(x, y, m_uiContext));
tile.addDrawable(new ReliefMapDrawable(x, y, m_uiContext));
}
final Iterator<Territory> territories = data.getMap().getTerritories().iterator();
while (territories.hasNext()) {
final Territory territory = territories.next();
clearTerritory(territory);
drawTerritory(territory, data, mapData);
}
// add the decorations
final Map<Image, List<Point>> decorations = mapData.getDecorations();
for (final Image img : decorations.keySet()) {
for (final Point p : decorations.get(img)) {
final DecoratorDrawable drawable = new DecoratorDrawable(p, img);
final Rectangle bounds = new Rectangle(p.x, p.y, img.getWidth(null), img.getHeight(null));
for (final Tile t : getTiles(bounds)) {
t.addDrawable(drawable);
}
}
}
} finally {
releaseLock();
}
} finally {
data.releaseReadLock();
}
}
public void updateTerritories(final Collection<Territory> territories, final GameData data, final MapData mapData) {
data.acquireReadLock();
try {
acquireLock();
try {
if (territories == null) {
return;
}
final Iterator<Territory> iter = territories.iterator();
while (iter.hasNext()) {
final Territory territory = iter.next();
updateTerritory(territory, data, mapData);
}
} finally {
releaseLock();
}
} finally {
data.releaseReadLock();
}
}
public void updateTerritory(final Territory territory, final GameData data, final MapData mapData) {
data.acquireReadLock();
try {
acquireLock();
try {
s_logger.log(Level.FINER, "Updating " + territory.getName());
clearTerritory(territory);
drawTerritory(territory, data, mapData);
} finally {
releaseLock();
}
} finally {
data.releaseReadLock();
}
}
private void clearTerritory(final Territory territory) {
if (m_territoryTiles.get(territory.getName()) == null) {
return;
}
final Collection<IDrawable> drawables = m_territoryDrawables.get(territory.getName());
if (drawables == null || drawables.isEmpty()) {
return;
}
final Iterator<Tile> tiles = m_territoryTiles.get(territory.getName()).iterator();
while (tiles.hasNext()) {
final Tile tile = tiles.next();
tile.removeDrawables(drawables);
}
m_allUnitDrawables.removeAll(drawables);
}
private void drawTerritory(final Territory territory, final GameData data, final MapData mapData) {
final Set<Tile> drawnOn = new HashSet<>();
final Set<IDrawable> drawing = new HashSet<>();
if (m_territoryOverlays.get(territory.getName()) != null) {
drawing.add(m_territoryOverlays.get(territory.getName()));
}
if (m_uiContext.getShowTerritoryEffects()) {
drawTerritoryEffects(territory, mapData, drawing);
}
if (m_uiContext.getShowUnits()) {
drawUnits(territory, mapData, drawnOn, drawing);
}
drawing.add(new BattleDrawable(territory.getName()));
final TerritoryAttachment ta = TerritoryAttachment.get(territory);
if (!territory.isWater()) {
drawing.add(new LandTerritoryDrawable(territory.getName()));
} else {
if (ta != null) {
// Kamikaze Zones
if (ta.getKamikazeZone()) {
drawing.add(new KamikazeZoneDrawable(territory, m_uiContext));
}
// Blockades
if (ta.getBlockadeZone()) {
drawing.add(new BlockadeZoneDrawable(territory, m_uiContext));
}
// Convoy Routes
if (ta.getConvoyRoute()) {
drawing.add(new ConvoyZoneDrawable(territory.getOwner(), territory, m_uiContext));
}
// Convoy Centers
if (ta.getProduction() > 0) {
drawing.add(new ConvoyZoneDrawable(territory.getOwner(), territory, m_uiContext));
}
}
drawing.add(new SeaZoneOutlineDrawable(territory.getName()));
}
final OptionalExtraBorderLevel optionalBorderLevel = m_uiContext.getDrawTerritoryBordersAgain();
if (optionalBorderLevel != OptionalExtraBorderLevel.LOW) {
drawing.add(new OptionalExtraTerritoryBordersDrawable(territory.getName(), optionalBorderLevel));
}
drawing.add(new TerritoryNameDrawable(territory.getName(), m_uiContext));
if (ta != null && ta.isCapital() && mapData.drawCapitolMarkers()) {
final PlayerID capitalOf = data.getPlayerList().getPlayerID(ta.getCapital());
drawing.add(new CapitolMarkerDrawable(capitalOf, territory, m_uiContext));
}
if (ta != null && (ta.getVictoryCity() != 0)) {
drawing.add(new VCDrawable(territory));
}
// add to the relevant tiles
final Iterator<Tile> tiles = getTiles(mapData.getBoundingRect(territory.getName())).iterator();
while (tiles.hasNext()) {
final Tile tile = tiles.next();
drawnOn.add(tile);
tile.addDrawables(drawing);
}
m_territoryDrawables.put(territory.getName(), drawing);
m_territoryTiles.put(territory.getName(), drawnOn);
}
private void drawTerritoryEffects(final Territory territory, final MapData mapData, final Set<IDrawable> drawing) {
final Iterator<Point> effectPoints = mapData.getTerritoryEffectPoints(territory).iterator();
Point drawingPoint = effectPoints.next();
for (final TerritoryEffect te : TerritoryEffectHelper.getEffects(territory)) {
drawing.add(new TerritoryEffectDrawable(te, drawingPoint));
drawingPoint = effectPoints.hasNext() ? effectPoints.next() : drawingPoint;
}
}
private void drawUnits(final Territory territory, final MapData mapData, final Set<Tile> drawnOn,
final Set<IDrawable> drawing) {
final Iterator<Point> placementPoints = mapData.getPlacementPoints(territory).iterator();
if (placementPoints == null || !placementPoints.hasNext()) {
throw new IllegalStateException("No where to place units:" + territory.getName());
}
Point lastPlace = null;
final Iterator<UnitCategory> unitCategoryIter =
UnitSeperator.categorize(territory.getUnits().getUnits()).iterator();
while (unitCategoryIter.hasNext()) {
final UnitCategory category = unitCategoryIter.next();
boolean overflow;
if (placementPoints.hasNext()) {
lastPlace = new Point(placementPoints.next());
overflow = false;
} else {
lastPlace = new Point(lastPlace);
lastPlace.x += m_uiContext.getUnitImageFactory().getUnitImageWidth();
overflow = true;
}
final UnitsDrawer drawable = new UnitsDrawer(category.getUnits().size(), category.getType().getName(),
category.getOwner().getName(), lastPlace, category.getDamaged(), category.getBombingDamage(),
category.getDisabled(), overflow, territory.getName(), m_uiContext);
drawing.add(drawable);
m_allUnitDrawables.add(drawable);
final Iterator<Tile> tiles =
getTiles(new Rectangle(lastPlace.x, lastPlace.y, m_uiContext.getUnitImageFactory().getUnitImageWidth(),
m_uiContext.getUnitImageFactory().getUnitImageHeight())).iterator();
while (tiles.hasNext()) {
final Tile tile = tiles.next();
tile.addDrawable(drawable);
drawnOn.add(tile);
}
}
}
public Image createTerritoryImage(final Territory t, final GameData data, final MapData mapData) {
return createTerritoryImage(t, t, data, mapData, true);
}
public Image createTerritoryImage(final Territory selected, final Territory focusOn, final GameData data,
final MapData mapData) {
return createTerritoryImage(selected, focusOn, data, mapData, false);
}
private Image createTerritoryImage(final Territory selected, final Territory focusOn, final GameData data,
final MapData mapData, final boolean drawOutline) {
acquireLock();
try {
// make a square
final Rectangle bounds = mapData.getBoundingRect(focusOn);
int square_length = Math.max(bounds.width, bounds.height);
final int grow = square_length / 4;
bounds.x -= grow;
bounds.y -= grow;
square_length += grow * 2;
// make sure it is not bigger than the whole map
final int mapDataWidth = mapData.getMapDimensions().width;
final int mapDataHeight = mapData.getMapDimensions().height;
if (square_length > mapDataWidth) {
square_length = mapDataWidth;
}
if (square_length > mapDataHeight) {
square_length = mapDataHeight;
}
bounds.width = square_length;
bounds.height = square_length;
// keep it in bounds
if (!mapData.scrollWrapX()) {
if (bounds.x < 0) {
bounds.x = 0;
}
if (bounds.width + bounds.x > mapDataWidth) {
bounds.x = mapDataWidth - bounds.width;
}
}
if (!mapData.scrollWrapY()) {
if (bounds.y < 0) {
bounds.y = 0;
}
if (bounds.height + bounds.y > mapDataHeight) {
bounds.y = mapDataHeight - bounds.height;
}
}
final Image rVal = Util.createImage(square_length, square_length, false);
final Graphics2D graphics = (Graphics2D) rVal.getGraphics();
graphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
graphics.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION,
RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
if (bounds.x < 0) {
bounds.x += mapDataWidth;
drawForCreate(selected, data, mapData, bounds, graphics, drawOutline);
if (bounds.y < 0) {
bounds.y += mapDataHeight;
drawForCreate(selected, data, mapData, bounds, graphics, drawOutline);
bounds.y -= mapDataHeight;
}
bounds.x -= mapDataWidth;
}
if (bounds.y < 0) {
bounds.y += mapDataHeight;
drawForCreate(selected, data, mapData, bounds, graphics, drawOutline);
bounds.y -= mapDataHeight;
}
// start as a set to prevent duplicates
drawForCreate(selected, data, mapData, bounds, graphics, drawOutline);
if (bounds.x + bounds.height > mapDataWidth) {
bounds.x -= mapDataWidth;
drawForCreate(selected, data, mapData, bounds, graphics, drawOutline);
if (bounds.y + bounds.width > mapDataHeight) {
bounds.y -= mapDataHeight;
drawForCreate(selected, data, mapData, bounds, graphics, drawOutline);
bounds.y += mapDataHeight;
}
bounds.x += mapDataWidth;
}
if (bounds.y + bounds.width > mapDataHeight) {
bounds.y -= mapDataHeight;
drawForCreate(selected, data, mapData, bounds, graphics, drawOutline);
bounds.y += mapDataHeight;
}
graphics.dispose();
return rVal;
} finally {
releaseLock();
}
}
private void drawForCreate(final Territory selected, final GameData data, final MapData mapData,
final Rectangle bounds, final Graphics2D graphics, final boolean drawOutline) {
final Set<IDrawable> drawablesSet = new HashSet<>();
final List<Tile> intersectingTiles = getTiles(bounds);
for (final Tile tile : intersectingTiles) {
drawablesSet.addAll(tile.getDrawables());
}
// the base tiles are scaled to save memory
// but we want to draw them unscaled here
// so unscale them
if (m_uiContext.getScale() != 1) {
final List<IDrawable> toAdd = new ArrayList<>();
final Iterator<IDrawable> iter = drawablesSet.iterator();
while (iter.hasNext()) {
final IDrawable drawable = iter.next();
if (drawable instanceof MapTileDrawable) {
iter.remove();
toAdd.add(((MapTileDrawable) drawable).getUnscaledCopy());
}
}
drawablesSet.addAll(toAdd);
}
final List<IDrawable> orderedDrawables = new ArrayList<>(drawablesSet);
Collections.sort(orderedDrawables, new DrawableComparator());
for (final IDrawable drawer : orderedDrawables) {
if (drawer.getLevel() >= IDrawable.UNITS_LEVEL) {
break;
}
if (drawer.getLevel() == IDrawable.TERRITORY_TEXT_LEVEL) {
continue;
}
drawer.draw(bounds, data, graphics, mapData, null, null);
}
if (!drawOutline) {
Color c;
if (selected.isWater()) {
c = Color.RED;
} else {
c = new Color(0, 0, 0);
}
final TerritoryOverLayDrawable told = new TerritoryOverLayDrawable(c, selected.getName(), 100, OP.FILL);
told.draw(bounds, data, graphics, mapData, null, null);
}
graphics.setStroke(new BasicStroke(10));
graphics.setColor(Color.RED);
for (Polygon poly : mapData.getPolygons(selected)) {
poly = new Polygon(poly.xpoints, poly.ypoints, poly.npoints);
poly.translate(-bounds.x, -bounds.y);
graphics.drawPolygon(poly);
}
}
public Rectangle getUnitRect(final List<Unit> units, final GameData data) {
if (units == null) {
return null;
}
data.acquireReadLock();
try {
acquireLock();
try {
for (final UnitsDrawer drawer : m_allUnitDrawables) {
final List<Unit> drawerUnits = drawer.getUnits(data).getSecond();
if (!drawerUnits.isEmpty() && units.containsAll(drawerUnits)) {
final Point placementPoint = drawer.getPlacementPoint();
return new Rectangle(placementPoint.x, placementPoint.y,
m_uiContext.getUnitImageFactory().getUnitImageWidth(),
m_uiContext.getUnitImageFactory().getUnitImageHeight());
}
}
return null;
} finally {
releaseLock();
}
} finally {
data.releaseReadLock();
}
}
public Tuple<Territory, List<Unit>> getUnitsAtPoint(final double x, final double y, final GameData gameData) {
gameData.acquireReadLock();
try {
acquireLock();
try {
for (final UnitsDrawer drawer : m_allUnitDrawables) {
final Point placementPoint = drawer.getPlacementPoint();
if (x > placementPoint.x && x < placementPoint.x + m_uiContext.getUnitImageFactory().getUnitImageWidth()) {
if (y > placementPoint.y && y < placementPoint.y + m_uiContext.getUnitImageFactory().getUnitImageHeight()) {
return drawer.getUnits(gameData);
}
}
}
return null;
} finally {
releaseLock();
}
} finally {
gameData.releaseReadLock();
}
}
public void setTerritoryOverlay(final Territory territory, final Color color, final int alpha, final GameData data,
final MapData mapData) {
acquireLock();
try {
final IDrawable drawable = new TerritoryOverLayDrawable(color, territory.getName(), alpha, OP.DRAW);
m_territoryOverlays.put(territory.getName(), drawable);
} finally {
releaseLock();
}
updateTerritory(territory, data, mapData);
}
public void setTerritoryOverlayForBorder(final Territory territory, final Color color, final GameData data,
final MapData mapData) {
acquireLock();
try {
final IDrawable drawable = new TerritoryOverLayDrawable(color, territory.getName(), OP.DRAW);
m_territoryOverlays.put(territory.getName(), drawable);
} finally {
releaseLock();
}
updateTerritory(territory, data, mapData);
}
public void clearTerritoryOverlay(final Territory territory, final GameData data, final MapData mapData) {
acquireLock();
try {
m_territoryOverlays.remove(territory.getName());
} finally {
releaseLock();
}
updateTerritory(territory, data, mapData);
}
}