package net.sf.colossus.gui; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Point; import java.awt.RenderingHints; import java.awt.Shape; import java.awt.Stroke; import java.awt.geom.AffineTransform; import java.awt.geom.Arc2D; import java.awt.geom.GeneralPath; import java.awt.geom.Line2D; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import net.sf.colossus.server.VariantSupport; import net.sf.colossus.util.HTMLColor; import net.sf.colossus.util.StaticResourceLoader; import net.sf.colossus.variant.BattleHex; import net.sf.colossus.variant.HazardHexside; /** * Class GUIBattleHex holds GUI info for one battle hex. * * @author David Ripton * @author Romain Dolbeau */ public class GUIBattleHex extends GUIHex<BattleHex> { private static final Logger LOGGER = Logger.getLogger(GUIBattleHex.class .getName()); private Component map; private static final Color highlightColor = Color.red; /** * Stores the neighboring views. * * This parallels the neighbors field in BattleHex, just on the view side. * * TODO check if we can avoid this */ private final GUIBattleHex[] neighbors = new GUIBattleHex[6]; private int scale; private int cx; /* x location of upper left Vertex */ private int cy; /* Y location of upper left Vertex */ // Hex terrain types are: // p, r, s, t, o, v, d, w, g, a // plain, bramble, sand, tree, bog, volcano, drift, tower, spring, tarpit // also // l // lake // Hexside terrain types are: // d, c, s, w, space // dune, cliff, slope, wall, no obstacle // The hexside is marked only in the higher hex. // Hex labels are: // A1-A3, B1-B4, C1-C5, D1-D6, E1-E5, F1-F4. // Letters increase left to right; numbers increase bottom to top. public GUIBattleHex(int cx, int cy, int scale, Component map, int xCoord, int yCoord) { super(new BattleHex(xCoord, yCoord)); this.cx = cx; this.cy = cy; this.map = map; this.scale = scale; makeHexagon(); } /* constructs a dummy with Battle map Coords */ public GUIBattleHex(int xCoord, int yCoord) { super(new BattleHex(xCoord, yCoord)); } public void setVertexZeroLocation(int cx, int cy) { this.cx = cx; this.cy = cy; makeHexagon(); } public void setWidth(int width) { scale = width / 4; makeHexagon(); } private void makeHexagon() { len = scale / 3.0; xVertex[0] = cx; yVertex[0] = cy; xVertex[1] = cx + 2 * scale; yVertex[1] = cy; xVertex[2] = cx + 3 * scale; yVertex[2] = cy + SQRT3 * scale; xVertex[3] = cx + 2 * scale; yVertex[3] = cy + 2 * SQRT3 * scale; xVertex[4] = cx; yVertex[4] = cy + 2 * SQRT3 * scale; xVertex[5] = cx - 1 * scale; yVertex[5] = cy + SQRT3 * scale; hexagon = makePolygon(6, xVertex, yVertex, true); rectBound = hexagon.getBounds(); setPreferredSize(new Dimension(rectBound.width, rectBound.height)); } private GeneralPath getInnerHexagon() { GeneralPath innerHexagon = null; Point2D.Double center = findCenter2D(); final double innerScale = 0.8; AffineTransform at = AffineTransform.getScaleInstance(innerScale, innerScale); innerHexagon = (GeneralPath)hexagon.createTransformedShape(at); // Translate innerHexagon to make it concentric. Rectangle2D innerBounds = innerHexagon.getBounds2D(); Point2D.Double innerCenter = new Point2D.Double(innerBounds.getX() + innerBounds.getWidth() / 2.0, innerBounds.getY() + innerBounds.getHeight() / 2.0); at = AffineTransform.getTranslateInstance( center.getX() - innerCenter.getX(), center.getY() - innerCenter.getY()); innerHexagon.transform(at); return innerHexagon; } @Override public void paint(Graphics g) { super.paint(g); Graphics2D g2 = (Graphics2D)g; if (getAntialias()) { g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); } else { g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); } Color terrainColor = getHexModel().getTerrainColor(); if (isSelected()) { if (terrainColor.equals(highlightColor)) { g2.setColor(HTMLColor.invertRGBColor(highlightColor)); } else { g2.setColor(highlightColor); } g2.fill(hexagon); GeneralPath innerHexagon = getInnerHexagon(); g2.setColor(terrainColor); g2.fill(innerHexagon); g2.setColor(Color.black); g2.draw(innerHexagon); } else { g2.setColor(terrainColor); g2.fill(hexagon); } g2.setColor(Color.black); g2.draw(hexagon); if ((useOverlay) && (paintOverlay(g2))) { // well, ok... } else { // Draw hexside features. for (int i = 0; i < 6; i++) { HazardHexside hazard = getHexModel().getHexsideHazard(i); int n; if (hazard != HazardHexside.NOTHING) { n = (i + 1) % 6; drawHexside(g2, xVertex[i], yVertex[i], xVertex[n], yVertex[n], hazard.getCode()); } // Draw them again from the other side. hazard = getHexModel().getOppositeHazard(i); if (hazard != HazardHexside.NOTHING) { n = (i + 1) % 6; drawHexside(g2, xVertex[n], yVertex[n], xVertex[i], yVertex[i], hazard.getCode()); } } } // Do not anti-alias text. g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); String name = getHexModel().getTerrainName().toUpperCase(); FontMetrics fontMetrics = g2.getFontMetrics(); g2.drawString(name, rectBound.x + ((rectBound.width - fontMetrics.stringWidth(name)) / 2), rectBound.y + ((fontMetrics.getHeight() + rectBound.height) / 2)); // Show hex label in upper left corner. g2.drawString( getHexModel().getLabel(), rectBound.x + (rectBound.width - fontMetrics.stringWidth(getHexModel() .getLabel())) / 3, rectBound.y + ((fontMetrics.getHeight() + rectBound.height) / 4)); } @Override public void repaint() { if (map == null) { super.repaint(); return; } // If an entrance needs repainting, paint the whole map. if (getHexModel().isEntrance()) { map.repaint(); } else { map.repaint(getBounds().x, getBounds().y, getBounds().width, getBounds().height); } } void drawHexside(Graphics2D g2, double vx1, double vy1, double vx2, double vy2, char hexsideType) { double x0; // first focus point double y0; double x1; // second focus point double y1; double x2; // center point double y2; double theta; // gate angle double[] x = new double[4]; // hexside points double[] y = new double[4]; // hexside points x0 = vx1 + (vx2 - vx1) / 6; y0 = vy1 + (vy2 - vy1) / 6; x1 = vx1 + (vx2 - vx1) / 3; y1 = vy1 + (vy2 - vy1) / 3; theta = Math.atan2(vy2 - vy1, vx2 - vx1); switch (hexsideType) { case 'c': // cliff -- triangles for (int j = 0; j < 3; j++) { x = getCliffOrArrowsPositionXArray(j, vx1, vx2, theta); y = getCliffOrArrowsPositionYArray(j, vy1, vy2, theta); GeneralPath polygon = makePolygon(3, x, y, false); g2.setColor(Color.white); g2.fill(polygon); g2.setColor(Color.black); g2.draw(polygon); } break; case 'd': // dune -- arcs for (int j = 0; j < 3; j++) { x0 = vx1 + (vx2 - vx1) * (2 + 3 * j) / 12; y0 = vy1 + (vy2 - vy1) * (2 + 3 * j) / 12; x1 = vx1 + (vx2 - vx1) * (4 + 3 * j) / 12; y1 = vy1 + (vy2 - vy1) * (4 + 3 * j) / 12; x2 = (x0 + x1) / 2; y2 = (y0 + y1) / 2; Rectangle2D.Double rect = new Rectangle2D.Double(); rect.x = x2 - len; rect.y = y2 - len; rect.width = 2 * len; rect.height = 2 * len; g2.setColor(Color.white); Arc2D.Double arc = new Arc2D.Double(rect.x, rect.y, rect.width, rect.height, Math.toDegrees(2 * Math.PI - theta), 180, Arc2D.OPEN); g2.fill(arc); g2.setColor(Color.black); g2.draw(arc); } break; case 's': // slope -- lines for (int j = 0; j < 3; j++) { x = getWallOrSlopePositionXArray(j, vx1, vx2, theta, 3); y = getWallOrSlopePositionYArray(j, vy1, vy2, theta, 3); g2.setColor(Color.black); g2.draw(new Line2D.Double(x[0], y[0], x[1], y[1])); g2.draw(new Line2D.Double(x[2], y[2], x[3], y[3])); } break; case 'w': // wall -- blocks for (int j = 0; j < 3; j++) { x = getWallOrSlopePositionXArray(j, vx1, vx2, theta, 2); y = getWallOrSlopePositionYArray(j, vy1, vy2, theta, 2); GeneralPath polygon = makePolygon(4, x, y, false); g2.setColor(Color.white); g2.fill(polygon); g2.setColor(Color.black); g2.draw(polygon); } break; case 'r': // river -- single blue line g2.setColor(HTMLColor.skyBlue); Stroke oldStroke = g2.getStroke(); g2.setStroke(new BasicStroke((float)5.)); g2.draw(new Line2D.Double(vx1, vy1, vx2, vy2)); g2.setColor(Color.black); g2.setStroke(oldStroke); break; default: LOGGER.log(Level.SEVERE, "Bogus hexside type"); } } public boolean innerContains(Point point) { return getInnerHexagon().contains(point); } private static String imagePostfix = "_Hazard"; private static Image loadOneOverlay(String name, int width, int height) { Image overlay = null; List<String> directories = VariantSupport.getImagesDirectoriesList(); overlay = StaticResourceLoader.getImage(name + imagePostfix, directories, width, height); return overlay; } public boolean paintOverlay(Graphics2D g) { Image overlay = loadOneOverlay(getHexModel().getTerrain().getName(), rectBound.width, rectBound.height); if (overlay != null) { // first, draw the Hex itself g.drawImage(overlay, rectBound.x, rectBound.y, rectBound.width, rectBound.height, map); } boolean didAllHexside = true; Shape oldClip = g.getClip(); //make sure we draw only inside our hex g.setClip(null); g.clip(hexagon); // second, draw the opposite Hex HexSide for (int i = 0; i < 6; i++) { BattleHex model = getHexModel(); HazardHexside hazard = model.getOppositeHazard(i); if (hazard != HazardHexside.NOTHING) { GUIBattleHex neighbor = getNeighbor(i); int dx1 = 0, dx2 = 0, dy1 = 0, dy2 = 0; final float xm, ym; float xi, yi; xm = (float)neighbor.findCenter2D().x; ym = (float)neighbor.findCenter2D().y; xi = (float)neighbor.xVertex[5] - xm; yi = (float)neighbor.yVertex[0] - ym; xi *= 1.2; //1.14814814814814814814; yi *= 1.2; //1.17021276595744680851; xi += xm; yi += ym; dx1 = (int)xi; dy1 = (int)yi; xi = (float)neighbor.xVertex[2] - xm; yi = (float)neighbor.yVertex[3] - ym; xi *= 1.2; //1.14814814814814814814; yi *= 1.2; //1.17021276595744680851; xi += xm; yi += ym; dx2 = (int)xi; dy2 = (int)yi; Image sideOverlay = loadOneOverlay(neighbor.getHexModel() .getHexsideImageName((i + 3) % 6), dx2 - dx1, dy2 - dy1); if (sideOverlay != null) { g.drawImage(sideOverlay, dx1, dy1, dx2 - dx1, dy2 - dy1, map); } else { didAllHexside = false; } } } g.setClip(oldClip); return didAllHexside; } public GUIBattleHex getNeighbor(int i) { assert (i >= 0) && (i <= 5) : "Neighbor index out of range"; return neighbors[i]; } public void setNeighbor(int i, GUIBattleHex hex) { assert (i >= 0) && (i <= 5) : "Neighbor index out of range"; neighbors[i] = hex; getHexModel().setNeighbor(i, hex.getHexModel()); } }