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.map;
00023
00024 import com.realtime.crossfire.jxclient.faces.Face;
00025 import com.realtime.crossfire.jxclient.server.crossfire.CrossfireUpdateMapListener;
00026 import com.realtime.crossfire.jxclient.server.crossfire.messages.Map2;
00027 import java.util.Collection;
00028 import java.util.HashMap;
00029 import java.util.HashSet;
00030 import java.util.Map;
00031 import java.util.Set;
00032 import org.jetbrains.annotations.NotNull;
00033 import org.jetbrains.annotations.Nullable;
00034
00045 public class CfMap {
00046
00050 private int minX = 0;
00051
00055 private int maxX = -1;
00056
00060 private int minY = 0;
00061
00065 private int maxY = -1;
00066
00070 private int minPx = 0;
00071
00075 private int maxPx = -1;
00076
00080 private int minPy = 0;
00081
00085 private int maxPy = -1;
00086
00091 private int ox;
00092
00097 private int oy;
00098
00102 private int patchX = 0;
00103
00107 private int patchY = 0;
00108
00112 @Nullable
00113 private CfMapPatch[][] patch = null;
00114
00118 @NotNull
00119 private final Set<CfMapSquare> dirtyMapSquares = new HashSet<CfMapSquare>();
00120
00125 @NotNull
00126 private final Map<Integer, Collection<CfMapSquare>> pendingFaceSquares = new HashMap<Integer, Collection<CfMapSquare>>();
00127
00133 public void reset(final int mapWidth, final int mapHeight) {
00134 minX = 0;
00135 maxX = -1;
00136 minY = 0;
00137 maxY = -1;
00138 minPx = 0;
00139 maxPx = -1;
00140 minPy = 0;
00141 maxPy = -1;
00142 patchX = 0;
00143 patchY = 0;
00144 patch = null;
00145 dirtyMapSquares.clear();
00146 pendingFaceSquares.clear();
00147
00148
00149 clearSquare(0, 0);
00150 clearSquare(mapWidth-1, mapHeight-1);
00151 }
00152
00159 public void setDarkness(final int x, final int y, final int darkness) {
00160 assert Thread.holdsLock(this);
00161 if (expandTo(x, y).setDarkness(ox, oy, darkness)) {
00162 for (int l = 0; l < Map2.NUM_LAYERS; l++) {
00163 setFaceInternal(x, y, l, CfMapSquare.DEFAULT_FACE);
00164 }
00165 }
00166 }
00167
00175 public int getDarkness(final int x, final int y) {
00176 assert Thread.holdsLock(this);
00177 final CfMapPatch mapPatch = getMapPatch(x, y);
00178 return mapPatch != null ? mapPatch.getDarkness(ox, oy) : CfMapSquare.DEFAULT_DARKNESS;
00179 }
00180
00188 public void setSmooth(final int x, final int y, final int layer, final int smooth) {
00189 final int result = expandTo(x, y).setSmooth(ox, oy, layer, smooth);
00190 if ((result&1) != 0) {
00191 for (int l = 0; l < Map2.NUM_LAYERS; l++) {
00192 setFaceInternal(x, y, l, CfMapSquare.DEFAULT_FACE);
00193 }
00194 }
00195 if ((result&2) != 0) {
00196 for (int dx = -1; dx <= +1; dx++) {
00197 for (int dy = -1; dy <= +1; dy++) {
00198 squareModified(getMapSquare(x+dx, y+dy));
00199 }
00200 }
00201 }
00202 }
00203
00211 public int getSmooth(final int x, final int y, final int layer) {
00212 final CfMapPatch mapPatch = getMapPatch(x, y);
00213 final int result = mapPatch != null ? mapPatch.getSmooth(ox, oy, layer) : CfMapSquare.DEFAULT_SMOOTH;
00214 return result;
00215 }
00216
00223 public void setMagicMap(final int x0, final int y0, final byte[][] data) {
00224 assert Thread.holdsLock(this);
00225 for (int y = 0; y < data.length; y++) {
00226 for (int x = 0; x < data[y].length; x++) {
00227 final int color = data[y][x]&CrossfireUpdateMapListener.FACE_COLOR_MASK;
00228 if (expandTo(x0+x, y0+y).setColor(ox, oy, color)) {
00229 for (int l = 0; l < Map2.NUM_LAYERS; l++) {
00230 setFaceInternal(x, y, l, CfMapSquare.DEFAULT_FACE);
00231 }
00232 }
00233 }
00234 }
00235 }
00236
00243 public int getColor(final int x, final int y) {
00244 assert Thread.holdsLock(this);
00245 final CfMapPatch mapPatch = getMapPatch(x, y);
00246 return mapPatch != null ? mapPatch.getColor(ox, oy) : CfMapSquare.DEFAULT_COLOR;
00247 }
00248
00257 public void setFace(final int x, final int y, final int layer, @Nullable final Face face) {
00258 assert Thread.holdsLock(this);
00259 if (expandTo(x, y).resetFogOfWar(ox, oy)) {
00260 setDarkness(x, y, CfMapSquare.DEFAULT_DARKNESS);
00261 for (int l = 0; l < Map2.NUM_LAYERS; l++) {
00262 setFaceInternal(x, y, l, l == layer ? face : CfMapSquare.DEFAULT_FACE);
00263 }
00264 dirty(x, y);
00265 } else {
00266 setFaceInternal(x, y, layer, face);
00267 }
00268 }
00269
00277 private void setFaceInternal(final int x, final int y, final int layer, @Nullable final Face face) {
00278 final CfMapSquare headMapSquare = expandTo(x, y).getSquare(ox, oy);
00279
00280 final Face oldFace = headMapSquare.getFace(layer);
00281 if (oldFace != null) {
00282 expandFace(x, y, layer, oldFace, headMapSquare, null);
00283 }
00284 headMapSquare.setFace(layer, face);
00285 if (face != null) {
00286 expandFace(x, y, layer, face, headMapSquare, headMapSquare);
00287 }
00288 }
00289
00300 private void expandFace(final int x, final int y, final int layer, @NotNull final Face face, @NotNull final CfMapSquare oldMapSquare, @Nullable final CfMapSquare newMapSquare) {
00301 final int sx = face.getTileWidth();
00302 final int sy = face.getTileHeight();
00303 for (int dx = 0; dx < sx; dx++) {
00304 for (int dy = 0; dy < sy; dy++) {
00305 if (dx > 0 || dy > 0) {
00306 if (newMapSquare != null) {
00307 setHeadMapSquare(x-dx, y-dy, layer, newMapSquare, true);
00308 } else if (getHeadMapSquare(x-dx, y-dy, layer) == oldMapSquare) {
00309 setHeadMapSquare(x-dx, y-dy, layer, null, true);
00310 }
00311 }
00312 }
00313 }
00314 }
00315
00324 private void dirtyFace(final int x, final int y, final int layer, @NotNull final Face face) {
00325 final int sx = face.getTileWidth();
00326 final int sy = face.getTileHeight();
00327 for (int dx = 0; dx < sx; dx++) {
00328 for (int dy = 0; dy < sy; dy++) {
00329 if (dx > 0 || dy > 0) {
00330 if (isFogOfWar(x-dx, y-dy)) {
00331 dirty(x-dx, y-dy);
00332 } else {
00333 setHeadMapSquare(x-dx, y-dy, layer, null, false);
00334 }
00335 }
00336 }
00337 }
00338 }
00339
00347 @Nullable
00348 public Face getFace(final int x, final int y, final int layer) {
00349 assert Thread.holdsLock(this);
00350 final CfMapPatch mapPatch = getMapPatch(x, y);
00351 return mapPatch != null ? mapPatch.getFace(ox, oy, layer) : CfMapSquare.DEFAULT_FACE;
00352 }
00353
00365 private void setHeadMapSquare(final int x, final int y, final int layer, @Nullable final CfMapSquare mapSquare, final boolean setAlways) {
00366 expandTo(x, y).setHeadMapSquare(ox, oy, layer, mapSquare, setAlways);
00367 }
00368
00377 @Nullable
00378 public CfMapSquare getHeadMapSquare(final int x, final int y, final int layer) {
00379 assert Thread.holdsLock(this);
00380 final CfMapPatch mapPatch = getMapPatch(x, y);
00381 return mapPatch != null ? mapPatch.getHeadMapSquare(ox, oy, layer) : null;
00382 }
00383
00390 public void clearSquare(final int x, final int y) {
00391 assert Thread.holdsLock(this);
00392 final CfMapPatch mapPatch = expandTo(x, y);
00393 mapPatch.clearSquare(ox, oy);
00394 for (int layer = 0; layer < Map2.NUM_LAYERS; layer++) {
00395 final Face face = mapPatch.getFace(ox, oy, layer);
00396 if (face != null) {
00397 dirtyFace(x, y, layer, face);
00398 }
00399 }
00400 }
00401
00407 public void dirty(final int x, final int y) {
00408 assert Thread.holdsLock(this);
00409 expandTo(x, y).dirty(ox, oy);
00410 }
00411
00418 public boolean isFogOfWar(final int x, final int y) {
00419 assert Thread.holdsLock(this);
00420 final CfMapPatch mapPatch = getMapPatch(x, y);
00421 return mapPatch != null && mapPatch.isFogOfWar(ox, oy);
00422 }
00423
00432 @Nullable
00433 private CfMapPatch getMapPatch(final int x, final int y) {
00434 if (x < minX || x > maxX || y < minY || y > maxY) {
00435 return null;
00436 }
00437
00438 final int px = ((x-patchX)>>CfMapPatch.SIZE_LOG)-minPx;
00439 final int py = ((y-patchY)>>CfMapPatch.SIZE_LOG)-minPy;
00440 assert px >= 0;
00441 assert py >= 0;
00442 assert px <= maxPx-minPx;
00443 assert py <= maxPy-minPy;
00444 ox = (x-patchX)&(CfMapPatch.SIZE-1);
00445 oy = (y-patchY)&(CfMapPatch.SIZE-1);
00446 assert ox >= 0;
00447 assert oy >= 0;
00448 assert ox < CfMapPatch.SIZE;
00449 assert oy < CfMapPatch.SIZE;
00450
00451 assert patch != null;
00452 assert patch[px] != null;
00453 final CfMapPatch mapPatch = patch[px][py];
00454 if (mapPatch != null) {
00455 return mapPatch;
00456 }
00457
00458 patch[px][py] = new CfMapPatch(this, x-patchX-ox, y-patchY-oy);
00459 assert patch != null;
00460 assert patch[px] != null;
00461 return patch[px][py];
00462 }
00463
00469 private void scroll(final int dx, final int dy) {
00470 assert Thread.holdsLock(this);
00471 if (dx == 0 && dy == 0) {
00472 return;
00473 }
00474
00475 minX += dx;
00476 maxX += dx;
00477 minY += dy;
00478 maxY += dy;
00479 patchX += dx;
00480 patchY += dy;
00481 }
00482
00490 @NotNull
00491 private CfMapPatch expandTo(final int x, final int y) {
00492 if (minX > maxX || minY > maxY) {
00493
00494 minX = x;
00495 maxX = x;
00496 minY = y;
00497 maxY = y;
00498 minPx = (x-patchX)>>CfMapPatch.SIZE_LOG;
00499 maxPx = minPx;
00500 minPy = (y-patchY)>>CfMapPatch.SIZE_LOG;
00501 maxPy = minPy;
00502 patch = new CfMapPatch[1][1];
00503
00504 patch[0][0] = null;
00505 } else {
00506 if (x < minX) {
00507 increase(x-minX, 0);
00508 }
00509 if (x > maxX) {
00510 increase(x-maxX, 0);
00511 }
00512 if (y < minY) {
00513 increase(0, y-minY);
00514 }
00515 if (y > maxY) {
00516 increase(0, y-maxY);
00517 }
00518 }
00519
00520 final CfMapPatch result = getMapPatch(x, y);
00521 assert result != null;
00522 return result;
00523 }
00524
00532 private void increase(final int dx, final int dy) {
00533 if (dx < 0) {
00534 final int newMinX = minX+dx;
00535 final int newMinPx = (newMinX-patchX)>>CfMapPatch.SIZE_LOG;
00536 final int diffPw = minPx-newMinPx;
00537 if (diffPw == 0) {
00538
00539
00540 minX = newMinX;
00541 } else {
00542
00543
00544 assert diffPw > 0;
00545
00546 final int newPw = size(newMinPx, maxPx);
00547 final int newPh = size(minPy, maxPy);
00548 assert patch != null;
00549 final int oldPw = patch.length;
00550 final int oldPh = patch[0].length;
00551
00552
00553 assert newPw > oldPw;
00554 assert newPw == oldPw+diffPw;
00555 assert newPh == oldPh;
00556
00557 minX = newMinX;
00558 minPx = newMinPx;
00559 patch = copyPatches(newPw, newPh, diffPw, 0, oldPw, oldPh);
00560 }
00561 } else if (dx > 0) {
00562 final int newMaxX = maxX+dx;
00563 final int newMaxPx = (newMaxX-patchX)>>CfMapPatch.SIZE_LOG;
00564 final int diffPw = newMaxPx-maxPx;
00565 if (diffPw == 0) {
00566
00567
00568 maxX = newMaxX;
00569 } else {
00570
00571
00572 assert diffPw > 0;
00573
00574 final int newPw = size(minPx, newMaxPx);
00575 final int newPh = size(minPy, maxPy);
00576 assert patch != null;
00577 final int oldPw = patch.length;
00578 final int oldPh = patch[0].length;
00579
00580
00581 assert newPw > oldPw;
00582 assert newPw == oldPw+diffPw;
00583 assert newPh == oldPh;
00584
00585 maxX = newMaxX;
00586 maxPx = newMaxPx;
00587 patch = copyPatches(newPw, newPh, 0, 0, oldPw, oldPh);
00588 }
00589 }
00590
00591 if (dy < 0) {
00592 final int newMinY = minY+dy;
00593 final int newMinPy = (newMinY-patchY)>>CfMapPatch.SIZE_LOG;
00594 final int diffPh = minPy-newMinPy;
00595 if (diffPh == 0) {
00596
00597
00598 minY = newMinY;
00599 } else {
00600
00601
00602 assert diffPh > 0;
00603
00604 final int newPw = size(minPx, maxPx);
00605 final int newPh = size(newMinPy, maxPy);
00606 assert patch != null;
00607 final int oldPw = patch.length;
00608 final int oldPh = patch[0].length;
00609
00610
00611 assert newPh > oldPh;
00612 assert newPh == oldPh+diffPh;
00613 assert newPw == oldPw;
00614
00615 minY = newMinY;
00616 minPy = newMinPy;
00617 patch = copyPatches(newPw, newPh, 0, diffPh, oldPw, oldPh);
00618 }
00619 } else if (dy > 0) {
00620 final int newMaxY = maxY+dy;
00621 final int newMaxPy = (newMaxY-patchY)>>CfMapPatch.SIZE_LOG;
00622 final int diffPh = newMaxPy-maxPy;
00623 if (diffPh == 0) {
00624
00625
00626 maxY = newMaxY;
00627 } else {
00628
00629
00630 assert diffPh > 0;
00631
00632 final int newPw = size(minPx, maxPx);
00633 final int newPh = size(minPy, newMaxPy);
00634 assert patch != null;
00635 final int oldPw = patch.length;
00636 final int oldPh = patch[0].length;
00637
00638
00639 assert newPh > oldPh;
00640 assert newPh == oldPh+diffPh;
00641 assert newPw == oldPw;
00642
00643 maxY = newMaxY;
00644 maxPy = newMaxPy;
00645 patch = copyPatches(newPw, newPh, 0, 0, oldPw, oldPh);
00646 }
00647 }
00648 }
00649
00657 private static int size(final int min, final int max) {
00658 return max-min+1;
00659 }
00660
00667 @NotNull
00668 public CfMapSquare getMapSquare(final int x, final int y) {
00669 assert Thread.holdsLock(this);
00670 return expandTo(x, y).getSquare(ox, oy);
00671 }
00672
00679 @Nullable
00680 public CfMapSquare getMapSquareUnlessDirty(final int x, final int y) {
00681 assert Thread.holdsLock(this);
00682 final CfMapSquare mapSquare = getMapSquare(x, y);
00683 return dirtyMapSquares.contains(mapSquare) ? null : mapSquare;
00684 }
00685
00691 public int getOffsetX() {
00692 assert Thread.holdsLock(this);
00693 return patchX;
00694 }
00695
00701 public int getOffsetY() {
00702 assert Thread.holdsLock(this);
00703 return patchY;
00704 }
00705
00716 @NotNull
00717 private CfMapPatch[][] copyPatches(final int newWidth, final int newHeight, final int offsetX, final int offsetY, final int height, final int width) {
00718 assert patch != null;
00719 final CfMapPatch[][] newPatch = new CfMapPatch[newWidth][newHeight];
00720 for (int y = 0; y < width; y++) {
00721 for (int x = 0; x < height; x++) {
00722 newPatch[offsetX+x][offsetY+y] = patch[x][y];
00723 }
00724 }
00725 return newPatch;
00726 }
00727
00732 public void squareModified(@NotNull final CfMapSquare mapSquare) {
00733 assert Thread.holdsLock(this);
00734 dirtyMapSquares.add(mapSquare);
00735 }
00736
00743 public void squarePendingFace(final int x, final int y, final int faceNum) {
00744 assert Thread.holdsLock(this);
00745 final Integer tmpFaceNum = faceNum;
00746 Collection<CfMapSquare> mapSquares = pendingFaceSquares.get(tmpFaceNum);
00747 if (mapSquares == null) {
00748 mapSquares = new HashSet<CfMapSquare>();
00749 pendingFaceSquares.put(tmpFaceNum, mapSquares);
00750 }
00751 mapSquares.add(getMapSquare(x, y));
00752 }
00753
00758 @NotNull
00759 public Set<CfMapSquare> getDirtyMapSquares() {
00760 assert Thread.holdsLock(this);
00761 final Set<CfMapSquare> result = new HashSet<CfMapSquare>(dirtyMapSquares);
00762 dirtyMapSquares.clear();
00763 return result;
00764 }
00765
00772 public void updateFace(final int faceNum, final int width, final int height) {
00773 for (int y = 0; y < height; y++) {
00774 for (int x = 0; x < width; x++) {
00775 for (int layer = 0; layer < Map2.NUM_LAYERS; layer++) {
00776 final Face face = getFace(x, y, layer);
00777 if (face != null && face.getFaceNum() == faceNum) {
00778 setFace(x, y, layer, face);
00779 dirty(x, y);
00780 }
00781 }
00782 }
00783 }
00784
00785 final Collection<CfMapSquare> mapSquares = pendingFaceSquares.remove(faceNum);
00786 if (mapSquares != null) {
00787 dirtyMapSquares.addAll(mapSquares);
00788 }
00789 }
00790
00799 public boolean processMapScroll(final int dx, final int dy, final int width, final int height) {
00800 if (Math.abs(dx) >= width || Math.abs(dy) >= height) {
00801 scroll(dx, dy);
00802 for (int y = 0; y < height; y++) {
00803 for (int x = 0; x < width; x++) {
00804 clearSquare(x, y);
00805 }
00806 }
00807
00808 return true;
00809 }
00810
00811 int tx = dx;
00812 while (tx > 0) {
00813 scroll(-1, 0);
00814 for (int y = 0; y < height; y++) {
00815 clearSquare(-1, y);
00816 clearSquare(width-1, y);
00817 }
00818 tx--;
00819 }
00820 while (tx < 0) {
00821 scroll(+1, 0);
00822 for (int y = 0; y < height; y++) {
00823 clearSquare(0, y);
00824 clearSquare(width, y);
00825 }
00826 tx++;
00827 }
00828
00829 int ty = dy;
00830 while (ty > 0) {
00831 scroll(0, -1);
00832 for (int x = 0; x < width; x++) {
00833 clearSquare(x, -1);
00834 clearSquare(x, height-1);
00835 }
00836 ty--;
00837 }
00838 while (ty < 0) {
00839 scroll(0, +1);
00840 for (int x = 0; x <= width; x++) {
00841 clearSquare(x, 0);
00842 clearSquare(x, height);
00843 }
00844 ty++;
00845 }
00846
00847 return false;
00848 }
00849
00850 }