// This file is part of MicropolisJ. // Copyright (C) 2013 Jason Long // Portions Copyright (C) 1989-2007 Electronic Arts Inc. // // MicropolisJ is free software; you can redistribute it and/or modify // it under the terms of the GNU GPLv3, with additional terms. // See the README file, included in this distribution, for details. package micropolisj.gui; import java.awt.*; import java.awt.event.*; import java.awt.image.*; import java.net.URL; import java.util.*; import javax.swing.*; import javax.swing.event.*; import micropolisj.engine.*; import static micropolisj.engine.TileConstants.*; public class OverlayMapView extends JComponent implements Scrollable, MapListener { Micropolis engine; ArrayList views = new ArrayList(); MapState mapState = MapState.ALL; public OverlayMapView(Micropolis _engine) { assert _engine != null; MouseAdapter mouse = new MouseAdapter() { @Override public void mousePressed(MouseEvent ev) { onMousePressed(ev); } @Override public void mouseDragged(MouseEvent ev) { onMouseDragged(ev); } }; addMouseListener(mouse); addMouseMotionListener(mouse); setEngine(_engine); } public Micropolis getEngine() { return engine; } public void setEngine(Micropolis newEngine) { assert newEngine != null; if (engine != null) { //old engine engine.removeMapListener(this); } engine = newEngine; if (engine != null) { //new engine engine.addMapListener(this); } invalidate(); //map size may have changed repaint(); engine.calculateCenterMass(); dragViewToCityCenter(); } public MapState getMapState() { return mapState; } @Override public Dimension getPreferredSize() { return new Dimension( getInsets().left + getInsets().right + TILE_WIDTH*engine.getWidth(), getInsets().top + getInsets().bottom + TILE_HEIGHT*engine.getHeight() ); } public void setMapState(MapState newState) { if (mapState == newState) return; mapState = newState; repaint(); } static BufferedImage tileArrayImage = loadImage("/tilessm.png"); static final int TILE_WIDTH = 3; static final int TILE_HEIGHT = 3; static final int TILE_OFFSET_Y = 3; static BufferedImage loadImage(String resourceName) { URL iconUrl = MicropolisDrawingArea.class.getResource(resourceName); Image refImage = new ImageIcon(iconUrl).getImage(); BufferedImage bi = new BufferedImage(refImage.getWidth(null), refImage.getHeight(null), BufferedImage.TYPE_INT_RGB); Graphics2D gr = bi.createGraphics(); gr.drawImage(refImage, 0, 0, null); return bi; } static final Color VAL_LOW = new Color(0xbfbfbf); static final Color VAL_MEDIUM = new Color(0xffff00); static final Color VAL_HIGH = new Color(0xff7f00); static final Color VAL_VERYHIGH = new Color(0xff0000); static final Color VAL_PLUS = new Color(0x007f00); static final Color VAL_VERYPLUS = new Color(0x00e600); static final Color VAL_MINUS = new Color(0xff7f00); static final Color VAL_VERYMINUS = new Color(0xffff00); private Color getCI(int x) { if (x < 50) return null; else if (x < 100) return VAL_LOW; else if (x < 150) return VAL_MEDIUM; else if (x < 200) return VAL_HIGH; else return VAL_VERYHIGH; } private Color getCI_rog(int x) { if (x > 100) return VAL_VERYPLUS; else if (x > 20) return VAL_PLUS; else if (x < -100) return VAL_VERYMINUS; else if (x < -20) return VAL_MINUS; else return null; } private void drawLandMap(Graphics gr) { int [][] A = engine.landValueMem; for (int y = 0; y < A.length; y++) { for (int x = 0; x < A[y].length; x++) { maybeDrawRect(gr, getCI(A[y][x]),x*6,y*6,6,6); } } } private void drawPollutionMap(Graphics gr) { int [][] A = engine.pollutionMem; for (int y = 0; y < A.length; y++) { for (int x = 0; x < A[y].length; x++) { maybeDrawRect(gr, getCI(10 + A[y][x]),x*6,y*6,6,6); } } } private void drawCrimeMap(Graphics gr) { int [][] A = engine.crimeMem; for (int y = 0; y < A.length; y++) { for (int x = 0; x < A[y].length; x++) { maybeDrawRect(gr, getCI(A[y][x]),x*6,y*6,6,6); } } } private void drawTrafficMap(Graphics gr) { int [][] A = engine.trfDensity; for (int y = 0; y < A.length; y++) { for (int x = 0; x < A[y].length; x++) { maybeDrawRect(gr, getCI(A[y][x]),x*6,y*6,6,6); } } } private void drawPopDensity(Graphics gr) { int [][] A = engine.popDensity; for (int y = 0; y < A.length; y++) { for (int x = 0; x < A[y].length; x++) { maybeDrawRect(gr, getCI(A[y][x]),x*6,y*6,6,6); } } } private void drawRateOfGrowth(Graphics gr) { int [][] A = engine.rateOGMem; for (int y = 0; y < A.length; y++) { for (int x = 0; x < A[y].length; x++) { maybeDrawRect(gr, getCI_rog(A[y][x]),x*24,y*24,24,24); } } } private void drawFireRadius(Graphics gr) { int [][] A = engine.fireRate; for (int y = 0; y < A.length; y++) { for (int x = 0; x < A[y].length; x++) { maybeDrawRect(gr, getCI(A[y][x]),x*24,y*24,24,24); } } } private void drawPoliceRadius(Graphics gr) { int [][] A = engine.policeMapEffect; for (int y = 0; y < A.length; y++) { for (int x = 0; x < A[y].length; x++) { maybeDrawRect(gr, getCI(A[y][x]),x*24,y*24,24,24); } } } private void maybeDrawRect(Graphics gr, Color col, int x, int y, int width, int height) { if (col != null) { gr.setColor(col); gr.fillRect(x,y,width,height); } } static final int UNPOWERED = 0x6666e6; //lightblue static final int POWERED = 0xff0000; //red static final int CONDUCTIVE = 0xbfbfbf; //lightgray private int checkPower(BufferedImage img, int x, int y, int rawTile) { int pix; if ((rawTile & LOMASK) <= 63) { return rawTile & LOMASK; } else if (isZoneCenter(rawTile)) { // zone pix = ((rawTile & PWRBIT) != 0) ? POWERED : UNPOWERED; } else if (isConductive(rawTile)) { pix = CONDUCTIVE; } else { return DIRT; } for (int yy = 0; yy < TILE_HEIGHT; yy++) { for (int xx = 0; xx < TILE_WIDTH; xx++) { img.setRGB(x*TILE_WIDTH+xx,y*TILE_HEIGHT+yy, pix); } } return -1; //this special value tells caller to skip the tile bitblt, //since it was performed here } @Override public void paintComponent(Graphics gr) { final int width = engine.getWidth(); final int height = engine.getHeight(); BufferedImage img = new BufferedImage(width*TILE_WIDTH, height*TILE_HEIGHT, BufferedImage.TYPE_INT_RGB); final Insets INSETS = getInsets(); Rectangle clipRect = gr.getClipBounds(); int minX = Math.max(0, (clipRect.x - INSETS.left) / TILE_WIDTH); int minY = Math.max(0, (clipRect.y - INSETS.top) / TILE_HEIGHT); int maxX = Math.min(width, 1 + (clipRect.x - INSETS.left + clipRect.width-1) / TILE_WIDTH); int maxY = Math.min(height, 1 + (clipRect.y - INSETS.top + clipRect.height-1) / TILE_HEIGHT); for (int y = minY; y < maxY; y++) { for (int x = minX; x < maxX; x++) { int tile = engine.getTile(x,y) & LOMASK; switch (mapState) { case RESIDENTIAL: if (tile >= COMBASE) { tile = DIRT; } break; case COMMERCIAL: if (tile > COMLAST || (tile >= RESBASE && tile < COMBASE)) { tile = DIRT; } break; case INDUSTRIAL: if ((tile >= RESBASE && tile < INDBASE) || (tile >= PORTBASE && tile < SMOKEBASE) || (tile >= TINYEXP && tile < 884) || tile >= FOOTBALLGAME1) { tile = DIRT; } break; case POWER_OVERLAY: tile = checkPower(img, x, y, engine.getTile(x,y)); break; case TRANSPORT: case TRAFFIC_OVERLAY: if (tile >= RESBASE || (tile >= 207 && tile <= LVPOWER10) || tile == 223) { tile = DIRT; } break; default: } // tile == -1 means it's already been drawn // in the checkPower function if (tile != -1) { if (tile >= 0 && tile <= LAST_TILE) { for (int yy = 0; yy < TILE_HEIGHT; yy++) { for (int xx = 0; xx < TILE_WIDTH; xx++) { img.setRGB(x*TILE_WIDTH+xx,y*TILE_HEIGHT+yy, tileArrayImage.getRGB(xx,tile*TILE_OFFSET_Y+yy)); } } } } } } gr.drawImage(img, INSETS.left, INSETS.top, null); gr = gr.create(); gr.translate(INSETS.left, INSETS.top); switch (mapState) { case POLICE_OVERLAY: drawPoliceRadius(gr); break; case FIRE_OVERLAY: drawFireRadius(gr); break; case LANDVALUE_OVERLAY: drawLandMap(gr); break; case CRIME_OVERLAY: drawCrimeMap(gr); break; case POLLUTE_OVERLAY: drawPollutionMap(gr); break; case TRAFFIC_OVERLAY: drawTrafficMap(gr); break; case GROWTHRATE_OVERLAY: drawRateOfGrowth(gr); break; case POPDEN_OVERLAY: drawPopDensity(gr); break; default: } for (ConnectedView cv : views) { Rectangle rect = getViewRect(cv); gr.setColor(Color.WHITE); gr.drawRect(rect.x-2,rect.y-2,rect.width+2,rect.height+2); gr.setColor(Color.BLACK); gr.drawRect(rect.x-0,rect.y-0,rect.width+2,rect.height+2); gr.setColor(Color.YELLOW); gr.drawRect(rect.x-1,rect.y-1,rect.width+2,rect.height+2); } } Rectangle getViewRect(ConnectedView cv) { Rectangle rawRect = cv.scrollPane.getViewport().getViewRect(); return new Rectangle( rawRect.x * 3 / 16, rawRect.y * 3 / 16, rawRect.width * 3 / 16, rawRect.height * 3 / 16 ); } private void dragViewTo(Point p) { if (views.isEmpty()) return; ConnectedView cv = views.get(0); Dimension d = cv.scrollPane.getViewport().getExtentSize(); Dimension mapSize = cv.scrollPane.getViewport().getViewSize(); Point np = new Point( p.x * 16 / 3 - d.width / 2, p.y * 16 / 3 - d.height / 2 ); np.x = Math.max(0, Math.min(np.x, mapSize.width - d.width)); np.y = Math.max(0, Math.min(np.y, mapSize.height - d.height)); cv.scrollPane.getViewport().setViewPosition(np); } //implements Scrollable public Dimension getPreferredScrollableViewportSize() { return new Dimension(120,120); } //implements Scrollable public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) { if (orientation == SwingConstants.VERTICAL) return visibleRect.height; else return visibleRect.width; } //implements Scrollable public boolean getScrollableTracksViewportWidth() { return false; } //implements Scrollable public boolean getScrollableTracksViewportHeight() { return false; } //implements Scrollable public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) { if (orientation == SwingConstants.VERTICAL) return TILE_HEIGHT; else return TILE_WIDTH; } //implements MapListener public void mapOverlayDataChanged(MapState overlayDataType) { repaint(); } //implements MapListener public void spriteMoved(Sprite sprite) { } //implements MapListener public void tileChanged(int xpos, int ypos) { Rectangle r = new Rectangle(xpos*TILE_WIDTH, ypos * TILE_HEIGHT, TILE_WIDTH, TILE_HEIGHT); repaint(r); } //implements MapListener public void wholeMapChanged() { repaint(); engine.calculateCenterMass(); dragViewToCityCenter(); } public void dragViewToCityCenter() { dragViewTo(new Point(TILE_WIDTH * engine.centerMassX + 1, TILE_HEIGHT * engine.centerMassY + 1)); } class ConnectedView implements ChangeListener { JScrollPane scrollPane; ConnectedView(MicropolisDrawingArea view, JScrollPane scrollPane) { this.scrollPane = scrollPane; scrollPane.getViewport().addChangeListener(this); } public void stateChanged(ChangeEvent ev) { repaint(); } } public void connectView(MicropolisDrawingArea view, JScrollPane scrollPane) { ConnectedView cv = new ConnectedView(view, scrollPane); views.add(cv); repaint(); } private void onMousePressed(MouseEvent ev) { if (ev.getButton() == MouseEvent.BUTTON1) dragViewTo(ev.getPoint()); } private void onMouseDragged(MouseEvent ev) { if ((ev.getModifiersEx() & MouseEvent.BUTTON1_DOWN_MASK) == 0) return; dragViewTo(ev.getPoint()); } }