Index: MapWin.java
===================================================================
RCS file: /cvsroot/jcrossclient/jcrossclient/MapWin.java,v
retrieving revision 1.4
retrieving revision 1.5
diff -u -r1.4 -r1.5
--- MapWin.java 1 Dec 2005 18:55:48 -0000 1.4
+++ MapWin.java 13 Jan 2006 01:56:19 -0000 1.5
@@ -1,784 +1,882 @@
/*
- * Copyright (c) 1997, Phil Brown, phil@bolthole.com
- * Copyright (c) 2005, Brendan Lally, brenlally@gmail.com
- *
- * This file is part of jcrossclient.
- *
- * Jcrossclient is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 2 of the License, or (at your option) any later version.
- *
- * Jcrossclient is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
- * PARTICULAR PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * Jcrossclient; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
- */
-
-/* Notes about our "Map":
- * We have a multi-level display going here.
- * At the back end, there is a 2d array of (VECTOR of pixmapID),
- * called Cells[][]
- * This gets updated when we read in a map string, in mapRead(String)
- * We also keep a "dirty" 2d array, of Cells that have been changed, not drawn.
- *
- * updateMap() draws cells that have changed, to our fullscreen Image buffer
- * drawMap() draws ALL cells, changed or not, to the buffer
- *
- * Finally, paint() actually draws our buffer to the screen.
- *
- * We only really need to sync on updating "fullmap", I think?!
- * So, anything that draws with fullmapgc
- */
+ Copyright (c) 1997, Phil Brown, phil@bolthole.com
+ Copyright (c) 2005, Brendan Lally, brenlally@gmail.com
+ This file is part of jcrossclient.
+ Jcrossclient is free software; you can redistribute it and/or modify it under
+ the terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 2 of the License, or (at your option) any later version.
+ Jcrossclient is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ You should have received a copy of the GNU General Public License along with
+ Jcrossclient; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+/*
+ Notes about our "Map":
+ We have a multi-level display going here.
+ At the back end, there is a 2d array of (Vector of MapSquare),
+ called Cells[][]
+ This gets updated when we read in a map string, in mapRead(String)
+ Each MapSquare also contains a "dirty" flag, indicating if it has been changed but not drawn.
+ updateMap() draws cells that have changed to our fullscreen Image buffer
+ drawMap() draws ALL cells, changed or not, to the buffer
+ Finally, paint() actually draws our buffer to the screen.
+ We only really need to sync on updating "fullmap", I think?!
+ So, anything that draws with fullmapgc
+ */
-import java.util.Vector;
-import java.lang.Integer;
+import java.util.LinkedList;
+import java.util.Iterator;
import java.awt.*;
import java.awt.event.*;
-import java.awt.AWTEvent;
import java.io.*;
-import java.awt.image.BufferedImage;
-/*Here's the plan:
- Only actually update the map window every X milliseconds.
- But allow outside folks to add to the information.
- Need to have a locking mechanism, I guess.
-*/
+/*
+ Here's the plan:
+ Only actually update the map window every X milliseconds.
+ But allow outside folks to add to the information.
+ Need to have a locking mechanism, I guess.
+ */
-
-class MapUpdater extends Thread
-{
+class MapUpdater extends Thread {
MapWin mapwin;
CFclient toplevel;
- static int sleepms=10;
+ static int sleepms = 10;
// note: this is in milliseconds
- public void setSleepDelay(int sleep)
- {
- if(sleep<10) sleep=10;
- if(sleep>1000) sleep=1000;
- sleepms=sleep;
- }
- public void run()
- {
- mapwin.assign_cells();
- while(mapwin.goodToGo() == false)
- {
- try
- {
+
+ public void setSleepDelay(int sleep) {
+ if (sleep < 10) {
+ sleep = 10;
+ }
+ if (sleep > 1000) {
+ sleep = 1000;
+ }
+ sleepms = sleep;
+ }
+
+ public void run() {
+ mapwin.assignCells();
+ while (mapwin.goodToGo() == false) {
+ try {
Thread.sleep(100);
- }catch (Exception err) { }
+ } catch (Exception err) {}
}
System.out.println("Map update thread good to go!");
- while (true)
- {
- //System.out.println("m");
- //mapwin.drawMap();
-
- if(mapwin.updateMap())
+ while (true) {
+ if (mapwin.updateMap()) {
mapwin.repaint();
-
+ }
toplevel.drawFloor();
- try
- {
+ try {
Thread.sleep(sleepms);
//Thread.yield();
- }catch (Exception err)
- {
- System.out.println("MapUpdater interrupted:"+
- err.getMessage());
}
-
+ catch (Exception err) {
+ System.out.println("MapUpdater interrupted:" +
+ err.getMessage());
+ }
+
}
-
+
}
- public MapUpdater(MapWin parent, CFclient top)
- {
- mapwin=parent;
+
+
+ public MapUpdater(MapWin parent, CFclient top) {
+ mapwin = parent;
toplevel = top;
-
+
}
}
-class Map_Square
-{
- public static final int NUMBER_OF_LAYERS=3; /* ugly and hardcoded*/
+class MapSquare {
+
+
+ // ugly and hardcoded
+ public final static int NUMBER_OF_LAYERS = 3;
private int faces[] = new int[NUMBER_OF_LAYERS];
- /* these are the number of tiles we are into the image, starting at zero */
- private int x_size[] = new int[NUMBER_OF_LAYERS];
- private int y_size[] = new int[NUMBER_OF_LAYERS];
+ private int xSize[] = new int[NUMBER_OF_LAYERS];
+ private int ySize[] = new int[NUMBER_OF_LAYERS];
private boolean offmap[] = new boolean[NUMBER_OF_LAYERS];
- private boolean needs_updating;
private int darkness;
-
- public Map_Square()
- {
+
+ public MapSquare() {
faces = new int[NUMBER_OF_LAYERS];
- needs_updating=false;
darkness = -1;
}
-
- /** returns true on success, false on failure */
- public synchronized void set_face(int face, int layer) {
- if (layer > NUMBER_OF_LAYERS || layer < 1)
+
+ public synchronized void setFace(int face, int layer) {
+ if (layer > NUMBER_OF_LAYERS || layer < 1) {
+ return;
+ }
+ if (face == -1) {
return;
- if (face == -1) return;
- faces[layer-1] = face;
- needs_updating = true;
- x_size[layer-1] = 1;
- y_size[layer-1] = 1;
+ }
+ faces[layer - 1] = face;
+ xSize[layer - 1] = 1;
+ ySize[layer - 1] = 1;
}
- public synchronized void set_face(int face, int layer, int x, int y, boolean off_map) {
- if (layer > NUMBER_OF_LAYERS || layer < 1)
+
+ public synchronized void setFace(int face, int layer,
+ int x, int y, boolean off_map) {
+ if (layer > NUMBER_OF_LAYERS || layer < 1) {
+ return;
+ }
+ if (face == -1) {
return;
- if (face == -1) return;
- faces[layer-1] = face;
- needs_updating = true;
- x_size[layer-1] = x;
- y_size[layer-1] = y;
- offmap[layer-1] = off_map;
+ }
+ faces[layer - 1] = face;
+ xSize[layer - 1] = x;
+ ySize[layer - 1] = y;
+ offmap[layer - 1] = off_map;
}
- public synchronized int get_face(int layer) {
- if (layer > NUMBER_OF_LAYERS || layer < 1)
+ public synchronized int getFace(int layer) {
+ if (layer > NUMBER_OF_LAYERS || layer < 1) {
return 0;
- return this.faces[layer-1];
+ }
+ return this.faces[layer - 1];
}
- public synchronized int get_x_size(int layer) {
- if (layer > NUMBER_OF_LAYERS || layer < 1)
+ public synchronized int getXSize(int layer) {
+ if (layer > NUMBER_OF_LAYERS || layer < 1) {
return 1;
- return x_size[layer-1];
+ }
+ return xSize[layer - 1];
}
- public synchronized int get_y_size(int layer) {
- if (layer > NUMBER_OF_LAYERS || layer < 1)
+ public synchronized int getYSize(int layer) {
+ if (layer > NUMBER_OF_LAYERS || layer < 1) {
return 1;
- return y_size[layer-1];
+ }
+ return ySize[layer - 1];
}
- /* clones the square from this to target, sets it to need updating */
- public synchronized void copy(Map_Square target) {
- for (int i=0;i<NUMBER_OF_LAYERS;i++) {
- if (!offmap[i])
- target.set_face(this.faces[i], i+1, x_size[i], y_size[i], false);
- }
- target.set_darkness(this.darkness); /*FIXME*/
- target.is_dirty();
- this.needs_updating = true;
- }
-
- /** if this square contains this face, we need to redraw it */
- public synchronized void face_is_dirty(int dirty_face) {
- int i;
- for (i=0;i<NUMBER_OF_LAYERS;i++)
- if (faces[i] == dirty_face)
- needs_updating=true;
+ // return true iff this square contains any layer bigger than 1x1
+
+ public synchronized boolean isBig() {
+ for (int i = 0; i < NUMBER_OF_LAYERS; i++) {
+ if ((xSize[i] > 1) || (ySize[i] > 1)) {
+ return true;
+ }
+ }
+ return false;
}
- public synchronized void clear() {
- int i;
- for (i=0;i<NUMBER_OF_LAYERS;i++) {
- x_size[i] = 1;
- y_size[i] = 1;
- faces[i]=0;
+
+ // return the largest x dimension of all the layers in the square
+
+ public synchronized int biggestX() {
+ int maxX = 0;
+ for (int i = 0; i < NUMBER_OF_LAYERS; i++) {
+ if (xSize[i] > maxX) {
+ maxX = xSize[i];
+ }
+ }
+ return maxX;
+ }
+
+
+ // return the largest Y dimension of all the layers in the square
+
+ public synchronized int biggestY() {
+ int maxY = 0;
+ for (int i = 0; i < NUMBER_OF_LAYERS; i++) {
+ if (ySize[i] > maxY) {
+ maxY = ySize[i];
+ }
+ }
+ return maxY;
+ }
+
+
+ /*
+ clones the square from this to target
+ */
+
+ public synchronized void copyTo(MapSquare target) {
+ for (int i = 0; i < NUMBER_OF_LAYERS; i++) {
+ target.setFace(this.faces[i], i + 1,
+ xSize[i], ySize[i], false);
}
- this.darkness =-1;
- this.needs_updating=true;
+ target.setDarkness(this.darkness);
+ /*
+ FIXME
+ */
}
- public synchronized void is_dirty() {
- this.needs_updating=true;
+
+ /**
+ * if this square contains this face, we need to redraw it
+ *
+ * @param dirty_face Description of the Parameter
+ */
+ public synchronized boolean containsFace(int face) {
+
+ for (int i = 0; i < NUMBER_OF_LAYERS; i++) {
+ if (faces[i] == face) {
+ return true;
+ }
+ }
+ return false;
}
- public synchronized boolean should_update() {
- return this.needs_updating;
+
+
+ public synchronized void clear() {
+ for (int i = 0; i < NUMBER_OF_LAYERS; i++) {
+ xSize[i] = 1;
+ ySize[i] = 1;
+ faces[i] = 0;
+ }
+ this.darkness = -1;
}
- public static int get_layer_count() {
+
+
+ public static int getLayerCount() {
return NUMBER_OF_LAYERS;
}
- public synchronized void was_updated() {
- this.needs_updating = false;
- }
- public synchronized int get_darkness() {
+
+
+ public synchronized int getDarkness() {
return this.darkness;
}
- public synchronized boolean set_darkness(int newvalue) {
- if (newvalue >=0 && newvalue < 256) {
- this.darkness=newvalue;
+
+
+ public synchronized boolean setDarkness(int newvalue) {
+ if (newvalue >= 0 && newvalue < 256) {
+ this.darkness = newvalue;
return true;
}
return false;
}
}
+/*
+ A class representing rectangles of MapSquares. Used in MapWin to keep track
+ of regions that need to be redrawn.
+ */
+class Rectangle {
+
+ public int x1;
+ public int x2;
+ public int y1;
+ public int y2;
+
+ public Rectangle(int newX1, int newY1,
+ int newX2, int newY2) {
+ x1 = newX1;
+ x2 = newX2;
+ y1 = newY1;
+ y2 = newY2;
+ }
+
+ // return whether this rectangle intersects
+ // another given rectangle
+
+ public boolean intersects(Rectangle other) {
+ return !((x1 > other.x2) || (y1 > other.y2)
+ || (x2 < other.x1) || (y2 < other.y1));
+ }
+}
+
+public class MapWin extends Canvas implements MouseListener, ActionListener {
-public class MapWin extends Canvas implements MouseListener,ActionListener
-{
- public final int MAX_FACE_SIZE=8; /* the biggest height or width any face can have */
- int stupidlock;
+ /*
+ the biggest height or width any face can have
+ */
+
+ public static final int MAX_FACE_SIZE = 8;
MapUpdater mapthread;
- public static int pixwidth=32, pixheight=32;
+ public static final int pixwidth = 32, pixheight = 32;
// okay, ugly...
CFclient Parent;
- int PlayerImageNum=-1;
+ int PlayerImageNum = -1;
Image PlayerImage;
- final int XMAX=15, YMAX=15;
- final int BUFFERX = XMAX + MAX_FACE_SIZE;
- final int BUFFERY = YMAX + MAX_FACE_SIZE;
-
- Map_Square Cells[][];
- Image fullmap=null;
- Graphics fullmapgc=null; // this gc exists solely to paint to fullmap
+ public static final int XMAX = 15, YMAX = 15;
+ public static final int BUFFERX = XMAX + MAX_FACE_SIZE;
+ public static final int BUFFERY = YMAX + MAX_FACE_SIZE;
- boolean debugflag=false;
-
- public void assign_cells(){
- /* java is a little speshul here, and won't size this correctly until we enter a method....*/
- Cells = new Map_Square[BUFFERX][BUFFERY];
- for (int i=0;i<BUFFERX;i++) {
- for (int j=0;j<BUFFERY;j++) {
- Cells[i][j]=new Map_Square();
+ MapSquare Cells[][];
+ LinkedList dirtyRectangles = new LinkedList();
+ Image fullmap = null;
+ Graphics fullmapgc = null;
+ // this gc exists solely to paint to fullmap
+
+ boolean debugflag = false;
+
+
+ public void assignCells() {
+ /*
+ java is a little speshul here, and won't size this correctly
+ until we enter a method....
+ */
+ Cells = new MapSquare[BUFFERX][BUFFERY];
+ for (int i = 0; i < BUFFERX; i++) {
+ for (int j = 0; j < BUFFERY; j++) {
+ Cells[i][j] = new MapSquare();
}
}
}
- void debug(String str)
- {
- if(debugflag)
+
+
+ void debug(String str) {
+ if (debugflag) {
System.out.println(str);
+ }
}
- void print(String str)
- {
- System.out.println(str);
- }
- public void keyPressed(KeyEvent evt){ }
+ public void keyPressed(KeyEvent evt) { }
+
+
+ public void keyReleased(KeyEvent evt) { }
- public void keyReleased(KeyEvent evt){ }
- public void keyTyped(KeyEvent evt) { }
+ public void keyTyped(KeyEvent evt) { }
- public void actionPerformed(ActionEvent evt)
- {
- String time=evt.getActionCommand();
- int ms=Integer.parseInt(time);
- System.out.println("Set map delay to "+ms+" ms.");
+
+ public void actionPerformed(ActionEvent evt) {
+ String time = evt.getActionCommand();
+ int ms = Integer.parseInt(time);
+ System.out.println("Set map delay to " + ms + " ms.");
mapthread.setSleepDelay(ms);
-
+
}
- public void changeUpdateDelay(int ms)
- {
- System.out.println("Setting map delay to "+ms+" ms.");
+
+ public void changeUpdateDelay(int ms) {
+ System.out.println("Setting map delay to " + ms + " ms.");
mapthread.setSleepDelay(ms);
}
+
// not used right now, but could be in future.
- public void wakeupMapUpdater()
- {
+ public void wakeupMapUpdater() {
mapthread.interrupt();
}
- /************************************************************/
+ // this exists solely for the MapUpdater to spin-lock on
- /*public int TwoByteConv(String str, int start)
- {
- int val;
- val=(int)str.charAt(start) <<8;
- val+=(int)str.charAt(start+1);
-
- return val;
- }*/
-
- // Quick and easy way to say "next redraw, draw ALL CELLS again"
- public void markAllUpdate()
- {
- for(int y=0; y<BUFFERY; y++)
- {
- for(int x=0; x<BUFFERX; x++)
- {
- Cells[x][y].is_dirty();
- }
- }
- }
- public void DebugMode()
- {
- System.out.println("TESTING: ignoring MapWin.DebugMode");
- debugflag=false; return;
-
- //debugflag=!debugflag;
+ public boolean goodToGo() {
+ return (fullmapgc != null);
}
- // this exists soley for the MapUpdater to spin-lock on
- public boolean goodToGo()
- {
- if(fullmapgc==null)
- return false;
- return true;
- }
+
// I'd love to call this in the constructor, but
+
// the createImage() thing only seems to work after
- // we are truely visible. sigh.
+ // we are truly visible. sigh.
// May as well wait until here to start up map update thread,
// since it depends on this working, anyways..
- void initBackBuffer()
- {
+
+ void initBackBuffer() {
debug("initializing map backbuffer");
- fullmap=createImage(XMAX*pixwidth,YMAX*pixheight);
- fullmapgc=fullmap.getGraphics();
- if(fullmap!=null)
+ fullmap = createImage(XMAX * pixwidth, YMAX * pixheight);
+ fullmapgc = fullmap.getGraphics();
+ if (fullmap != null) {
debug("got fullmap");
- else
+ } else {
debug("FAILED to init fullmap image");
- if(fullmapgc != null) {
+ }
+ if (fullmapgc != null) {
debug("got fullmapgc");
- fullmapgc.clearRect(0,0,
- XMAX*pixwidth,
- YMAX*pixheight);
+ fullmapgc.clearRect(0, 0,
+ XMAX * pixwidth,
+ YMAX * pixheight);
fullmapgc.drawString("Waiting for images...",
- 5+MAX_FACE_SIZE*pixwidth, YMAX*pixheight/2);
- }
- else {
+ 5 + MAX_FACE_SIZE * pixwidth,
+ YMAX * pixheight / 2);
+ } else {
debug("FAILED to init fullmapgc");
}
}
-
- /* This routine renders ONLY map squares that have changed, to
- * our back buffer. It also updates the need_redraw[][] at the
- * same time, by way of calling paintCell()
- *
- * It does NOT DRAW TO THE USER'S SCREEN
- *
- * It returns true if we actually changed anything,
- * false if nothing changed
- * This has been hacked to call draw map instead, this is slow,
- * inefficient and above all, easy.
- */
- synchronized boolean updateMap() {
- drawMap();
- return true;/*
- //debug("updateMap() started");
- if(fullmap == null) {
- debug("Calling initBackBuffer in updateMap()");
- initBackBuffer();
- }
- // The problem isn't actually here, as shown by dumpdirty.
- // the problem is that something, somewhere, is somehow
- // writing to the SCREEN, when it has no business
- // doing so. and making black rectangles.
- // But it somehow seems related to line-of-sight issues.
- // very confusing!!
+ /* return a rectangle clipped to the viewport
+ */
- /*
- if(debugflag)
- {
- debug("in updateMap(), before actual redraw, squares needing redraw are:");
- dumpdirty();
- }
- */
- /*
- boolean changed=false;
- for(int y = 0; y < YMAX; y++) {
- for(int x = 0; x < XMAX; x++) {
- if(Cells[x][y].should_update()) {
- changed=true;
- paintCell(fullmapgc, x, y);
- }
- }
- }
-
-
- return changed;*/
- }
-
- // mark all cells with this face as "dirty", so on the
- // next updateMap(), they get redrawn
- void dirtyFace(int face)
- {
- for(int y=0; y<BUFFERY; y++)
- {
- for(int x=0; x<BUFFERX; x++)
- {
- Cells[x][y].face_is_dirty(face);
- }
- }
+ public static Rectangle clippedRectangle(int x1, int y1, int x2, int y2){
+ return new Rectangle(Math.max(0, x1), Math.max(0, y1),
+ Math.min(BUFFERX - 1, x2), Math.min(BUFFERY - 1, y2) );
}
-
- /* called by MapScrollCmd, in CFclient
- * scroll the map!
- * if x_change >0, means move pixmaps this way <<==
- * etc, etc
- *
- * What we will do is copy the facenumbers across, from one cell to the
- * one that it should be, then we will clear the cell, and mark both dirty
- */
- public synchronized void mapScroll(int x_change, int y_change)
- {
- int i,j;
- /* this isn't a particularly nice way to do this, but it should
- * be quite fast. */
- if (x_change >= 0) {
- if (y_change >= 0) {
- for (i=x_change;i<BUFFERX;i++) {
- for (j=y_change;j<BUFFERY;j++) {
- Cells[i][j].copy(Cells[i-x_change][j-y_change]);
- Cells[i][j].clear();
- }
- }
- }
- else {
- for (i=x_change;i<BUFFERX;i++) {
- for (j=BUFFERY+y_change-1;j>=0;j--) {
- Cells[i][j].copy(Cells[i-x_change][j-y_change]);
- Cells[i][j].clear();
- }
- }
+
+ /* mark a region of MapSquares as needing to be redrawn */
+
+ public void addDirtyRectangle(Rectangle r) {
+ Iterator iter = dirtyRectangles.listIterator();
+ boolean intersected = false;
+ while (iter.hasNext() && !intersected) {
+ Rectangle nextRect = (Rectangle) iter.next();
+ if (r.intersects(nextRect)) {
+ iter.remove();
+ addCombined(r, nextRect);
+ intersected = true;
}
}
- else {
- if (y_change >= 0) {
- for (i=BUFFERX+x_change-1;i>=0;i--) {
- for (j=y_change;j<BUFFERY;j++) {
- Cells[i][j].copy(Cells[i-x_change][j-y_change]);
- Cells[i][j].clear();
- }
- }
- }
- else {
- for (i=BUFFERX+x_change-1;i>=0;i--) {
- for (j=BUFFERY+y_change-1;j>=0;j--) {
- Cells[i][j].copy(Cells[i-x_change][j-y_change]);
- Cells[i][j].clear();
- }
- }
- }
+ if (!intersected) {
+ dirtyRectangles.add(r);
}
- /* having gone through all of that, lets make it completely pointless,
- * by doing the stupidest, most inefficiant thing we could possibly do here */
- updateMap();
}
- // run through and see if there are any 'dirty' map squares
- // that need to be written out to back buffer
- boolean needUpdate() {
- for (int y=0; y<BUFFERY; y++) {
- for (int x=0; x<BUFFERX; x++) {
- if(Cells[x][y].should_update()==true)
- return true;
- }
- }
- return false;
- }
- // quickly say if each cell supposedly needs a refresh or not
- void dumpdirty()
- {
- for(int y=0; y<BUFFERY; y++)
- {
- StringBuffer line = new StringBuffer();
- for(int x=0; x<BUFFERX; x++)
- {
- if(Cells[x][y].should_update()==true)
- {
- line.append("X ");
- } else {
- line.append("0 ");
- }
- }
- System.out.println(line);
- }
- }
-
- /* called by "Map1aCmd" in CFclient
- * This processes a map update from the server
- * This function is a mutation of the original mapRead and the map code
- * from the mainstream cf client, and hacked to vaguely compile. Consider
- * it a study in how to write C in an inferior language, look at the C
- * client's common/commands.c for what this function wants to look like.
- */
- public synchronized void map1aRead(DataInputStream mapdata) throws IOException {
- //System.out.println("map1aRead now called");
- int maplen = mapdata.available();
- boolean bottom = true;
- int mask;
- int x, y, layer, darkness;
- int faces[] = new int[3];
- int pos = 0;
- int x_size;
- int y_size;
- Image next_face;
- int NUM_LAYERS=2; /* this should be defined somewhere nicer */
- //System.out.println("maplen is " + maplen);
- while ( pos < maplen ) {
- // System.out.println("reading a new tile");
- bottom = true;
- //place = str.charAt(parse++);
- mask = (int) mapdata.readUnsignedShort();
- pos+=2;
- // System.out.println("mask is " + mask);
-
- x = (mask >> 10) & 0x3f;;
- y = (mask >> 4) & 0x3f;;
- // System.out.println("tile is at " + x + ":" + y);
-
- if ((mask & 0xf) == 0) {
- //Cells[x][y].clear();
- /* by commenting this out, we get something close to fog of war support */
- continue; /* if empty mask, none of the checks will be true. */
- }
- /* If this space was previously stored for fog of war, need to clear
- * it now. Needs to be done before anything else happens that we
- * might want to store in it.
- */
-
- if ((mask & 0x8) != 0 ) { /* darkness bit */
- darkness = mapdata.readUnsignedByte();
- pos+=1;
- // System.out.println("darkness value is set, it is " + darkness);
- }
- else darkness = -1;
-
- /* Reduce redundant by putting the get image
- * and flags in a common block. The layers
- * are the inverse of the bit order unfortunately.
- */
- for (layer=NUM_LAYERS; layer>=0; layer--) {
- if ((mask & (1 << layer)) != 0) {
- faces[NUM_LAYERS-layer] = mapdata.readUnsignedShort();
- pos+=2;
- }
- else {
- faces[NUM_LAYERS-layer] = -1;
- }
- }
- /* if you value your sanity, just give up on trying to follow this */
- for (layer=0; layer<=NUM_LAYERS; layer++) {
- if (faces[layer] < 0) continue;
- next_face=Parent.getPixmap(faces[layer]);
- /* had we an image < 32 pixels, the int division would give zero, but we will
- * use 1 square at least.*/
- x_size=Math.max(1, next_face.getWidth(null)/pixwidth);
- y_size=Math.max(1, next_face.getHeight(null)/pixheight);
- if (x_size > 1 || y_size > 1) {
- boolean is_off_map =false;
- if (x > XMAX || y > YMAX) is_off_map = true;
- /* the faces are sent to their bottom right corner, but we store them
- * in the top left corner, so we need to figure out what the new cell is */
- int x_coordinate = (x+1)-x_size;
- int y_coordinate = (y+1)-y_size;
-
- Cells[x_coordinate+MAX_FACE_SIZE][y_coordinate+MAX_FACE_SIZE].set_face(
- faces[layer], layer+1, x_size, y_size, is_off_map);
- }
- else if (x<XMAX && y< YMAX)
- Cells[x+MAX_FACE_SIZE][y+MAX_FACE_SIZE].set_face(faces[layer], layer+1);
- }
- }
+ /*
+ given two intersecting rectangles, add a big rectangle that
+ covers both
+ */
+
+ public void addCombined(Rectangle r1, Rectangle r2) {
+
+ addDirtyRectangle(new Rectangle(Math.min(r1.x1, r2.x1), Math.min(r1.y1, r2.y1),
+ Math.max(r1.x2, r2.x2), Math.max(r1.y2, r2.y2) ) );
}
- /*
- * It sucks that we have to register for ALL THESE
- * just to listen for mouse clicks. Sigh.
- */
- public void mouseClicked(MouseEvent evt) {
- int dx = (evt.getX()/pixwidth) - XMAX/2;
- int dy = (evt.getY()/pixheight) - YMAX/2;
- int buttonmask=evt.getModifiers();
-
- if ((buttonmask & InputEvent.BUTTON3_MASK)>0) {
-
- } else if ((buttonmask & InputEvent.BUTTON1_MASK)>0) {
- Parent.lookat(dx, dy);
- }
- }
- public void mouseEntered(MouseEvent evt) {
- }
- public void mouseExited(MouseEvent evt) {
- }
- public void mousePressed(MouseEvent evt) {
- }
- public void mouseReleased(MouseEvent evt) {
- }
-
- // Given a gc, and the CELL (x,y) co-ords, draw
- // all objects in a given cell, or clear that window space if
- // cell is empty
- // Also, set flag that cell has been drawn
- // this is currently broken, I'll fix it later, maybe.
- /*public void paintCell(Graphics gc, int cellx, int celly)
- {
- Map_Square cell = Cells[cellx][celly];
- cell.was_updated();
- gc.clearRect(cellx*pixwidth, celly*pixheight, pixwidth, pixheight);
- // draw all pixmaps that overlay each other,
- // at a particular Cell
- int count=0;
- int imgindex=-1;
- Image img;
- for(count=1; count<=cell.get_layer_count(); count++)
- {
- imgindex = cell.get_face(count);
- if (imgindex <=0) continue;
- img = Parent.getPixmap(imgindex);
- if(img==null)
- {
- debug("We need face "+imgindex);
- // get "?" image
- img=Parent.getPixmap(0);
- // put placeholder, so we dont request it
- // again, then request from server.
- Parent.addPixmap(imgindex,img);
- Parent.askForFace(imgindex);
- }
- gc.drawImage(img, cellx*pixwidth, celly*pixheight,
- this);
- }
- }*/
-
- public void paint_cell_layer(Graphics gc, int cellx, int celly, int layer)
- {
- Map_Square cell = Cells[cellx][celly];
- int imgindex=-1;
- Image img;
- imgindex = cell.get_face(layer);
- if (imgindex <=0) return;
- img = Parent.getPixmap(imgindex);
- if(img==null) {
- debug("We need face "+imgindex);
- // get "?" image
- img=Parent.getPixmap(0);
- // put placeholder, so we dont request it
- // again, then request from server.
- Parent.addPixmap(imgindex,img);
- Parent.askForFace(imgindex);
- }
- gc.drawImage(img, (cellx-MAX_FACE_SIZE)*pixwidth, (celly-MAX_FACE_SIZE)*pixheight, this);
- }
-
- /* This is the routine that can do a FULL refresh of
- * our map
- * It draws every single square we know about, to the back buffer.
- * On the other hand, if you want to just update squares
- * that have changed, use updateMap()
- * the regions that need updating
- */
- public void drawMap() {
- debug("drawMap() called");
-
- if(fullmap==null) {
- debug("calling initBackBuffer in drawMap()");
- initBackBuffer();
- }
- int x,y;
- for (int i = 1; i<=Map_Square.get_layer_count(); i++) {
- for(y=0; y<BUFFERY; y++) {
- for(x=0; x<BUFFERX; x++) {
+ /*
+ write the collection of cells described by the given rectangle
+ to the buffer
+ */
+ public void drawRectangle(Rectangle r) {
+ for (int i = 1; i <= MapSquare.getLayerCount(); i++) {
+ for (int y = r.y1; y <= r.y2; y++) {
+ for (int x = r.x1; x <= r.x2; x++) {
if (i == 1) {
- fullmapgc.clearRect(x*pixwidth, y*pixheight, pixwidth, pixheight);
- Cells[x][y].was_updated();
+ clearCell(x, y);
}
- paint_cell_layer(fullmapgc, x, y, i);
+ paintCellLayer(x, y, i);
}
}
}
}
- /* used when we get a newmap command */
- public void clearmap() {
- for(int y=0; y<BUFFERY; y++) {
- for(int x=0; x<BUFFERX; x++) {
- Cells[x][y].clear();
- }
- }
- }
-
- // we should really make a nice routine that subclasses
- // this and only draws what "needs" to be updated.
- // Then again.. sigh...
- // We never actually call this, due to some odd locking issues
- // about awt I don't understand. But interestingly, it does
- // get called through update(), which gets called by the
- // AWT-default repaint() routine
- //
- // We do all game-driven updates "directly".
- // [see updateMap() or drawMap()]
- public void paint(Graphics gc)
- {
- //debug("MapWin.paint(gc) called");
-
- if(fullmap==null)
- {
- debug("oops.. fullmap null for paint(), trying init");
- initBackBuffer();
- if(fullmap==null)
- {
- debug("oops.. fullmap STILL null?");
- return;
- }
-
- }
-
- gc.drawImage(fullmap, 0,0, this);
- if(PlayerImage==null) return;
- }
- /* This sets the image for the player. We will automatically
- * draw this image at the center of the screen
- */
+ /*
+ This is the routine that can do a FULL refresh of
+ our map
+ It draws every single square we know about to the back buffer.
+ On the other hand, if you want to just update squares
+ that have changed, use updateMap()
+ */
+ public void drawMap() {
+ debug("drawMap() called");
- public void setPlayerImage(int facenum){
- PlayerImage=Parent.getPixmap(facenum);
- PlayerImageNum=facenum;
- debug(" MapWin set PlayerImageNum to "+facenum);
- if(PlayerImage==null){
- debug("(But image is null right now)");
- } else {
- debug(" (Player image NOT null)");
- Cells[MAX_FACE_SIZE + XMAX/2][MAX_FACE_SIZE + YMAX/2].is_dirty();
+ if (fullmap == null) {
+ debug("calling initBackBuffer in drawMap()");
+ initBackBuffer();
}
- }
-
- // Since paint(gc) draws Every Cell, there is no need for
- // the default update() behaviour of clearing the window first
- // interestingly enough, note that repaint() calls this routine
- public void update(Graphics gc)
- {
- paint(gc);
- }
-
- public MapWin(CFclient parent)
- {
- Cells = new Map_Square[XMAX][YMAX];
- Parent=parent;
- setSize(pixwidth*XMAX, pixheight*YMAX);
- setBackground(Color.black);
- setForeground(Color.white);
-
- // These should really be on parent.
- // but again, lets do this for debug now
-
- addKeyListener(Parent);
- addFocusListener(Parent);
-
- addMouseListener(this);
-
- mapthread = new MapUpdater(this, parent);
- mapthread.start();
+ drawRectangle(new Rectangle(0, 0, BUFFERX - 1, BUFFERY - 1) );
+ dirtyRectangles.clear();
}
+ /* Check for cells containing layers bigger than 1x1. For each such cell
+ Check if it overlaps any current dirty rectangles. If it does, add its
+ extent to the dirty rectangles list.
+ */
+ private synchronized void checkBigCells() {
+ for (int y = 0; y < BUFFERY; y++) {
+ for (int x = 0; x < BUFFERX; x++) {
+ if (Cells[x][y].isBig()) {
+ Iterator ri = dirtyRectangles.listIterator();
+ boolean intersected = false;
+ while(ri.hasNext() && (!intersected) ) {
+ Rectangle currentDirty = (Rectangle) ri.next();
+ Rectangle current = clippedRectangle(x, y, Cells[x][y].biggestX() + x - 1,
+ Cells[x][y].biggestY() + y - 1);
+ if (current.intersects(currentDirty) ) {
+ addDirtyRectangle(current);
+ intersected = true;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /*
+ This routine renders ONLY map squares that have changed, to
+ our back buffer. It also clears the dirty rectangles list when it is
+ finished. It returns true if we actually painted anything,
+ false if nothing changed
+ */
+ synchronized boolean updateMap() {
+ if (fullmap == null) {
+ debug("Calling initBackBuffer in updateMap()");
+ initBackBuffer();
+ }
+
+ boolean changed = false;
+ checkBigCells();
+ Iterator di = dirtyRectangles.listIterator();
+ while (di.hasNext()) {
+ changed = true;
+ drawRectangle((Rectangle) di.next());
+ }
+ dirtyRectangles.clear();
+ return changed;
+ }
+
+
+ // mark all cells with this face as "dirty", so on the
+ // next updateMap(), they get redrawn
+
+
+ /*
+ called by MapScrollCmd, in CFclient
+ scroll the map!
+ if x_change >0, means move pixmaps this way <<==
+ etc, etc
+ What we will do is copy the facenumbers across, from one cell to the
+ one that it should be, then we will clear the cell, and mark both dirty
+ */
+
+ public synchronized void mapScroll(int x_change, int y_change) {
+ int i;
+ int j;
+ /*
+ this isn't a particularly nice way to do this, but it should
+ be quite fast.
+ */
+ if (x_change >= 0) {
+ if (y_change >= 0) {
+ for (i = x_change; i < BUFFERX; i++) {
+ for (j = y_change; j < BUFFERY; j++) {
+ Cells[i][j].copyTo(Cells[i - x_change][j - y_change]);
+ Cells[i][j].clear();
+ }
+ }
+ } else {
+ for (i = x_change; i < BUFFERX; i++) {
+ for (j = BUFFERY + y_change - 1; j >= 0; j--) {
+ Cells[i][j].copyTo(Cells[i - x_change][j - y_change]);
+ Cells[i][j].clear();
+ }
+ }
+ }
+ } else {
+ if (y_change >= 0) {
+ for (i = BUFFERX + x_change - 1; i >= 0; i--) {
+ for (j = y_change; j < BUFFERY; j++) {
+ Cells[i][j].copyTo(Cells[i - x_change][j - y_change]);
+ Cells[i][j].clear();
+ }
+ }
+ } else {
+ for (i = BUFFERX + x_change - 1; i >= 0; i--) {
+ for (j = BUFFERY + y_change - 1; j >= 0; j--) {
+ Cells[i][j].copyTo(Cells[i - x_change][j - y_change]);
+ Cells[i][j].clear();
+ }
+ }
+ }
+ }
+ addDirtyRectangle( new Rectangle(0, 0, BUFFERX - 1, BUFFERY - 1) );
+ }
+
+
+ /*
+ called by "Map1aCmd" in CFclient
+ This processes a map update from the server
+ This function is a mutation of the original mapRead and the map code
+ from the mainstream cf client, and hacked to vaguely compile. Consider
+ it a study in how to write C in an inferior language, look at the C
+ client's common/commands.c for what this function wants to look like.
+ */
+ public synchronized void map1aRead(DataInputStream mapdata)
+ throws IOException {
+ final int DARKNESS_MASK = 0x8;
+ final int COORD_MASK = 0x3f;
+ int maplen = mapdata.available();
+ int mask;
+ int x;
+ int y;
+ int layer;
+ int faces[] = new int[MapSquare.NUMBER_OF_LAYERS];
+ int pos = 0;
+ int x_size;
+ int y_size;
+ Image next_face;
+
+ // Layers are 0-indexed here and 1-indexed in MapSquare. Fix this?
+
+ final int NUM_LAYERS = MapSquare.NUMBER_OF_LAYERS - 1;
+ boolean bottom = false;
+ int darkness;
+ while (pos < maplen) {
+ bottom = true;
+ mask = (int) mapdata.readUnsignedShort();
+ pos += 2;
+
+ x = (mask >> 10) & COORD_MASK;
+ y = (mask >> 4) & COORD_MASK;
+
+ if ((mask & 0xf)== 0) {
+ /*
+ by commenting this next line out, we get something
+ close to fog of war support
+ */
+ // Cells[x][y].clear();
+ continue;
+ /*
+ if empty mask, none of the checks will be true.
+ */
+ }
+
+ if ((mask & DARKNESS_MASK) != 0) {
+ /*
+ darkness bit
+ */
+ darkness = mapdata.readUnsignedByte();
+ pos += 1;
+ // System.out.println("darkness value is set, it is " + darkness);
+ }
+ else {
+ darkness = -1;
+ }
+ /* correct? maybe we ought to leave
+ darkness alone
+ */
+
+ /* Reduce redundancy by putting the get image
+ and flags in a common block. The layers
+ are the inverse of the bit order unfortunately.
+ */
+ for (layer = NUM_LAYERS; layer >= 0; layer--) {
+ if ((mask & (1 << layer)) != 0) {
+ faces[NUM_LAYERS - layer]
+ = mapdata.readUnsignedShort();
+ pos += 2;
+ } else {
+ // set this face < 0, so it will be ignored
+ // in the for loop below
+ faces[NUM_LAYERS - layer] = -1;
+ }
+ }
+ /* if you value your sanity, just give up on trying
+ to follow this
+ */
+ for (layer = 0; layer <= NUM_LAYERS; layer++) {
+ if (faces[layer] < 0) {
+ continue;
+ }
+ next_face = Parent.getPixmap(faces[layer]);
+ /*
+ had we an image < 32 pixels, the int
+ division would give zero, but we will use
+ 1 square at least.
+ */
+ int width = next_face.getWidth(null);
+ int height = next_face.getHeight(null);
+ boolean is_off_map = (x > XMAX + MAX_FACE_SIZE
+ || y > YMAX + MAX_FACE_SIZE);
+ x_size = (width % pixwidth == 0 ? 0 : 1)
+ + width / pixwidth;
+ y_size = (height % pixheight == 0 ? 0 : 1)
+ + height / pixheight;
+ if (x_size > 1 || y_size > 1) {
+
+ /*
+ the faces are sent to their bottom
+ right corner, but we store them in
+ the top left corner, so we need to
+ figure out what the new cell is
+ */
+ int left = x + 1 - x_size + MAX_FACE_SIZE;
+ int top = y + 1 - y_size + MAX_FACE_SIZE;
+
+ addDirtyRectangle(clippedRectangle(left, top,
+ Math.max(x, left + Cells[left][top].biggestX() - 1),
+ Math.max(y, top + Cells[left][top].biggestY() - 1) ) );
+ Cells[left][top].setFace(faces[layer],
+ layer + 1, x_size,
+ y_size, is_off_map);
+ } else if ((x < XMAX) && (y < YMAX)) {
+ addDirtyRectangle(clippedRectangle(x + MAX_FACE_SIZE,
+ y + MAX_FACE_SIZE,
+ x + MAX_FACE_SIZE + Cells[x][y].biggestX() - 1,
+ y + MAX_FACE_SIZE + Cells[x][y].biggestY() - 1) );
+ Cells[x + MAX_FACE_SIZE][y + MAX_FACE_SIZE].setFace(faces[layer], layer + 1);
+ }
+ }
+ }
+ }
+
+
+ /*
+ It sucks that we have to register for ALL THESE
+ just to listen for mouse clicks. Sigh.
+ */
+ public void mouseClicked(MouseEvent evt) {
+ int dx = (evt.getX() / pixwidth) - XMAX / 2;
+ int dy = (evt.getY() / pixheight) - YMAX / 2;
+ int buttonmask = evt.getModifiers();
+
+ if ((buttonmask & InputEvent.BUTTON3_MASK) > 0) {}
+ else if ((buttonmask & InputEvent.BUTTON1_MASK) > 0) {
+ Parent.lookat(dx, dy);
+ }
+ }
+
+
+ public void mouseEntered(MouseEvent evt) {}
+
+
+ public void mouseExited(MouseEvent evt) {}
+
+
+ public void mousePressed(MouseEvent evt) {}
+
+
+ public void mouseReleased(MouseEvent evt) {}
+
+
+ // given the cell coords, clear the cell on the
+ // back-buffer
+
+
+
+ public void clearCell(int cellx, int celly) {
+
+ fullmapgc.clearRect((cellx - MAX_FACE_SIZE) * pixwidth,
+ (celly - MAX_FACE_SIZE) * pixheight,
+ pixwidth, pixheight);
+ }
+
+
+ // given the cell coordinates and a layer number
+ // draw a single layer of the cell
+
+ public void paintCellLayer(int cellx, int celly, int layer) {
+ MapSquare cell = Cells[cellx][celly];
+ int imgindex = -1;
+ Image img;
+ imgindex = cell.getFace(layer);
+ if (imgindex <= 0) {
+ return;
+ }
+ img = Parent.getPixmap(imgindex);
+ if (img == null) {
+ debug("We need face " + imgindex);
+ // get "?" image
+ img = Parent.getPixmap(0);
+ // put placeholder, so we dont request it
+ // again, then request from server.
+ Parent.addPixmap(imgindex, img);
+ Parent.askForFace(imgindex);
+ }
+ fullmapgc.drawImage(img, (cellx - MAX_FACE_SIZE) * pixwidth,
+ (celly - MAX_FACE_SIZE) * pixheight,
+ this);
+ }
+
+
+ /*
+ given the coordinates, paint all the layers of a cell, clearing the
+ rectangle first.
+ */
+ public void paintCell(int x, int y) {
+ clearCell(x, y);
+ for (int i = 1; i <= MapSquare.getLayerCount(); i++) {
+ paintCellLayer(x, y, i);
+ }
+ }
+
+
+ /*
+ used when we get a newmap command
+ */
+ public void clearmap() {
+ for (int y = 0; y < BUFFERY; y++) {
+ for (int x = 0; x < BUFFERX; x++) {
+ Cells[x][y].clear();
+ }
+ }
+ addDirtyRectangle(new Rectangle(0, 0, BUFFERX -1 , BUFFERY - 1) );
+ }
+
+
+ /* mark all the squares containing the given face */
+ public synchronized void dirtyFace(int face) {
+ for (int y = 0; y < BUFFERY; y++) {
+ for(int x = 0; x < BUFFERX; x++) {
+ if (Cells[x][y].containsFace(face) ) {
+ addDirtyRectangle(clippedRectangle(x, y, Cells[x][y].biggestX() + x - 1,
+ Cells[x][y].biggestY() + y - 1) );
+ }
+ }
+ }
+ }
+
+ // paint the back-buffer to the screen
+
+ public void paint(Graphics gc) {
+ //debug("MapWin.paint(gc) called");
+
+ if (fullmap == null) {
+ debug("oops.. fullmap null for paint(), trying init");
+ initBackBuffer();
+ if (fullmap == null) {
+ debug("oops.. fullmap STILL null?");
+ return;
+ }
+ }
+
+ gc.drawImage(fullmap, 0, 0, this);
+ }
+
+
+ /*
+ This sets the image for the player. We will automatically
+ draw this image at the center of the screen
+ */
+ public synchronized void setPlayerImage(int facenum) {
+ PlayerImage = Parent.getPixmap(facenum);
+ PlayerImageNum = facenum;
+ debug(" MapWin set PlayerImageNum to " + facenum);
+ if (PlayerImage == null) {
+ debug("(But image is null right now)");
+ } else {
+ debug(" (Player image NOT null)");
+ addDirtyRectangle(new Rectangle(BUFFERX/2, BUFFERY/2, BUFFERX/2, BUFFERY/2) );
+ }
+ }
+
+
+ // Since paint(gc) draws Every Cell, there is no need for
+ // the default update() behaviour of clearing the window first
+
+ public void update(Graphics gc) {
+ paint(gc);
+ }
+
+
+ public MapWin(CFclient parent) {
+ Parent = parent;
+ setSize(pixwidth * XMAX, pixheight * YMAX);
+ setBackground(Color.black);
+ setForeground(Color.white);
+
+ // These should really be on parent.
+ // but again, lets do this for debug now
+
+ addKeyListener(Parent);
+ addFocusListener(Parent);
+
+ addMouseListener(this);
+
+ // initBackBuffer();
+ mapthread = new MapUpdater(this, parent);
+ mapthread.start();
+ }
+
+
+ public void askForFocus() {
+ debug("MapWin taking Keyboard Focus");
+ requestFocus();
+ }
+ }
- public void askForFocus(){
- debug("MapWin taking Keyboard Focus");
- requestFocus();
- }
-}