git-svn-id: https://micropolis.googlecode.com/svn/trunk/micropolis-java@746 d9718cc8-9f43-0410-858b-315f434eb58c
519 lines
12 KiB
Java
519 lines
12 KiB
Java
// 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<ConnectedView> views = new ArrayList<ConnectedView>();
|
|
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());
|
|
}
|
|
}
|