commit af18b7390848e3486fb636f3577e0e6826b4c515 Author: kzed Date: Sat Dec 4 13:24:32 2010 +0000 added fescen9 branch diff --git a/MapListener.java b/MapListener.java new file mode 100644 index 00000000..bda55853 --- /dev/null +++ b/MapListener.java @@ -0,0 +1,77 @@ +import java.util.logging.Logger; + +public class MapListener extends PluginListener { + private static final Logger log = Logger.getLogger("Minecraft"); + private MapManager mgr; + + public MapListener(MapManager mgr) + { + this.mgr = mgr; + } + + @Override + public boolean onBlockCreate(Player player, Block blockPlaced, Block blockClicked, int itemInHand) + { + if(mgr.touch(blockPlaced.getX(), blockPlaced.getY(), blockPlaced.getZ())) + mgr.debug(player.getName() + " touch " + blockPlaced.getX() + "," + blockPlaced.getY() + "," + blockPlaced.getZ() + " from onBlockCreate"); + return false; + } + + @Override + public boolean onBlockDestroy(Player player, Block block) + { + int x = block.getX(); + int y = block.getY(); + int z = block.getZ(); + if(x == 0 && y == 0 && z == 0) + return false; + + if(mgr.touch(x, y, z)) + mgr.debug(player.getName() + " touch " + x + "," + y + "," + z + " from onBlockBreak"); + + return false; + } + + @Override + public boolean onCommand(Player player, String[] split) + { + if(!player.canUseCommand(split[0])) + return false; + + if(split[0].equals("/map_wait")) { + if(split.length < 2) { + mgr.renderWait = 1000; + } else { + try { + mgr.renderWait = Integer.parseInt(split[1]); + } catch(NumberFormatException e) { + player.sendMessage(Colors.Rose + "Invalid number"); + } + } + return true; + } + + if(split[0].equals("/map_regen")) { + mgr.regenerate((int) player.getX(), (int) player.getY(), (int) player.getZ()); + player.sendMessage(Colors.Rose + "Map regeneration in progress"); + return true; + } + + if(split[0].equals("/map_stat")) { + player.sendMessage(Colors.Rose + "Stale tiles: " + mgr.getStaleCount() + " Recent updates: " + mgr.getRecentUpdateCount()); + return true; + } + + if(split[0].equals("/map_debug")) { + mgr.debugPlayer = player.getName(); + return true; + } + + if(split[0].equals("/map_nodebug")) { + mgr.debugPlayer = null; + return true; + } + + return false; + } +} diff --git a/MapManager.java b/MapManager.java new file mode 100644 index 00000000..f3b49aca --- /dev/null +++ b/MapManager.java @@ -0,0 +1,392 @@ +import java.util.HashMap; +import java.util.Iterator; +import java.util.logging.Logger; +import java.util.logging.Level; +import java.util.Scanner; +import java.util.LinkedList; +import java.util.ListIterator; +import java.util.NoSuchElementException; +import java.util.Vector; + +import java.io.File; +import java.io.IOException; + +import java.awt.Color; + +public class MapManager extends Thread { + protected static final Logger log = Logger.getLogger("Minecraft"); + + /* dimensions of a map tile */ + public static final int tileWidth = 128; + public static final int tileHeight = 128; + + /* lock for our data structures */ + public static final Object lock = new Object(); + + /* a hash table of known MapTiles, by their key (projection coords) */ + private HashMap tileStore; + + /* a list of MapTiles to be updated */ + private LinkedList staleTiles; + + /* whether the worker thread should be running now */ + private boolean running = false; + + /* map x, y, z for projection origin */ + public static final int anchorx = 0; + public static final int anchory = 127; + public static final int anchorz = 0; + + /* color database: id -> Color */ + public HashMap colors = null; + + /* path to colors.txt */ + private String colorsetpath = "colors.txt"; + + /* path to image tile directory */ + public String tilepath = "tiles/"; + + /* time to pause between rendering tiles (ms) */ + public int renderWait = 500; + + /* remember up to this old tile updates (ms) */ + private static final int maxTileAge = 60000; + + /* this list stores the tile updates */ + public LinkedList tileUpdates = null; + + /* map debugging mode (send debugging messages to this player) */ + public String debugPlayer = null; + + public void debug(String msg) + { + if(debugPlayer == null) return; + Server s = etc.getServer(); + Player p = s.getPlayer(debugPlayer); + if(p == null) return; + p.sendMessage("Map> " + Colors.Red + msg); + } + + public MapManager() + { + /* load configuration */ + PropertiesFile properties; + + properties = new PropertiesFile("server.properties"); + try { + tilepath = properties.getString("map-tilepath", "tiles/"); + colorsetpath = properties.getString("map-colorsetpath", "colors.txt"); + } catch(Exception ex) { + log.log(Level.SEVERE, "Exception while reading properties for dynamic map", ex); + } + + tileStore = new HashMap(); + staleTiles = new LinkedList(); + tileUpdates = new LinkedList(); + + + + } + + /* tile X for position x */ + static int tilex(int x) + { + if(x < 0) + return x - (tileWidth + (x % tileWidth)); + else + return x - (x % tileWidth); + } + + /* tile Y for position y */ + static int tiley(int y) + { + if(y < 0) + return y - (tileHeight + (y % tileHeight)); + else + return y - (y % tileHeight); + } + + /* initialize and start map manager */ + public void startManager() + { + colors = new HashMap(); + + /* load colorset */ + File cfile = new File(colorsetpath); + + try { + Scanner scanner = new Scanner(cfile); + int nc = 0; + while(scanner.hasNextLine()) { + String line = scanner.nextLine(); + if (line.startsWith("#") || line.equals("")) { + continue; + } + + String[] split = line.split("\t"); + if (split.length < 17) { + continue; + } + + Integer id = new Integer(split[0]); + + Color[] c = new Color[4]; + + /* store colors by raycast sequence number */ + c[0] = new Color(Integer.parseInt(split[1]), Integer.parseInt(split[2]), Integer.parseInt(split[3]), Integer.parseInt(split[4])); + c[3] = new Color(Integer.parseInt(split[5]), Integer.parseInt(split[6]), Integer.parseInt(split[7]), Integer.parseInt(split[8])); + c[1] = new Color(Integer.parseInt(split[9]), Integer.parseInt(split[10]), Integer.parseInt(split[11]), Integer.parseInt(split[12])); + c[2] = new Color(Integer.parseInt(split[13]), Integer.parseInt(split[14]), Integer.parseInt(split[15]), Integer.parseInt(split[16])); + + colors.put(id, c); + nc += 1; + } + scanner.close(); + + log.info(nc + " colors loaded from " + colorsetpath); + } catch(Exception e) { + log.log(Level.SEVERE, "Failed to load colorset: " + colorsetpath, e); + return; + } + + running = true; + this.start(); + try { + this.setPriority(MIN_PRIORITY); + log.info("Set minimum priority for worker thread"); + } catch(SecurityException e) { + log.info("Failed to set minimum priority for worker thread!"); + } + } + + /* stop map manager */ + public void stopManager() + { + if(!running) + return; + + log.info("Stopping map renderer..."); + running = false; + + try { + this.join(); + } catch(InterruptedException e) { + log.info("Waiting for map renderer to stop is interrupted"); + } + } + + /* the worker/renderer thread */ + public void run() + { + log.info("Map renderer has started."); + + while(running) { + + /* + if(debugPlayer != null) { + Player p = etc.getServer().getPlayer(debugPlayer); + if(p != null) { + int x = (int) p.getX(); + int y = (int) p.getY(); + int z = (int) p.getZ(); + int dx = x - anchorx; + int dy = y - anchory; + int dz = z - anchorz; + int px = dx + dz; + int py = dx - dz - dy; + + int tx = tilex(px); + int ty = tiley(py); + + p.sendMessage(Colors.Red + "pos " + x + "," + y + "," + z + " -> px=" + px + " py=" + py + " -> tx=" + tx + " ty=" + ty); + } + } + */ + + MapTile t = this.popStaleTile(); + if(t != null) { + t.render(this); + + long now = System.currentTimeMillis(); + long deadline = now - maxTileAge; + + /* update the tileupdate list */ + synchronized(lock) { + ListIterator it = tileUpdates.listIterator(0); + while(it.hasNext()) { + TileUpdate tu = it.next(); + if(tu.at < deadline || tu.tile == t) + it.remove(); + } + tileUpdates.addLast(new TileUpdate(now, t)); + } + + try { + this.sleep(renderWait); + } catch(InterruptedException e) { + } + } else { + try { + this.sleep(1000); + } catch(InterruptedException e) { + } + } + } + + log.info("Map renderer has stopped."); + } + + /* "touch" a block - its map tile will be regenerated */ + public boolean touch(int x, int y, int z) + { + int dx = x - anchorx; + int dy = y - anchory; + int dz = z - anchorz; + int px = dx + dz; + int py = dx - dz - dy; + + int tx = tilex(px); + int ty = tiley(py); + + boolean r; + + r = pushStaleTile(tx, ty); + + /* + if(r) { + debug("touch stale " + x + "," + y + "," + z + " -> px=" + px + " py=" + py + " -> tx=" + tx + " ty=" + ty); + } + */ + + boolean ledge = tilex(px - 4) != tx; + boolean tedge = tiley(py - 4) != ty; + boolean redge = tilex(px + 4) != tx; + boolean bedge = tiley(py + 4) != ty; + + if(ledge) + r = pushStaleTile(tx - tileWidth, ty) || r; + if(redge) + r = pushStaleTile(tx + tileWidth, ty) || r; + if(tedge) + r = pushStaleTile(tx, ty - tileHeight) || r; + if(bedge) + r = pushStaleTile(tx, ty + tileHeight) || r; + + if(ledge && tedge) + r = pushStaleTile(tx - tileWidth, ty - tileHeight) || r; + if(ledge && bedge) + r = pushStaleTile(tx - tileWidth, ty + tileHeight) || r; + if(redge && tedge) + r = pushStaleTile(tx + tileWidth, ty - tileHeight) || r; + if(redge && bedge) + r = pushStaleTile(tx + tileWidth, ty + tileHeight) || r; + + return r; + } + + /* get next MapTile that needs to be regenerated, or null + * the mapTile is removed from the list of stale tiles! */ + public MapTile popStaleTile() + { + synchronized(lock) { + try { + MapTile t = staleTiles.removeFirst(); + t.stale = false; + return t; + } catch(NoSuchElementException e) { + return null; + } + } + } + + /* put a MapTile that needs to be regenerated on the list of stale tiles */ + public boolean pushStaleTile(MapTile m) + { + synchronized(lock) { + if(m.stale) return false; + + m.stale = true; + staleTiles.addLast(m); + + debug(m.toString() + " is now stale"); + + return true; + } + } + + /* make a MapTile stale by projection position */ + public boolean pushStaleTile(int tx, int ty) + { + return pushStaleTile(getTileByPosition(tx, ty)); + } + + /* get (or create) MapTile by projection position */ + private MapTile getTileByPosition(int px, int py) + { + Long key = MapTile.key(px, py); + synchronized(lock) { + MapTile t = tileStore.get(key); + if(t == null) { + /* no maptile exists, need to create one */ + t = new MapTile(px, py); + tileStore.put(key, t); + return t; + } else { + return t; + } + } + } + + /* return number of stale tiles */ + public int getStaleCount() + { + synchronized(lock) { + return staleTiles.size(); + } + } + + /* return number of recently updated tiles */ + public int getRecentUpdateCount() + { + synchronized(lock) { + return tileUpdates.size(); + } + } + + /* regenerate the entire map, starting at position */ + public void regenerate(int x, int y, int z) + { + int dx = x - anchorx; + int dy = y - anchory; + int dz = z - anchorz; + int px = dx + dz; + int py = dx - dz - dy; + + int tx = tilex(px); + int ty = tiley(py); + + MapTile first = getTileByPosition(tx, ty); + + Vector open = new Vector(); + open.add(first); + + Server s = etc.getServer(); + + while(open.size() > 0) { + MapTile t = open.remove(open.size() - 1); + if(t.stale) continue; + int h = s.getHighestBlockY(t.mx, t.mz); + + log.info("walking: " + t.mx + ", " + t.mz + ", h = " + h); + if(h < 1) + continue; + + pushStaleTile(t); + + open.add(getTileByPosition(t.px + tileWidth, t.py)); + open.add(getTileByPosition(t.px - tileWidth, t.py)); + open.add(getTileByPosition(t.px, t.py + tileHeight)); + open.add(getTileByPosition(t.px, t.py - tileHeight)); + } + } +} diff --git a/MapTile.java b/MapTile.java new file mode 100644 index 00000000..91980ae0 --- /dev/null +++ b/MapTile.java @@ -0,0 +1,191 @@ +import java.util.logging.Logger; +import java.util.logging.Level; + +import java.awt.*; +import java.awt.image.*; + +import java.io.File; +import java.io.IOException; + +import javax.imageio.ImageIO; + +public class MapTile { + protected static final Logger log = Logger.getLogger("Minecraft"); + + /* projection position */ + public int px, py; + + /* minecraft space origin */ + public int mx, my, mz; + + /* whether this tile needs to be updated */ + boolean stale = false; + + /* create new MapTile */ + public MapTile(int px, int py) + { + this.px = px; + this.py = py; + + mx = MapManager.anchorx + px / 2 + py / 2; + my = MapManager.anchory; + mz = MapManager.anchorz + px / 2 - py / 2; + } + + /* get key by projection position */ + public static long key(int px, int py) + { + long lpx = (long) px; + long lpy = (long) py; + + return ((lpx & (long) 0xffffffffL) << 32) | (lpy & (long) 0xffffffffL); + } + + /* hash value, based on projection position */ + public int hashCode() + { + return (px << 16) ^ py; + } + + /* equality comparison - based on projection position */ + public boolean equals(MapTile o) + { + return o.px == px && o.py == py; + } + + /* return a simple string representation... */ + public String toString() + { + return px + "_" + py; + } + + /* render this tile */ + public void render(MapManager mgr) + { + mgr.debug("Rendering tile: " + this.toString()); + + BufferedImage im = new BufferedImage(MapManager.tileWidth, MapManager.tileHeight, BufferedImage.TYPE_INT_RGB); + + WritableRaster r = im.getRaster(); + + int ix = mx; + int iy = my; + int iz = mz; + int jx, jz; + + int x, y; + + /* draw the map */ + for(y=0; y=0; x-=2) { + Color c1 = scan(mgr, jx, iy, jz, 0); + Color c2 = scan(mgr, jx, iy, jz, 2); + + r.setPixel(x, y, new int[] { c1.getRed(), c1.getGreen(), c1.getBlue() }); + r.setPixel(x-1, y, new int[] { c2.getRed(), c2.getGreen(), c2.getBlue() }); + + jx++; + jz++; + + } + + y ++; + + jx = ix; + jz = iz - 1; + + for(x=MapManager.tileWidth-1; x>=0; x-=2) { + Color c1 = scan(mgr, jx, iy, jz, 2); + jx++; + jz++; + Color c2 = scan(mgr, jx, iy, jz, 0); + + r.setPixel(x, y, new int[] { c1.getRed(), c1.getGreen(), c1.getBlue() }); + r.setPixel(x-1, y, new int[] { c2.getRed(), c2.getGreen(), c2.getBlue() }); + } + + y ++; + + ix ++; + iz --; + } + + /* save image */ + try { + String path = getPath(mgr); + File file = new File(path); + ImageIO.write(im, "png", file); + + //log.info("Saved tile: " + path); + } catch(IOException e) { + log.log(Level.SEVERE, "Failed to save tile: " + getPath(mgr), e); + } + } + + /* generate a path name for this map tile */ + String getPath(MapManager mgr) + { + return mgr.tilepath + "t_" + px + "_" + py + ".png"; + } + + /* cast a ray into the map */ + private Color scan(MapManager mgr, int x, int y, int z, int seq) + { + Server s = etc.getServer(); + + for(;;) { + if(y < 0) + return Color.BLUE; + + int id = s.getBlockIdAt(x, y, z); + + switch(seq) { + case 0: + x--; + break; + case 1: + y--; + break; + case 2: + z++; + break; + case 3: + y--; + break; + } + + seq = (seq + 1) & 3; + + if(id != 0) { + Color[] colors = mgr.colors.get(id); + if(colors != null) { + Color c = colors[seq]; + if(c.getAlpha() > 0) { + /* we found something that isn't transparent! */ + if(c.getAlpha() == 255) { + /* it's opaque - the ray ends here */ + return c; + } + + /* this block is transparent, so recurse */ + Color bg = scan(mgr, x, y, z, seq); + + int cr = c.getRed(); + int cg = c.getGreen(); + int cb = c.getBlue(); + int ca = c.getAlpha(); + cr *= ca; + cg *= ca; + cb *= ca; + int na = 255 - ca; + + return new Color((bg.getRed() * na + cr) >> 8, (bg.getGreen() * na + cg) >> 8, (bg.getBlue() * na + cb) >> 8); + } + } + } + } + } +} diff --git a/TileUpdate.java b/TileUpdate.java new file mode 100644 index 00000000..0d5ad1f5 --- /dev/null +++ b/TileUpdate.java @@ -0,0 +1,12 @@ +/* this class stores a tile update */ + +public class TileUpdate { + public long at; + public MapTile tile; + + public TileUpdate(long at, MapTile tile) + { + this.at = at; + this.tile = tile; + } +} diff --git a/WebServer.java b/WebServer.java new file mode 100644 index 00000000..e84bdff9 --- /dev/null +++ b/WebServer.java @@ -0,0 +1,53 @@ +import java.io.*; +import java.net.*; +import java.util.*; + +import java.util.logging.Logger; + +public class WebServer extends Thread { + + public static final String VERSION = "Huncraft"; + protected static final Logger log = Logger.getLogger("Minecraft"); + + private ServerSocket sock = null; + private boolean running = false; + + private MapManager mgr; + + public WebServer(int port, MapManager mgr) throws IOException + { + this.mgr = mgr; + sock = new ServerSocket(port, 5, InetAddress.getByName("127.0.0.1")); + running = true; + start(); + log.info("map WebServer started"); + } + + public void run() + { + while (running) { + try { + Socket socket = sock.accept(); + WebServerRequest requestThread = new WebServerRequest(socket, mgr); + requestThread.start(); + } + catch (IOException e) { + log.info("map WebServer.run() stops with IOException"); + break; + } + } + log.info("map WebServer run() exiting"); + } + + public void shutdown() + { + try { + if(sock != null) { + sock.close(); + } + } catch(IOException e) { + log.info("map stop() got IOException while closing socket"); + } + running = false; + } +} diff --git a/WebServerRequest.java b/WebServerRequest.java new file mode 100644 index 00000000..685bd42c --- /dev/null +++ b/WebServerRequest.java @@ -0,0 +1,97 @@ +import java.io.*; +import java.net.*; +import java.util.*; + +import java.util.logging.Logger; + +public class WebServerRequest extends Thread { + private Socket sock; + private MapManager mgr; + + public WebServerRequest(Socket socket, MapManager mgr) + { + sock = socket; + this.mgr = mgr; + } + + private static void sendHeader(BufferedOutputStream out, int code, String contentType, long contentLength, long lastModified) throws IOException + { + out.write(("HTTP/1.0 " + code + " OK\r\n" + + "Date: " + new Date().toString() + "\r\n" + + "Server: JibbleWebServer/1.0\r\n" + + "Content-Type: " + contentType + "\r\n" + + "Expires: Thu, 01 Dec 1994 16:00:00 GMT\r\n" + + ((contentLength != -1) ? "Content-Length: " + contentLength + "\r\n" : "") + + "Last-modified: " + new Date(lastModified).toString() + "\r\n" + + "\r\n").getBytes()); + } + + private static void sendError(BufferedOutputStream out, int code, String message) throws IOException + { + message = message + "
" + WebServer.VERSION; + sendHeader(out, code, "text/html", message.length(), System.currentTimeMillis()); + out.write(message.getBytes()); + out.flush(); + out.close(); + } + + public void run() + { + InputStream reader = null; + try { + sock.setSoTimeout(30000); + BufferedReader in = new BufferedReader(new InputStreamReader(sock.getInputStream())); + BufferedOutputStream out = new BufferedOutputStream(sock.getOutputStream()); + + String request = in.readLine(); + if (request == null || !request.startsWith("GET ") || !(request.endsWith(" HTTP/1.0") || request.endsWith("HTTP/1.1"))) { + // Invalid request type (no "GET") + sendError(out, 500, "Invalid Method."); + return; + } + + String path = request.substring(4, request.length() - 9); + + int current = (int) (System.currentTimeMillis() / 1000); + long cutoff = 0; + if(path.charAt(0) == '/') { + try { + cutoff = ((long) Integer.parseInt(path.substring(1))) * 1000; + } catch(NumberFormatException e) { + } + } + + sendHeader(out, 200, "text/plain", -1, System.currentTimeMillis()); + + StringBuilder sb = new StringBuilder(); + sb.append(current + "\n"); + + for(Player player : etc.getServer().getPlayerList()) { + sb.append(player.getName() + " " + player.getX() + " " + player.getY() + " " + player.getZ() + "\n"); + } + + synchronized(mgr.lock) { + for(TileUpdate tu : mgr.tileUpdates) { + if(tu.at >= cutoff) { + sb.append(tu.tile.px + "_" + tu.tile.py + "\n"); + } + } + } + + out.write(sb.toString().getBytes()); + + out.flush(); + out.close(); + } + catch (IOException e) { + if (reader != null) { + try { + reader.close(); + } + catch (Exception anye) { + // Do nothing. + } + } + } + } +} diff --git a/build.sh b/build.sh new file mode 100755 index 00000000..4212c080 --- /dev/null +++ b/build.sh @@ -0,0 +1,3 @@ +#!/bin/bash +rm -f *.class *.jar +javac *.java -cp ../hey0/bin/Minecraft_Mod.jar:../minecraft_server.jar && jar cvf map.jar *.class diff --git a/clean.sh b/clean.sh new file mode 100755 index 00000000..db437f5e --- /dev/null +++ b/clean.sh @@ -0,0 +1,2 @@ +#!/bin/bash +rm -f *.class *.jar diff --git a/colors.txt b/colors.txt new file mode 100644 index 00000000..b2ad8cc6 --- /dev/null +++ b/colors.txt @@ -0,0 +1,72 @@ +1 120 120 120 255 96 96 96 255 60 60 60 255 48 48 48 255 +2 117 176 73 255 93 140 58 255 58 88 36 255 46 70 29 255 +3 134 96 67 255 107 76 53 255 67 48 33 255 53 38 26 255 +4 115 115 115 255 92 92 92 255 57 57 57 255 46 46 46 255 +48 115 115 115 255 92 92 92 255 57 57 57 255 46 46 46 255 +5 157 128 79 255 125 102 63 255 78 64 39 255 62 51 31 255 +6 120 120 120 0 96 96 96 0 60 60 60 0 48 48 48 0 +7 84 84 84 255 67 67 67 255 42 42 42 255 33 33 33 255 +8 38 92 255 51 30 73 204 51 19 46 127 51 15 36 102 51 +9 38 92 255 51 30 73 204 51 19 46 127 51 15 36 102 51 +10 255 90 0 255 204 72 0 255 127 45 0 255 102 36 0 255 +11 255 90 0 255 204 72 0 255 127 45 0 255 102 36 0 255 +12 218 210 158 255 174 168 126 255 109 105 79 255 87 84 63 255 +13 136 126 126 255 108 100 100 255 68 63 63 255 54 50 50 255 +14 143 140 125 255 114 112 100 255 71 70 62 255 57 56 50 255 +15 136 130 127 255 108 104 101 255 68 65 63 255 54 52 50 255 +16 115 115 115 255 92 92 92 255 57 57 57 255 46 46 46 255 +17 102 81 51 255 81 64 40 255 51 40 25 255 40 32 20 255 +18 60 192 41 100 48 153 32 100 30 96 20 100 24 76 16 100 +20 255 255 255 64 204 204 204 64 127 127 127 64 102 102 102 64 +35 222 222 222 255 177 177 177 255 111 111 111 255 88 88 88 255 +37 255 0 0 255 204 0 0 255 127 0 0 255 102 0 0 255 +38 255 255 0 0 204 204 0 0 127 127 0 0 102 102 0 0 +41 232 245 46 255 185 196 36 255 116 122 23 255 92 98 18 255 +42 191 191 191 255 152 152 152 255 95 95 95 255 76 76 76 255 +43 200 200 200 255 160 160 160 255 100 100 100 255 80 80 80 255 +44 200 200 200 255 160 160 160 255 100 100 100 255 80 80 80 255 +45 170 86 62 255 136 68 49 255 85 43 31 255 68 34 24 255 +46 160 83 65 255 128 66 52 255 80 41 32 255 64 33 26 255 +49 26 11 43 255 20 8 34 255 13 5 21 255 10 4 17 255 +51 255 170 30 200 204 136 24 200 127 85 15 200 102 68 12 200 +53 157 128 79 255 125 102 63 255 78 64 39 255 62 51 31 255 +54 125 91 38 255 100 72 30 255 62 45 19 255 50 36 15 255 +56 129 140 143 255 103 112 114 255 64 70 71 255 51 56 57 255 +57 45 166 152 255 36 132 121 255 22 83 76 255 18 66 60 255 +58 114 88 56 255 91 70 44 255 57 44 28 255 45 35 22 255 +59 146 192 0 255 116 153 0 255 73 96 0 255 58 76 0 255 +60 95 58 30 255 76 46 24 255 47 29 15 255 38 23 12 255 +61 96 96 96 255 76 76 76 255 48 48 48 255 38 38 38 255 +62 96 96 96 255 76 76 76 255 48 48 48 255 38 38 38 255 +63 111 91 54 255 88 72 43 255 55 45 27 255 44 36 21 255 +64 136 109 67 255 108 87 53 255 68 54 33 255 54 43 26 255 +65 181 140 64 32 144 112 51 32 90 70 32 32 72 56 25 32 +66 150 134 102 180 120 107 81 180 75 67 51 180 60 53 40 180 +67 115 115 115 255 92 92 92 255 57 57 57 255 46 46 46 255 +71 191 191 191 255 152 152 152 255 95 95 95 255 76 76 76 255 +73 131 107 107 255 104 85 85 255 65 53 53 255 52 42 42 255 +74 131 107 107 255 104 85 85 255 65 53 53 255 52 42 42 255 +78 255 255 255 255 204 204 204 255 127 127 127 255 102 102 102 255 +79 83 113 163 51 66 90 130 51 41 56 81 51 33 45 65 51 +80 250 250 250 255 200 200 200 255 125 125 125 255 100 100 100 255 +81 25 120 25 255 20 96 20 255 12 60 12 255 10 48 10 255 +82 151 157 169 255 120 125 135 255 75 78 84 255 60 62 67 255 +83 193 234 150 255 154 187 120 255 96 117 75 255 77 93 60 255 + +redstone torch off +75 159 127 80 255 72 56 25 0 181 140 64 255 144 112 51 0 +redstone torch on +76 159 127 80 255 102 0 0 0 255 0 0 255 204 0 0 0 +torch +50 159 127 80 255 98 88 20 0 245 220 50 255 196 176 40 0 + +fence +85 172 136 82 255 172 136 82 0 91 70 37 255 91 70 37 0 +netherstone +87 166 89 89 255 141 80 62 255 135 15 15 255 96 6 6 255 +slowsand +88 133 109 94 255 121 97 82 255 90 70 57 255 79 59 46 255 +lightstone +89 249 212 156 255 255 188 94 255 192 143 70 255 122 91 44 255 +portal +90 99 66 222 128 99 66 222 128 99 66 222 128 99 66 222 128 diff --git a/map.java b/map.java new file mode 100644 index 00000000..0eeddfa9 --- /dev/null +++ b/map.java @@ -0,0 +1,52 @@ +import java.util.logging.Logger; +import java.io.IOException; + +public class map extends Plugin { + + protected static final Logger log = Logger.getLogger("Minecraft"); + + private WebServer server = null; + private MapManager mgr = null; + private MapListener listener = null; + + @Override + public void enable() { + log.info("Map INIT"); + + mgr = new MapManager(); + mgr.startManager(); + + try { + server = new WebServer(8123, mgr); + } catch(IOException e) { + log.info("position failed to start WebServer (IOException)"); + } + + listener = new MapListener(mgr); + } + + @Override + public void disable() { + log.info("Map UNINIT"); + + mgr.stopManager(); + + if(server != null) { + server.shutdown(); + server = null; + } + } + + @Override + public void initialize() { + etc.getLoader().addListener(PluginLoader.Hook.COMMAND, listener, this, PluginListener.Priority.MEDIUM); + etc.getLoader().addListener(PluginLoader.Hook.BLOCK_CREATED, listener, this, PluginListener.Priority.MEDIUM); + etc.getLoader().addListener(PluginLoader.Hook.BLOCK_DESTROYED, listener, this, PluginListener.Priority.MEDIUM); + + etc.getInstance().addCommand("/map_wait", " [wait] - set wait between tile renders (ms)"); + etc.getInstance().addCommand("/map_stat", " - query number of tiles in render queue"); + etc.getInstance().addCommand("/map_regen", " - regenerate entire map"); + etc.getInstance().addCommand("/map_debug", " - send map debugging messages"); + etc.getInstance().addCommand("/map_nodebug", " - disable map debugging messages"); + } +} diff --git a/web/follow_off.png b/web/follow_off.png new file mode 100644 index 00000000..ba506d09 Binary files /dev/null and b/web/follow_off.png differ diff --git a/web/follow_on.png b/web/follow_on.png new file mode 100644 index 00000000..2fb7be7a Binary files /dev/null and b/web/follow_on.png differ diff --git a/web/index.html b/web/index.html new file mode 100644 index 00000000..4d967389 --- /dev/null +++ b/web/index.html @@ -0,0 +1,733 @@ + + + + + + + + + +
+
???
+ + diff --git a/web/list_off.png b/web/list_off.png new file mode 100644 index 00000000..b7be11be Binary files /dev/null and b/web/list_off.png differ diff --git a/web/list_on.png b/web/list_on.png new file mode 100644 index 00000000..285f76e7 Binary files /dev/null and b/web/list_on.png differ diff --git a/web/map.js b/web/map.js new file mode 100644 index 00000000..42a5b462 --- /dev/null +++ b/web/map.js @@ -0,0 +1,484 @@ +/** + * This constructor creates a label and associates it with a marker. + * It is for the private use of the MarkerWithLabel class. + * @constructor + * @param {Marker} marker The marker with which the label is to be associated. + * @private + */ +function MarkerLabel_(marker) { + this.marker_ = marker; + + this.labelDiv_ = document.createElement("div"); + this.labelDiv_.style.cssText = "position: absolute; overflow: hidden;"; + + // Set up the DIV for handling mouse events in the label. This DIV forms a transparent veil + // in the "overlayMouseTarget" pane, a veil that covers just the label. This is done so that + // events can be captured even if the label is in the shadow of a google.maps.InfoWindow. + // Code is included here to ensure the veil is always exactly the same size as the label. + this.eventDiv_ = document.createElement("div"); + this.eventDiv_.style.cssText = this.labelDiv_.style.cssText; +} + +// MarkerLabel_ inherits from OverlayView: +MarkerLabel_.prototype = new google.maps.OverlayView(); + +/** + * Adds the DIV representing the label to the DOM. This method is called + * automatically when the marker's setMap method is called. + * @private + */ +MarkerLabel_.prototype.onAdd = function () { + var me = this; + var cMouseIsDown = false; + var cDraggingInProgress = false; + var cSavedPosition; + var cSavedZIndex; + var cLatOffset, cLngOffset; + var cIgnoreClick; + + // Stops all processing of an event. + // + var cAbortEvent = function (e) { + if (e.preventDefault) { + e.preventDefault(); + } + e.cancelBubble = true; + if (e.stopPropagation) { + e.stopPropagation(); + } + }; + + this.getPanes().overlayImage.appendChild(this.labelDiv_); + this.getPanes().overlayMouseTarget.appendChild(this.eventDiv_); + + this.listeners_ = [ + google.maps.event.addDomListener(document, "mouseup", function (mEvent) { + if (cDraggingInProgress) { + mEvent.latLng = cSavedPosition; + cIgnoreClick = true; // Set flag to ignore the click event reported after a label drag + google.maps.event.trigger(me.marker_, "dragend", mEvent); + } + cMouseIsDown = false; + google.maps.event.trigger(me.marker_, "mouseup", mEvent); + }), + google.maps.event.addListener(me.marker_.getMap(), "mousemove", function (mEvent) { + if (cMouseIsDown && me.marker_.getDraggable()) { + // Change the reported location from the mouse position to the marker position: + mEvent.latLng = new google.maps.LatLng(mEvent.latLng.lat() - cLatOffset, mEvent.latLng.lng() - cLngOffset); + cSavedPosition = mEvent.latLng; + if (cDraggingInProgress) { + google.maps.event.trigger(me.marker_, "drag", mEvent); + } else { + // Calculate offsets from the click point to the marker position: + cLatOffset = mEvent.latLng.lat() - me.marker_.getPosition().lat(); + cLngOffset = mEvent.latLng.lng() - me.marker_.getPosition().lng(); + google.maps.event.trigger(me.marker_, "dragstart", mEvent); + } + } + }), + google.maps.event.addDomListener(this.eventDiv_, "mouseover", function (e) { + //me.eventDiv_.style.cursor = "pointer"; + google.maps.event.trigger(me.marker_, "mouseover", e); + }), + google.maps.event.addDomListener(this.eventDiv_, "mouseout", function (e) { + //me.eventDiv_.style.cursor = me.marker_.getCursor(); + google.maps.event.trigger(me.marker_, "mouseout", e); + }), + google.maps.event.addDomListener(this.eventDiv_, "click", function (e) { + if (cIgnoreClick) { // Ignore the click reported when a label drag ends + cIgnoreClick = false; + } else { + cAbortEvent(e); // Prevent click from being passed on to map + google.maps.event.trigger(me.marker_, "click", e); + } + }), + google.maps.event.addDomListener(this.eventDiv_, "dblclick", function (e) { + cAbortEvent(e); // Prevent map zoom when double-clicking on a label + google.maps.event.trigger(me.marker_, "dblclick", e); + }), + google.maps.event.addDomListener(this.eventDiv_, "mousedown", function (e) { + cMouseIsDown = true; + cDraggingInProgress = false; + cLatOffset = 0; + cLngOffset = 0; + cAbortEvent(e); // Prevent map pan when starting a drag on a label + google.maps.event.trigger(me.marker_, "mousedown", e); + }), + google.maps.event.addListener(this.marker_, "dragstart", function (mEvent) { + cDraggingInProgress = true; + cSavedZIndex = me.marker_.getZIndex(); + }), + google.maps.event.addListener(this.marker_, "drag", function (mEvent) { + me.marker_.setPosition(mEvent.latLng); + me.marker_.setZIndex(1000000); // Moves the marker to the foreground during a drag + }), + google.maps.event.addListener(this.marker_, "dragend", function (mEvent) { + cDraggingInProgress = false; + me.marker_.setZIndex(cSavedZIndex); + }), + google.maps.event.addListener(this.marker_, "position_changed", function () { + me.setPosition(); + }), + google.maps.event.addListener(this.marker_, "zindex_changed", function () { + me.setZIndex(); + }), + google.maps.event.addListener(this.marker_, "visible_changed", function () { + me.setVisible(); + }), + google.maps.event.addListener(this.marker_, "labelvisible_changed", function () { + me.setVisible(); + }), + google.maps.event.addListener(this.marker_, "title_changed", function () { + me.setTitle(); + }), + google.maps.event.addListener(this.marker_, "labelcontent_changed", function () { + me.setContent(); + }), + google.maps.event.addListener(this.marker_, "labelanchor_changed", function () { + me.setAnchor(); + }), + google.maps.event.addListener(this.marker_, "labelclass_changed", function () { + me.setStyles(); + }), + google.maps.event.addListener(this.marker_, "labelstyle_changed", function () { + me.setStyles(); + }) + ]; +}; + +/** + * Removes the DIV for the label from the DOM. It also removes all event handlers. + * This method is called automatically when the marker's setMap(null) + * method is called. + * @private + */ +MarkerLabel_.prototype.onRemove = function () { + var i; + this.labelDiv_.parentNode.removeChild(this.labelDiv_); + this.eventDiv_.parentNode.removeChild(this.eventDiv_); + + // Remove event listeners: + for (i = 0; i < this.listeners_.length; i++) { + google.maps.event.removeListener(this.listeners_[i]); + } +}; + +/** + * Draws the label on the map. + * @private + */ +MarkerLabel_.prototype.draw = function () { + this.setContent(); + this.setTitle(); + this.setStyles(); +}; + +/** + * Sets the content of the label. + * The content can be plain text or an HTML DOM node. + * @private + */ +MarkerLabel_.prototype.setContent = function () { + var content = this.marker_.get("labelContent"); + if (typeof content.nodeType === "undefined") { + this.labelDiv_.innerHTML = content; + this.eventDiv_.innerHTML = this.labelDiv_.innerHTML; + } else { + this.labelDiv_.appendChild(content); + content = content.cloneNode(true); + this.eventDiv_.appendChild(content); + } +}; + +/** + * Sets the content of the tool tip for the label. It is + * always set to be the same as for the marker itself. + * @private + */ +MarkerLabel_.prototype.setTitle = function () { + this.eventDiv_.title = this.marker_.getTitle() || ""; +}; + +/** + * Sets the style of the label by setting the style sheet and applying + * other specific styles requested. + * @private + */ +MarkerLabel_.prototype.setStyles = function () { + var i, labelStyle; + + // Apply style values from the style sheet defined in the labelClass parameter: + this.labelDiv_.className = this.marker_.get("labelClass"); + this.eventDiv_.className = this.labelDiv_.className; + + // Clear existing inline style values: + this.labelDiv_.style.cssText = ""; + this.eventDiv_.style.cssText = ""; + // Apply style values defined in the labelStyle parameter: + labelStyle = this.marker_.get("labelStyle"); + for (i in labelStyle) { + if (labelStyle.hasOwnProperty(i)) { + this.labelDiv_.style[i] = labelStyle[i]; + this.eventDiv_.style[i] = labelStyle[i]; + } + } + this.setMandatoryStyles(); +}; + +/** + * Sets the mandatory styles to the DIV representing the label as well as to the + * associated event DIV. This includes setting the DIV position, zIndex, and visibility. + * @private + */ +MarkerLabel_.prototype.setMandatoryStyles = function () { + this.labelDiv_.style.position = "absolute"; + this.labelDiv_.style.overflow = "hidden"; + // Make sure the opacity setting causes the desired effect on MSIE: + if (typeof this.labelDiv_.style.opacity !== "undefined") { + this.labelDiv_.style.filter = "alpha(opacity=" + (this.labelDiv_.style.opacity * 100) + ")"; + } + + this.eventDiv_.style.position = this.labelDiv_.style.position; + this.eventDiv_.style.overflow = this.labelDiv_.style.overflow; + this.eventDiv_.style.opacity = 0.01; // Don't use 0; DIV won't be clickable on MSIE + this.eventDiv_.style.filter = "alpha(opacity=1)"; // For MSIE + + this.setAnchor(); + this.setPosition(); // This also updates zIndex, if necessary. + this.setVisible(); +}; + +/** + * Sets the anchor point of the label. + * @private + */ +MarkerLabel_.prototype.setAnchor = function () { + var anchor = this.marker_.get("labelAnchor"); + this.labelDiv_.style.marginLeft = -anchor.x + "px"; + this.labelDiv_.style.marginTop = -anchor.y + "px"; + this.eventDiv_.style.marginLeft = -anchor.x + "px"; + this.eventDiv_.style.marginTop = -anchor.y + "px"; +}; + +/** + * Sets the position of the label. The zIndex is also updated, if necessary. + * @private + */ +MarkerLabel_.prototype.setPosition = function () { + var position = this.getProjection().fromLatLngToDivPixel(this.marker_.getPosition()); + + this.labelDiv_.style.left = position.x + "px"; + this.labelDiv_.style.top = position.y + "px"; + this.eventDiv_.style.left = this.labelDiv_.style.left; + this.eventDiv_.style.top = this.labelDiv_.style.top; + + this.setZIndex(); +}; + +/** + * Sets the zIndex of the label. If the marker's zIndex property has not been defined, the zIndex + * of the label is set to the vertical coordinate of the label. This is in keeping with the default + * stacking order for Google Maps: markers to the south are in front of markers to the north. + * @private + */ +MarkerLabel_.prototype.setZIndex = function () { + var zAdjust = (this.marker_.get("labelInBackground") ? -1 : +1); + if (typeof this.marker_.getZIndex() === "undefined") { + this.labelDiv_.style.zIndex = parseInt(this.labelDiv_.style.top, 10) + zAdjust; + this.eventDiv_.style.zIndex = this.labelDiv_.style.zIndex; + } else { + this.labelDiv_.style.zIndex = this.marker_.getZIndex() + zAdjust; + this.eventDiv_.style.zIndex = this.labelDiv_.style.zIndex; + } +}; + +/** + * Sets the visibility of the label. The label is visible only if the marker itself is + * visible (i.e., its visible property is true) and the labelVisible property is true. + * @private + */ +MarkerLabel_.prototype.setVisible = function () { + if (this.marker_.get("labelVisible")) { + this.labelDiv_.style.display = this.marker_.getVisible() ? "block" : "none"; + } else { + this.labelDiv_.style.display = "none"; + } + this.eventDiv_.style.display = this.labelDiv_.style.display; +}; + +/** + * @name MarkerWithLabelOptions + * @class This class represents the optional parameter passed to the {@link MarkerWithLabel} constructor. + * The properties available are the same as for google.maps.Marker with the addition + * of the properties listed below. To change any of these additional properties after the labeled + * marker has been created, call google.maps.Marker.set(propertyName, propertyValue). + *

+ * When any of these properties changes, a property changed event is fired. The names of these + * events are derived from the name of the property and are of the form propertyname_changed. + * For example, if the content of the label changes, a labelcontent_changed event + * is fired. + *

+ * @property {string|Node} [labelContent] The content of the label (plain text or an HTML DOM node). + * @property {Point} [labelAnchor] By default, a label is drawn with its anchor point at (0,0) so + * that its top left corner is positioned at the anchor point of the associated marker. Use this + * property to change the anchor point of the label. For example, to center a 50px-wide label + * beneath a marker, specify a labelAnchor of google.maps.Point(25, 0). + * (Note: x-values increase to the right and y-values increase to the bottom.) + * @property {string} [labelClass] The name of the CSS class defining the styles for the label. + * Note that style values for position, overflow, top, + * left, zIndex, display, marginLeft, and + * marginTop are ignored; these styles are for internal use only. + * @property {Object} [labelStyle] An object literal whose properties define specific CSS + * style values to be applied to the label. Style values defined here override those that may + * be defined in the labelClass style sheet. If this property is changed after the + * label has been created, all previously set styles (except those defined in the style sheet) + * are removed from the label before the new style values are applied. + * Note that style values for position, overflow, top, + * left, zIndex, display, marginLeft, and + * marginTop are ignored; these styles are for internal use only. + * @property {boolean} [labelInBackground] A flag indicating whether a label that overlaps its + * associated marker should appear in the background (i.e., in a plane below the marker). + * The default is false, which causes the label to appear in the foreground. + * @property {boolean} [labelVisible] A flag indicating whether the label is to be visible. + * The default is true. Note that even if labelVisible is + * true, the label will not be visible unless the associated marker is also + * visible (i.e., unless the marker's visible property is true). + */ +/** + * Creates a MarkerWithLabel with the options specified in {@link MarkerWithLabelOptions}. + * @constructor + * @param {MarkerWithLabelOptions} [opt_options] The optional parameters. + */ +function MarkerWithLabel(opt_options) { + opt_options = opt_options || {}; + opt_options.labelContent = opt_options.labelContent || ""; + opt_options.labelAnchor = opt_options.labelAnchor || new google.maps.Point(0, 0); + opt_options.labelClass = opt_options.labelClass || "markerLabels"; + opt_options.labelStyle = opt_options.labelStyle || {}; + opt_options.labelInBackground = opt_options.labelInBackground || false; + if (typeof opt_options.labelVisible === "undefined") { + opt_options.labelVisible = true; + } + + this.label = new MarkerLabel_(this); // Bind the label to the marker + + // Call the parent constructor. It calls Marker.setValues to initialize, so all + // the new parameters are conveniently saved and can be accessed with get/set. + // Marker.set triggers a property changed event (called "propertyname_changed") + // that the marker label listens for in order to react to state changes. + google.maps.Marker.apply(this, arguments); +} + +// MarkerWithLabel inherits from Marker: +MarkerWithLabel.prototype = new google.maps.Marker(); + +MarkerWithLabel.prototype.setMap = function (theMap) { + // Call the inherited function... + google.maps.Marker.prototype.setMap.apply(this, arguments); + + // ... then deal with the label: + this.label.setMap(theMap); +}; + + + +/* generic function for making an XMLHttpRequest + * url: request URL + * func: callback function for success + * type: 'text' by default (callback is called with response text) + * otherwise, callback is called with a parsed XML dom + * fail: callback function for failure + * post: if given, make a POST request instead of GET; post data given + * + * contenttype: if given for a POST, set request content-type header + */ +function makeRequest(url, func, type, fail, post, contenttype) +{ + var http_request = false; + + type = typeof(type) != 'undefined' ? type : 'text'; + fail = typeof(fail) != 'undefined' ? fail : function() { }; + + if(window.XMLHttpRequest) { + http_request = new XMLHttpRequest(); + } else if(window.ActiveXObject) { + http_request = new ActiveXObject("Microsoft.XMLHTTP"); + } + + if(type == 'text') { + http_request.onreadystatechange = function() { + if(http_request.readyState == 4) { + if(http_request.status == 200) { + func(http_request.responseText); + } else { + fail(http_request); + } + } + } + } else { + http_request.onreadystatechange = function() { + if(http_request.readyState == 4) { + if(http_request.status == 200) { + func(http_request.responseXML); + } else { + fail(http_request); + } + } + } + } + + if(typeof(post) != 'undefined') { + http_request.open('POST', url, true); + if(typeof(contenttype) != 'undefined') + http_request.setRequestHeader("Content-Type", contenttype); + http_request.send(post); + } else { + http_request.open('GET', url, true); + http_request.send(null); + } +} + +var markers = new Array(); + +function initActive() +{ + playerUpdate(); +} + +function playerUpdate() +{ + makeRequest('/pos', function(res) { + var rows = res.split('\n'); + var loggedin = new Array(); + for(var line in rows) { + var p = rows[line].split(' '); + loggedin[p[0]] = 1; + if(p[0] == '') continue; + if(p[0] in markers) { + var m = markers[p[0]]; + var converted = fromWorldToLatLng(p[1], p[2], p[3]); + m.setPosition(converted); + } else { + var converted = fromWorldToLatLng(p[1], p[2], p[3]); + var marker = new MarkerWithLabel({ + position: converted, + map: map, + labelContent: p[0], + labelAnchor: new google.maps.Point(-10, 30), + labelClass: "labels" + }); + + markers[p[0]] = marker; + } + } + + for(var m in markers) { + if(!(m in loggedin)) { + markers[m].setMap(null); + delete markers[m]; + } + } + + setTimeout(playerUpdate, 2000); + }, 'text', function() { alert('failed to get position data'); } ); +} diff --git a/web/marker.png b/web/marker.png new file mode 100644 index 00000000..1fd60c71 Binary files /dev/null and b/web/marker.png differ