00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022 package com.realtime.crossfire.jxclient.gui.map;
00023
00024 import com.realtime.crossfire.jxclient.faces.Face;
00025 import com.realtime.crossfire.jxclient.faces.FacesProvider;
00026 import com.realtime.crossfire.jxclient.gui.gui.AbstractGUIElement;
00027 import com.realtime.crossfire.jxclient.gui.gui.GUIElement;
00028 import com.realtime.crossfire.jxclient.gui.gui.GUIElementListener;
00029 import com.realtime.crossfire.jxclient.gui.gui.TooltipManager;
00030 import com.realtime.crossfire.jxclient.map.CfMap;
00031 import com.realtime.crossfire.jxclient.map.CfMapSquare;
00032 import com.realtime.crossfire.jxclient.map.MapListener;
00033 import com.realtime.crossfire.jxclient.map.MapScrollListener;
00034 import com.realtime.crossfire.jxclient.map.MapSizeListener;
00035 import com.realtime.crossfire.jxclient.map.MapUpdaterState;
00036 import com.realtime.crossfire.jxclient.map.NewmapListener;
00037 import com.realtime.crossfire.jxclient.server.crossfire.messages.Map2;
00038 import com.realtime.crossfire.jxclient.util.MathUtils;
00039 import java.awt.Color;
00040 import java.awt.Dimension;
00041 import java.awt.Graphics;
00042 import java.awt.Graphics2D;
00043 import java.awt.GraphicsConfiguration;
00044 import java.awt.GraphicsDevice;
00045 import java.awt.GraphicsEnvironment;
00046 import java.awt.Image;
00047 import java.awt.Transparency;
00048 import java.awt.image.BufferedImage;
00049 import java.util.ArrayDeque;
00050 import java.util.Deque;
00051 import java.util.HashMap;
00052 import java.util.Map;
00053 import java.util.Set;
00054 import javax.swing.ImageIcon;
00055 import org.jetbrains.annotations.NotNull;
00056 import org.jetbrains.annotations.Nullable;
00057
00063 public abstract class AbstractGUIMap extends AbstractGUIElement {
00064
00068 private static final long serialVersionUID = 1;
00069
00073 @NotNull
00074 private final MapUpdaterState mapUpdaterState;
00075
00079 @NotNull
00080 private final FacesProvider facesProvider;
00081
00086 @Nullable
00087 private final SmoothingRenderer smoothingRenderer;
00088
00093 @NotNull
00094 private final Object bufferedImageSync = new Object();
00095
00100 @Nullable
00101 private transient BufferedImage bufferedImage = null;
00102
00106 private boolean clearMapPending = true;
00107
00111 @NotNull
00112 private final Deque<Long> scrollMapPending = new ArrayDeque<Long>();
00113
00117 private int mapWidth = 0;
00118
00122 private int mapHeight = 0;
00123
00127 private final int tileSize;
00128
00132 private int playerX = 0;
00133
00137 private int playerY = 0;
00138
00142 private int offsetX = 0;
00143
00147 private int offsetY = 0;
00148
00153 private int displayMinX = 0;
00154
00159 private int displayMaxX = 0;
00160
00165 private int displayMinY = 0;
00166
00171 private int displayMaxY = 0;
00172
00177 private int displayMinOffsetX = 0;
00178
00183 private int displayMaxOffsetX = 0;
00184
00189 private int displayMinOffsetY = 0;
00190
00195 private int displayMaxOffsetY = 0;
00196
00201 @NotNull
00202 private final Map<Color, Image> images = new HashMap<Color, Image>();
00203
00207 @NotNull
00208 private final MapListener mapListener = new MapListener() {
00209
00210 @Override
00211 public void mapChanged(@NotNull final CfMap map, @NotNull final Set<CfMapSquare> changedSquares) {
00212 assert Thread.holdsLock(map);
00213 synchronized (bufferedImageSync) {
00214 final int x0 = map.getOffsetX();
00215 final int y0 = map.getOffsetY();
00216 final Graphics2D g = createBufferGraphics(map);
00217 try {
00218 for (final CfMapSquare mapSquare : changedSquares) {
00219 final int x = mapSquare.getX()+x0;
00220 if (displayMinX <= x && x < displayMaxX) {
00221 final int y = mapSquare.getY()+y0;
00222 if (displayMinY <= y && y < displayMaxY) {
00223 redrawSquare(g, mapSquare, map, x, y);
00224 }
00225 }
00226 }
00227 markPlayer(g, 0, 0);
00228 } finally {
00229 g.dispose();
00230 }
00231 }
00232 setChanged();
00233 }
00234
00235 };
00236
00240 @NotNull
00241 private final NewmapListener newmapListener = new NewmapListener() {
00242
00243 @Override
00244 public void commandNewmapReceived() {
00245 synchronized (bufferedImageSync) {
00246 clearMapPending = true;
00247 scrollMapPending.clear();
00248 }
00249 setChanged();
00250 }
00251
00252 };
00253
00258 private void clearMap(@NotNull final Graphics2D g) {
00259 g.setColor(Color.BLACK);
00260 g.fillRect(0, 0, getWidth(), getHeight());
00261 g.setBackground(DarknessColors.FOG_OF_WAR_COLOR);
00262 g.clearRect(0, 0, getWidth(), getHeight());
00263 }
00264
00268 @NotNull
00269 private final MapScrollListener mapscrollListener = new MapScrollListener() {
00270
00271 @Override
00272 public void mapScrolled(final int dx, final int dy) {
00273 synchronized (bufferedImageSync) {
00274 scrollMapPending.offerLast(((long)dx<<32)|(dy&0xFFFFFFFFL));
00275 }
00276 setChanged();
00277 }
00278
00279 };
00280
00284 @NotNull
00285 private final MapSizeListener mapSizeListener = new MapSizeListener() {
00286
00287 @Override
00288 public void mapSizeChanged(final int mapWidth, final int mapHeight) {
00289 setMapSize(mapWidth, mapHeight);
00290 redrawAll();
00291 }
00292
00293 };
00294
00305 protected AbstractGUIMap(@NotNull final TooltipManager tooltipManager, @NotNull final GUIElementListener elementListener, @NotNull final String name, @NotNull final MapUpdaterState mapUpdaterState, @NotNull final FacesProvider facesProvider, @Nullable final SmoothingRenderer smoothingRenderer) {
00306 super(tooltipManager, elementListener, name, Transparency.OPAQUE);
00307 this.smoothingRenderer = smoothingRenderer;
00308 tileSize = facesProvider.getSize();
00309 assert tileSize > 0;
00310 this.mapUpdaterState = mapUpdaterState;
00311 this.facesProvider = facesProvider;
00312 this.mapUpdaterState.addMapSizeListener(mapSizeListener);
00313 this.mapUpdaterState.addCrossfireMapListener(mapListener);
00314 this.mapUpdaterState.addCrossfireNewmapListener(newmapListener);
00315 this.mapUpdaterState.addCrossfireMapScrollListener(mapscrollListener);
00316 setMapSize(this.mapUpdaterState.getMapWidth(), this.mapUpdaterState.getMapHeight());
00317 }
00318
00322 @Override
00323 public void dispose() {
00324 super.dispose();
00325 mapUpdaterState.removeMapSizeListener(mapSizeListener);
00326 mapUpdaterState.removeCrossfireNewmapListener(newmapListener);
00327 mapUpdaterState.removeCrossfireMapScrollListener(mapscrollListener);
00328 mapUpdaterState.removeCrossfireMapListener(mapListener);
00329 }
00330
00334 @Override
00335 public void execute() {
00336
00337 }
00338
00342 private void redrawAll() {
00343 final CfMap map = mapUpdaterState.getMap();
00344
00345 synchronized (map) {
00346
00347 synchronized (bufferedImageSync) {
00348 final Graphics g = createBufferGraphics(map);
00349 try {
00350 redrawTiles(g, map, displayMinX, displayMinY, displayMaxX, displayMaxY);
00351 markPlayer(g, 0, 0);
00352 } finally {
00353 g.dispose();
00354 }
00355 }
00356 }
00357 }
00358
00364 private void redrawAllUnlessDirty(@NotNull final Graphics g, @NotNull final CfMap map) {
00365 redrawTilesUnlessDirty(g, map, displayMinX-offsetX/tileSize, displayMinY-offsetY/tileSize, displayMaxX-offsetX/tileSize, displayMaxY-offsetY/tileSize);
00366 }
00367
00377 private void redrawTiles(@NotNull final Graphics g, @NotNull final CfMap map, final int x0, final int y0, final int x1, final int y1) {
00378 for (int x = x0; x < x1; x++) {
00379 for (int y = y0; y < y1; y++) {
00380 final int mapSquareX = x-offsetX/tileSize;
00381 final int mapSquareY = y-offsetY/tileSize;
00382 final CfMapSquare mapSquare = map.getMapSquare(mapSquareX, mapSquareY);
00383 redrawSquare(g, mapSquare, map, mapSquareX, mapSquareY);
00384 }
00385 }
00386 }
00387
00397 private void redrawTilesUnlessDirty(@NotNull final Graphics g, @NotNull final CfMap map, final int x0, final int y0, final int x1, final int y1) {
00398 for (int x = x0; x < x1; x++) {
00399 for (int y = y0; y < y1; y++) {
00400 redrawSquareUnlessDirty(g, map, x, y);
00401 }
00402 }
00403 }
00404
00414 private void redrawSquareUnlessDirty(@NotNull final Graphics g, @NotNull final CfMap map, final int x, final int y) {
00415 final CfMapSquare mapSquare = map.getMapSquareUnlessDirty(x, y);
00416 if (mapSquare != null) {
00417 redrawSquare(g, mapSquare, map, x, y);
00418 }
00419 }
00420
00429 protected void redrawSquare(@NotNull final Graphics g, @NotNull final CfMapSquare mapSquare, @NotNull final CfMap map, final int x, final int y) {
00430 redrawSquare(g, x, y, mapSquare, map);
00431 if (x < 0 || y < 0 || x >= mapWidth || y >= mapHeight || mapSquare.isFogOfWar()) {
00432 paintColoredSquare(g, DarknessColors.FOG_OF_WAR_COLOR, offsetX+x*tileSize, offsetY+y*tileSize);
00433 }
00434 final int darkness = mapSquare.getDarkness();
00435 if (darkness < CfMapSquare.DARKNESS_FULL_BRIGHT) {
00436 paintColoredSquare(g, DarknessColors.getDarknessColor(darkness), offsetX+x*tileSize, offsetY+y*tileSize);
00437 }
00438 }
00439
00448 private void redrawSquare(@NotNull final Graphics g, final int x, final int y, @NotNull final CfMapSquare mapSquare, @NotNull final CfMap map) {
00449 final int px = offsetX+x*tileSize;
00450 final int py = offsetY+y*tileSize;
00451 final int mapSquareX = mapSquare.getX();
00452 final int mapSquareY = mapSquare.getY();
00453 boolean foundSquare = false;
00454 for (int layer = 0; layer < Map2.NUM_LAYERS; layer++) {
00455 final CfMapSquare headMapSquare = mapSquare.getHeadMapSquare(layer);
00456 if (headMapSquare != null) {
00457 final Face headFace = headMapSquare.getFace(layer);
00458 assert headFace != null;
00459 final int dx = headMapSquare.getX()-mapSquareX;
00460 final int dy = headMapSquare.getY()-mapSquareY;
00461 assert dx > 0 || dy > 0;
00462 if (!foundSquare) {
00463 foundSquare = true;
00464 paintSquareBackground(g, px, py, true, mapSquare);
00465 }
00466 paintImage(g, headFace, px, py, tileSize*dx, tileSize*dy);
00467 }
00468
00469 final Face face = mapSquare.getFace(layer);
00470 if (face != null) {
00471 if (!foundSquare) {
00472 foundSquare = true;
00473 paintSquareBackground(g, px, py, true, mapSquare);
00474 }
00475 paintImage(g, face, px, py, 0, 0);
00476 if (smoothingRenderer != null) {
00477 smoothingRenderer.paintSmooth(g, x, y, px, py, layer, map, tileSize);
00478 }
00479 }
00480 }
00481 if (!foundSquare) {
00482 paintSquareBackground(g, px, py, false, mapSquare);
00483 }
00484 }
00485
00494 protected abstract void paintSquareBackground(@NotNull final Graphics g, final int px, final int py, final boolean hasImage, @NotNull final CfMapSquare mapSquare);
00495
00505 private void paintImage(@NotNull final Graphics g, @NotNull final Face face, final int px, final int py, final int offsetX, final int offsetY) {
00506 final ImageIcon imageIcon = facesProvider.getImageIcon(face.getFaceNum(), null);
00507 final int sx = imageIcon.getIconWidth()-offsetX;
00508 final int sy = imageIcon.getIconHeight()-offsetY;
00509 g.drawImage(imageIcon.getImage(), px, py, px+tileSize, py+tileSize, sx-tileSize, sy-tileSize, sx, sy, null);
00510 }
00511
00518 protected abstract void markPlayer(@NotNull final Graphics g, final int dx, final int dy);
00519
00523 @Override
00524 public void paintComponent(@NotNull final Graphics g) {
00525 super.paintComponent(g);
00526 synchronized (bufferedImageSync) {
00527 g.drawImage(bufferedImage, 0, 0, null);
00528 }
00529 }
00530
00536 private void setMapSize(final int mapWidth, final int mapHeight) {
00537 this.mapWidth = mapWidth;
00538 this.mapHeight = mapHeight;
00539
00540 final int nX = MathUtils.divRoundUp(playerX, tileSize);
00541 displayMinOffsetX = playerX-nX*tileSize;
00542 assert -tileSize < displayMinOffsetX && displayMinOffsetX <= 0;
00543 assert (playerX-displayMinOffsetX)%tileSize == 0;
00544 displayMinX = (mapWidth-1)/2-nX;
00545 final int tilesX = MathUtils.divRoundUp(getWidth()-displayMinOffsetX, tileSize);
00546 displayMaxX = displayMinX+tilesX;
00547 assert (displayMaxX-displayMinX)*tileSize >= getWidth();
00548 assert (displayMaxX-displayMinX)*tileSize-getWidth() < 2*tileSize;
00549 displayMaxOffsetX = MathUtils.mod(-displayMinOffsetX-getWidth(), tileSize);
00550 offsetX = displayMinOffsetX-displayMinX*tileSize;
00551
00552 final int nY = MathUtils.divRoundUp(playerY, tileSize);
00553 displayMinOffsetY = playerY-nY*tileSize;
00554 assert -tileSize < displayMinOffsetY && displayMinOffsetY <= 0;
00555 assert (playerY-displayMinOffsetY)%tileSize == 0;
00556 displayMinY = (mapHeight-1)/2-nY;
00557 final int tilesY = MathUtils.divRoundUp(getHeight()-displayMinOffsetY, tileSize);
00558 displayMaxY = displayMinY+tilesY;
00559 assert (displayMaxY-displayMinY)*tileSize >= getHeight();
00560 assert (displayMaxY-displayMinY)*tileSize-getHeight() < 2*tileSize;
00561 displayMaxOffsetY = MathUtils.mod(-displayMinOffsetY-getHeight(), tileSize);
00562 offsetY = displayMinOffsetY-displayMinY*tileSize;
00563
00564 final GraphicsEnvironment graphicsEnvironment = GraphicsEnvironment.getLocalGraphicsEnvironment();
00565 final GraphicsDevice graphicsDevice = graphicsEnvironment.getDefaultScreenDevice();
00566 final GraphicsConfiguration graphicsConfiguration = graphicsDevice.getDefaultConfiguration();
00567 bufferedImage = graphicsConfiguration.createCompatibleImage(Math.max(1, getWidth()), Math.max(1, getHeight()), Transparency.OPAQUE);
00568 redrawAll();
00569 }
00570
00575 public int getPlayerX() {
00576 return playerX;
00577 }
00578
00583 public int getPlayerY() {
00584 return playerY;
00585 }
00586
00591 public int getOffsetX() {
00592 return offsetX;
00593 }
00594
00599 public int getOffsetY() {
00600 return offsetY;
00601 }
00602
00606 @Override
00607 public void setBounds(final int x, final int y, final int width, final int height) {
00608 super.setBounds(x, y, width, height);
00609 playerX = width/2-tileSize/2;
00610 playerY = height/2-tileSize/2;
00611 setMapSize(mapWidth, mapHeight);
00612 redrawAll();
00613 }
00614
00622 private void updateScrolledMap(@NotNull final Graphics g, @NotNull final CfMap map, final int dx, final int dy) {
00623 if (Math.abs(dx)*tileSize >= getWidth() || Math.abs(dy)*tileSize >= getHeight()) {
00624 redrawAllUnlessDirty(g, map);
00625 } else {
00626 final int x = dx > 0 ? dx : 0;
00627 final int w = dx > 0 ? -dx : dx;
00628 final int y = dy > 0 ? dy : 0;
00629 final int h = dy > 0 ? -dy : dy;
00630 g.copyArea(x*tileSize, y*tileSize, getWidth()+w*tileSize, getHeight()+h*tileSize, -dx*tileSize, -dy*tileSize);
00631
00632 if (dx > 0) {
00633 final int ww = (displayMaxOffsetX == 0 ? 0 : 1)+dx;
00634 redrawTilesUnlessDirty(g, map, displayMaxX-ww, displayMinY, displayMaxX, displayMaxY);
00635 } else if (dx < 0) {
00636 final int ww = (displayMinOffsetX == 0 ? 0 : 1)-dx;
00637 redrawTilesUnlessDirty(g, map, displayMinX, displayMinY, displayMinX+ww, displayMaxY);
00638 }
00639 if (dy > 0) {
00640 final int hh = (displayMaxOffsetY == 0 ? 0 : 1)+dy;
00641 redrawTilesUnlessDirty(g, map, displayMinX, displayMaxY-hh, displayMaxX, displayMaxY);
00642 } else if (dy < 0) {
00643 final int hh = (displayMinOffsetY == 0 ? 0 : 1)-dy;
00644 redrawTilesUnlessDirty(g, map, displayMinX, displayMinY, displayMaxX, displayMinY+hh);
00645 }
00646 }
00647 }
00648
00656 protected void paintColoredSquare(@NotNull final Graphics g, @NotNull final Color color, final int x, final int y) {
00657 Image image = images.get(color);
00658 if (image == null) {
00659 final BufferedImage tmp = new BufferedImage(tileSize, tileSize, color.getTransparency() == Transparency.OPAQUE ? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB);
00660 final Graphics g2 = tmp.createGraphics();
00661 try {
00662 g2.setColor(color);
00663 g2.fillRect(0, 0, tileSize, tileSize);
00664 } finally {
00665 g2.dispose();
00666 }
00667 image = tmp;
00668 images.put(color, image);
00669 }
00670 g.drawImage(image, x, y, tileSize, tileSize, null);
00671 }
00672
00677 protected int getMapWidth() {
00678 return mapWidth;
00679 }
00680
00685 protected int getMapHeight() {
00686 return mapHeight;
00687 }
00688
00696 @NotNull
00697 private Graphics2D createBufferGraphics(@NotNull final CfMap map) {
00698 assert Thread.holdsLock(bufferedImageSync);
00699 assert bufferedImage != null;
00700 final Graphics2D graphics = bufferedImage.createGraphics();
00701 if (clearMapPending) {
00702 clearMapPending = false;
00703 clearMap(graphics);
00704 }
00705 while (true) {
00706 final Long scrollMap = scrollMapPending.pollFirst();
00707 if (scrollMap == null) {
00708 break;
00709 }
00710 final long tmp = scrollMap;
00711 final int dx = (int)(tmp>>32);
00712 final int dy = (int)tmp;
00713 updateScrolledMap(graphics, map, dx, dy);
00714 markPlayer(graphics, dx, dy);
00715 }
00716 return graphics;
00717 }
00718
00722 @Nullable
00723 @Override
00724 public Dimension getMinimumSize() {
00725 return new Dimension(tileSize, tileSize);
00726 }
00727
00728 }