diff --git a/configuration.txt b/configuration.txt index 5401e0a1..5f335235 100644 --- a/configuration.txt +++ b/configuration.txt @@ -73,6 +73,9 @@ display-whitelist: false # How often a tile gets rendered (in seconds). renderinterval: 1 +# Zoom-out tile update period - how often to scan for and process tile updates into zoom-out tiles (in seconds) +zoomoutperiod: 60 + # Tile hashing is used to minimize tile file updates when no changes have occurred - set to false to disable enabletilehash: true @@ -133,6 +136,8 @@ templates: enabled: true # # If bigworld set to true, use alternate directory layout better suited to large worlds # bigworld: true + # # Number of extra zoom-out levels for world (each level is twice as big as the previous one) + # extrazoomout: 3 center: x: 0 y: 64 @@ -209,6 +214,8 @@ templates: enabled: true # # If bigworld set to true, use alternate directory layout better suited to large worlds # bigworld: true + # # Number of extra zoom-out levels for world (each level is twice as big as the previous one) + # extrazoomout: 3 center: x: 0 y: 64 @@ -238,6 +245,8 @@ templates: enabled: true # # If bigworld set to true, use alternate directory layout better suited to large worlds # bigworld: true + # # Number of extra zoom-out levels for world (each level is twice as big as the previous one) + # extrazoomout: 3 center: x: 0 y: 64 @@ -312,6 +321,8 @@ worlds: # z: 0 # # If bigworld set to true, use alternate directory layout better suited to large worlds # bigworld: true + # # Number of extra zoom-out levels for world (each level is twice as big as the previous one) + # extrazoomout: 3 # maps: # - class: org.dynmap.flat.FlatMap # name: flat @@ -381,6 +392,8 @@ worlds: # x: 0 # y: 64 # z: 0 + # # Number of extra zoom-out levels for world (each level is twice as big as the previous one) + # extrazoomout: 3 # maps: # - class: org.dynmap.flat.FlatMap # name: flat diff --git a/src/main/java/org/dynmap/ClientConfigurationComponent.java b/src/main/java/org/dynmap/ClientConfigurationComponent.java index 6c795e79..aa54146f 100644 --- a/src/main/java/org/dynmap/ClientConfigurationComponent.java +++ b/src/main/java/org/dynmap/ClientConfigurationComponent.java @@ -33,6 +33,7 @@ public class ClientConfigurationComponent extends Component { s(wo, "center/y", wn.getFloat("center/y", 64.0f)); s(wo, "center/z", wn.getFloat("center/z", 0.0f)); s(wo, "bigworld", world.bigworld); + s(wo, "extrazoomout", world.extrazoomoutlevels); a(t, "worlds", wo); for(MapType mt : world.maps) { diff --git a/src/main/java/org/dynmap/DynmapWorld.java b/src/main/java/org/dynmap/DynmapWorld.java index 8933a129..ffa3bea6 100644 --- a/src/main/java/org/dynmap/DynmapWorld.java +++ b/src/main/java/org/dynmap/DynmapWorld.java @@ -5,8 +5,20 @@ import java.util.List; import org.bukkit.World; import org.bukkit.Location; +import org.dynmap.debug.Debug; +import org.dynmap.kzedmap.KzedMap; +import org.dynmap.kzedmap.KzedMap.KzedBufferedImage; +import org.dynmap.utils.FileLockManager; import org.dynmap.utils.MapChunkCache; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.FilenameFilter; +import java.io.IOException; +import java.util.HashMap; + +import javax.imageio.ImageIO; + public class DynmapWorld { public World world; public List maps = new ArrayList(); @@ -19,4 +31,251 @@ public class DynmapWorld { public boolean sendposition; public boolean sendhealth; public boolean bigworld; /* If true, deeper directory hierarchy */ + public int extrazoomoutlevels; /* Number of additional zoom out levels to generate */ + public File worldtilepath; + + private static class DirFilter implements FilenameFilter { + public boolean accept(File f, String n) { + if(!n.equals("..") && !n.equals(".")) { + File fn = new File(f, n); + return fn.isDirectory(); + } + return false; + } + } + + private static class PNGFileFilter implements FilenameFilter { + String prefix; + public PNGFileFilter(String pre) { prefix = pre; } + public boolean accept(File f, String n) { + if(n.endsWith(".png") && n.startsWith(prefix)) { + File fn = new File(f, n); + return fn.isFile(); + } + return false; + } + } + + public void freshenZoomOutFiles() { + for(int i = 0; i < extrazoomoutlevels; i++) { + freshenZoomOutFilesByLevel(i); + } + } + + private static class PrefixData { + int stepsize; + int[] stepseq; + boolean neg_step_x; + String baseprefix; + int zoomlevel; + String zoomprefix; + String fnprefix; + String zfnprefix; + } + + public void freshenZoomOutFilesByLevel(int zoomlevel) { + int cnt = 0; + Debug.debug("freshenZoomOutFiles(" + world.getName() + "," + zoomlevel + ")"); + if(worldtilepath.exists() == false) /* Quit if not found */ + return; + HashMap maptab = new HashMap(); + /* Build table of file prefixes and step sizes */ + for(MapType mt : maps) { + List pfx = mt.baseZoomFilePrefixes(); + int stepsize = mt.baseZoomFileStepSize(); + boolean neg_step_x = false; + if(stepsize < 0) { + stepsize = -stepsize; + neg_step_x = true; + } + int[] stepseq = mt.zoomFileStepSequence(); + for(String p : pfx) { + PrefixData pd = new PrefixData(); + pd.stepsize = stepsize; + pd.neg_step_x = neg_step_x; + pd.stepseq = stepseq; + pd.baseprefix = p; + pd.zoomlevel = zoomlevel; + pd.zoomprefix = "zzzzzzzzzzzz".substring(0, zoomlevel); + if(bigworld) { + if(zoomlevel > 0) { + pd.zoomprefix += "_"; + pd.zfnprefix = "z" + pd.zoomprefix; + } + else { + pd.zfnprefix = "z_"; + } + pd.fnprefix = pd.zoomprefix; + } + else { + pd.fnprefix = pd.zoomprefix + pd.baseprefix; + pd.zfnprefix = "z" + pd.fnprefix; + } + + maptab.put(p, pd); + } + } + if(bigworld) { /* If big world, next directories are map name specific */ + DirFilter df = new DirFilter(); + for(String pfx : maptab.keySet()) { /* Walk through prefixes, as directories */ + PrefixData pd = maptab.get(pfx); + File dname = new File(worldtilepath, pfx); + /* Now, go through subdirectories under this one, and process them */ + String[] subdir = dname.list(df); + if(subdir == null) continue; + for(String s : subdir) { + File sdname = new File(dname, s); + cnt += processZoomDirectory(sdname, pd); + } + } + } + else { /* Else, classic file layout */ + for(String pfx : maptab.keySet()) { /* Walk through prefixes, as directories */ + cnt += processZoomDirectory(worldtilepath, maptab.get(pfx)); + } + } + Debug.debug("freshenZoomOutFiles(" + world.getName() + "," + zoomlevel + ") - done (" + cnt + " updated files)"); + } + + + private static class ProcessTileRec { + File zf; + String zfname; + int x, y; + } + private String makeFilePath(PrefixData pd, int x, int y, boolean zoomed) { + if(bigworld) + return pd.baseprefix + "/" + ((x/pd.stepsize) >> 5) + "_" + ((y/pd.stepsize) >> 5) + "/" + (zoomed?pd.zfnprefix:pd.fnprefix) + x + "_" + y + ".png"; + else + return (zoomed?pd.zfnprefix:pd.fnprefix) + "_" + x + "_" + y + ".png"; + } + private int processZoomDirectory(File dir, PrefixData pd) { + Debug.debug("processZoomDirectory(" + dir.getPath() + "," + pd.baseprefix + ")"); + HashMap toprocess = new HashMap(); + int step = pd.stepsize << pd.zoomlevel; + String[] files = dir.list(new PNGFileFilter(pd.fnprefix)); + if(files == null) + return 0; + for(String fn : files) { + /* Build file object */ + File f = new File(dir, fn); + /* Parse filename to predict zoomed out file */ + fn = fn.substring(0, fn.lastIndexOf('.')); /* Strip off extension */ + String[] tok = fn.split("_"); /* Split by underscores */ + int x = 0; + int y = 0; + boolean parsed = false; + if(tok.length >= 2) { + try { + x = Integer.parseInt(tok[tok.length-2]); + y = Integer.parseInt(tok[tok.length-1]); + parsed = true; + } catch (NumberFormatException nfx) { + } + } + if(!parsed) + continue; + if(pd.neg_step_x) x = -x; + if(x >= 0) + x = x - (x % (2*step)); + else + x = x + (x % (2*step)); + if(pd.neg_step_x) x = -x; + if(y >= 0) + y = y - (y % (2*step)); + else + y = y + (y % (2*step)); + /* Make name of corresponding zoomed tile */ + String zfname = makeFilePath(pd, x, y, true); + File zf = new File(worldtilepath, zfname); + /* If zoom file exists and is older than our file, nothing to do */ + if(zf.exists() && (zf.lastModified() >= f.lastModified())) { + continue; + } + String zfpath = zf.getPath(); + if(!toprocess.containsKey(zfpath)) { + ProcessTileRec rec = new ProcessTileRec(); + rec.zf = zf; + rec.x = x; + rec.y = y; + rec.zfname = zfname; + toprocess.put(zfpath, rec); + } + } + int cnt = 0; + /* Do processing */ + for(ProcessTileRec s : toprocess.values()) { + processZoomTile(pd, dir, s.zf, s.zfname, s.x - (pd.neg_step_x?step:0), s.y); + cnt++; + } + Debug.debug("processZoomDirectory(" + dir.getPath() + "," + pd.baseprefix + ") - done (" + cnt + " files)"); + return cnt; + } + private void processZoomTile(PrefixData pd, File dir, File zf, String zfname, int tx, int ty) { + Debug.debug("processZoomFile(" + pd.baseprefix + "," + dir.getPath() + "," + zf.getPath() + "," + tx + "," + ty + ")"); + int width = 128, height = 128; + BufferedImage zIm = null; + KzedBufferedImage kzIm = null; + int[] argb = new int[width*height]; + int step = pd.stepsize << pd.zoomlevel; + + /* create image buffer */ + kzIm = KzedMap.allocateBufferedImage(width, height); + zIm = kzIm.buf_img; + + for(int i = 0; i < 4; i++) { + File f = new File(worldtilepath, makeFilePath(pd, (tx + step*(1&pd.stepseq[i])), (ty + step*(pd.stepseq[i]>>1)), false)); + if(f.exists()) { + BufferedImage im = null; + FileLockManager.getReadLock(f); + try { + im = ImageIO.read(f); + } catch (IOException e) { + } catch (IndexOutOfBoundsException e) { + } + FileLockManager.releaseReadLock(f); + if(im != null) { + im.getRGB(0, 0, width, height, argb, 0, width); /* Read data */ + im.flush(); + /* Do binlinear scale to 64x64 */ + Color c1 = new Color(); + for(int y = 0; y < height; y += 2) { + for(int x = 0; x < width; x += 2) { + int red = 0; + int green = 0; + int blue = 0; + int alpha = 0; + for(int yy = y; yy < y+2; yy++) { + for(int xx = x; xx < x+2; xx++) { + c1.setARGB(argb[(yy*width)+xx]); + red += c1.getRed(); + green += c1.getGreen(); + blue += c1.getBlue(); + alpha += c1.getAlpha(); + } + } + c1.setRGBA(red>>2, green>>2, blue>>2, alpha>>2); + argb[(y*width/2) + (x/2)] = c1.getARGB(); + } + } + /* blit scaled rendered tile onto zoom-out tile */ + zIm.setRGB(((i>>1) != 0)?0:width/2, (i & 1) * height/2, width/2, height/2, argb, 0, width); + } + } + } + FileLockManager.getWriteLock(zf); + try { + if(!zf.getParentFile().exists()) + zf.getParentFile().mkdirs(); + FileLockManager.imageIOWrite(zIm, "png", zf); + Debug.debug("Saved zoom-out tile at " + zf.getPath()); + } catch (IOException e) { + Debug.error("Failed to save zoom-out tile: " + zf.getName(), e); + } catch (java.lang.NullPointerException e) { + Debug.error("Failed to save zoom-out tile (NullPointerException): " + zf.getName(), e); + } + FileLockManager.releaseWriteLock(zf); + KzedMap.freeBufferedImage(kzIm); + MapManager.mapman.pushUpdate(this.world, new Client.Tile(zfname)); + } } diff --git a/src/main/java/org/dynmap/MapManager.java b/src/main/java/org/dynmap/MapManager.java index be548053..88c68893 100644 --- a/src/main/java/org/dynmap/MapManager.java +++ b/src/main/java/org/dynmap/MapManager.java @@ -26,13 +26,15 @@ public class MapManager { public AsynchronousQueue tileQueue; private static final int DEFAULT_CHUNKS_PER_TICK = 200; - + private static final int DEFAULT_ZOOMOUT_PERIOD = 60; public List worlds = new ArrayList(); public Map worldsLookup = new HashMap(); private BukkitScheduler scheduler; private DynmapPlugin plug_in; private long timeslice_int = 0; /* In milliseconds */ private int max_chunk_loads_per_tick = DEFAULT_CHUNKS_PER_TICK; + + private int zoomout_period = DEFAULT_ZOOMOUT_PERIOD; /* Zoom-out tile processing period, in seconds */ /* Which fullrenders are active */ private HashMap active_renders = new HashMap(); /* List of MapChunkCache requests to be processed */ @@ -261,6 +263,17 @@ public class MapManager { } } + private class DoZoomOutProcessing implements Runnable { + public void run() { + Debug.debug("DoZoomOutProcessing started"); + for(DynmapWorld w : worlds) { + w.freshenZoomOutFiles(); + } + renderpool.schedule(this, zoomout_period, TimeUnit.SECONDS); + Debug.debug("DoZoomOutProcessing finished"); + } + } + public MapManager(DynmapPlugin plugin, ConfigurationNode configuration) { plug_in = plugin; mapman = this; @@ -276,6 +289,10 @@ public class MapManager { timeslice_int = (long)(configuration.getDouble("timesliceinterval", 0.0) * 1000); max_chunk_loads_per_tick = configuration.getInteger("maxchunkspertick", DEFAULT_CHUNKS_PER_TICK); if(max_chunk_loads_per_tick < 5) max_chunk_loads_per_tick = 5; + /* Get zoomout processing periond in seconds */ + zoomout_period = configuration.getInteger("zoomoutperiod", DEFAULT_ZOOMOUT_PERIOD); + if(zoomout_period < 5) zoomout_period = 5; + scheduler = plugin.getServer().getScheduler(); hashman = new TileHashManager(DynmapPlugin.tilesDirectory, configuration.getBoolean("enabletilehash", true)); @@ -288,6 +305,7 @@ public class MapManager { scheduler.scheduleSyncRepeatingTask(plugin, new CheckWorldTimes(), 5*20, 5*20); /* Check very 5 seconds */ scheduler.scheduleSyncRepeatingTask(plugin, new ProcessChunkLoads(), 1, 1); /* Chunk loader task */ + } void renderFullWorld(Location l, CommandSender sender) { @@ -343,6 +361,8 @@ public class MapManager { dynmapWorld.sendposition = worldConfiguration.getBoolean("sendposition", true); dynmapWorld.sendhealth = worldConfiguration.getBoolean("sendhealth", true); dynmapWorld.bigworld = worldConfiguration.getBoolean("bigworld", false); + dynmapWorld.extrazoomoutlevels = worldConfiguration.getInteger("extrazoomout", 0); + dynmapWorld.worldtilepath = new File(plug_in.tilesDirectory, w.getName()); if(loclist != null) { for(ConfigurationNode loc : loclist) { Location lx = new Location(w, loc.getDouble("x", 0), loc.getDouble("y", 64), loc.getDouble("z", 0)); @@ -422,6 +442,7 @@ public class MapManager { public void startRendering() { tileQueue.start(); renderpool = new DynmapScheduledThreadPoolExecutor(); + renderpool.schedule(new DoZoomOutProcessing(), 60000, TimeUnit.MILLISECONDS); } public void stopRendering() { diff --git a/src/main/java/org/dynmap/MapType.java b/src/main/java/org/dynmap/MapType.java index e5c0c979..b0e92bbf 100644 --- a/src/main/java/org/dynmap/MapType.java +++ b/src/main/java/org/dynmap/MapType.java @@ -27,5 +27,12 @@ public abstract class MapType { public boolean isHightestBlockYDataNeeded() { return false; } public boolean isRawBiomeDataNeeded() { return false; } public boolean isBlockTypeDataNeeded() { return true; } - + + public abstract List baseZoomFilePrefixes(); + public abstract int baseZoomFileStepSize(); + /** + * Step sequence for creating zoomed file: first index is top-left, second top-right, third bottom-left, forth bottom-right + * Values correspond to tile X,Y (0), X+step,Y (1), X,Y+step (2), X+step,Y+step (3) + */ + public abstract int[] zoomFileStepSequence(); } diff --git a/src/main/java/org/dynmap/flat/FlatMap.java b/src/main/java/org/dynmap/flat/FlatMap.java index f97e9add..2274feea 100644 --- a/src/main/java/org/dynmap/flat/FlatMap.java +++ b/src/main/java/org/dynmap/flat/FlatMap.java @@ -300,7 +300,7 @@ public class FlatMap extends MapType { /* If day too, handle it */ if(night_and_day) { - File dayfile = new File(outputFile.getParent(), tile.getDayFilename()); + File dayfile = new File(tile.getDynmapWorld().worldtilepath, tile.getDayFilename()); FileLockManager.getWriteLock(dayfile); crc = hashman.calculateTileHash(argb_buf_day); if((!dayfile.exists()) || (crc != hashman.getImageHashCode(tile.getKey(), "day", t.x, t.y))) { @@ -406,6 +406,19 @@ public class FlatMap extends MapType { return prefix; } + public List baseZoomFilePrefixes() { + ArrayList s = new ArrayList(); + s.add(getName() + "_128"); + if(night_and_day) + s.add(getName()+"_day_128"); + return s; + } + + public int baseZoomFileStepSize() { return 1; } + + private static final int[] stepseq = { 1, 3, 0, 2 }; + + public int[] zoomFileStepSequence() { return stepseq; } public static class FlatMapTile extends MapTile { FlatMap map; @@ -427,7 +440,7 @@ public class FlatMap extends MapType { public String getFilename() { if(fname == null) { if(world.bigworld) - fname = map.prefix + "/" + ((-(y+1))>>5) + "_" + (x>>5) + "/" + size + "_" + -(y+1) + "_" + x + ".png"; + fname = map.prefix + "_" + size + "/" + ((-(y+1))>>5) + "_" + (x>>5) + "/" + -(y+1) + "_" + x + ".png"; else fname = map.prefix + "_" + size + "_" + -(y+1) + "_" + x + ".png"; } @@ -437,7 +450,7 @@ public class FlatMap extends MapType { public String getDayFilename() { if(fname_day == null) { if(world.bigworld) - fname_day = map.prefix + "_day/" + ((-(y+1))>>5) + "_" + (x>>5) + "/" + size + "_" + -(y+1) + "_" + x + ".png"; + fname_day = map.prefix + "_day_" + size + "/" + ((-(y+1))>>5) + "_" + (x>>5) + "/" + -(y+1) + "_" + x + ".png"; else fname_day = map.prefix + "_day_" + size + "_" + -(y+1) + "_" + x + ".png"; } diff --git a/src/main/java/org/dynmap/kzedmap/CaveTileRenderer.java b/src/main/java/org/dynmap/kzedmap/CaveTileRenderer.java index 67a02e71..7b540440 100644 --- a/src/main/java/org/dynmap/kzedmap/CaveTileRenderer.java +++ b/src/main/java/org/dynmap/kzedmap/CaveTileRenderer.java @@ -11,6 +11,8 @@ public class CaveTileRenderer extends DefaultTileRenderer { super(configuration); } + public boolean isNightAndDayEnabled() { return false; } + @Override protected void scan(World world, int seq, boolean isnether, final Color result, final Color result_day, MapIterator mapiter) { diff --git a/src/main/java/org/dynmap/kzedmap/DefaultTileRenderer.java b/src/main/java/org/dynmap/kzedmap/DefaultTileRenderer.java index d5c068f7..b32101d4 100644 --- a/src/main/java/org/dynmap/kzedmap/DefaultTileRenderer.java +++ b/src/main/java/org/dynmap/kzedmap/DefaultTileRenderer.java @@ -48,6 +48,8 @@ public class DefaultTileRenderer implements MapTileRenderer { return name; } + public boolean isNightAndDayEnabled() { return night_and_day; } + public DefaultTileRenderer(ConfigurationNode configuration) { this.configuration = configuration; name = (String) configuration.get("prefix"); @@ -282,7 +284,8 @@ public class DefaultTileRenderer implements MapTileRenderer { mtile.file = fname; boolean updated_dfname = false; - File dfname = new File(fname.getParent(), mtile.getDayFilename()); + + File dfname = new File(mtile.getDynmapWorld().worldtilepath, mtile.getDayFilename()); if(img_day != null) { FileLockManager.getWriteLock(dfname); crc = hashman.calculateTileHash(img.argb_buf); @@ -311,7 +314,7 @@ public class DefaultTileRenderer implements MapTileRenderer { boolean ztile_updated = false; FileLockManager.getWriteLock(zoomFile); if(updated_fname || (!zoomFile.exists())) { - saveZoomedTile(zmtile, zoomFile, zimg, ox, oy); + saveZoomedTile(zmtile, zoomFile, zimg, ox, oy, null); MapManager.mapman.pushUpdate(zmtile.getWorld(), new Client.Tile(zmtile.getFilename())); ztile_updated = true; @@ -321,11 +324,11 @@ public class DefaultTileRenderer implements MapTileRenderer { MapManager.mapman.updateStatistics(zmtile, null, true, ztile_updated, !rendered); if(zimg_day != null) { - File zoomFile_day = new File(zoomFile.getParent(), zmtile.getDayFilename()); + File zoomFile_day = new File(zmtile.getDynmapWorld().worldtilepath, zmtile.getDayFilename()); ztile_updated = false; FileLockManager.getWriteLock(zoomFile_day); if(updated_dfname || (!zoomFile_day.exists())) { - saveZoomedTile(zmtile, zoomFile_day, zimg_day, ox, oy); + saveZoomedTile(zmtile, zoomFile_day, zimg_day, ox, oy, "day"); MapManager.mapman.pushUpdate(zmtile.getWorld(), new Client.Tile(zmtile.getDayFilename())); ztile_updated = true; @@ -337,7 +340,7 @@ public class DefaultTileRenderer implements MapTileRenderer { } private void saveZoomedTile(final KzedZoomedMapTile zmtile, final File zoomFile, - final KzedBufferedImage zimg, int ox, int oy) { + final KzedBufferedImage zimg, int ox, int oy, String subkey) { BufferedImage zIm = null; KzedBufferedImage kzIm = null; try { @@ -372,6 +375,7 @@ public class DefaultTileRenderer implements MapTileRenderer { } catch (java.lang.NullPointerException e) { Debug.error("Failed to save zoom-out tile (NullPointerException): " + zoomFile.getName(), e); } + if(zIm_allocated) KzedMap.freeBufferedImage(kzIm); else diff --git a/src/main/java/org/dynmap/kzedmap/KzedMap.java b/src/main/java/org/dynmap/kzedmap/KzedMap.java index 4963edb4..85d2d757 100644 --- a/src/main/java/org/dynmap/kzedmap/KzedMap.java +++ b/src/main/java/org/dynmap/kzedmap/KzedMap.java @@ -326,6 +326,22 @@ public class KzedMap extends MapType { return false; } + public List baseZoomFilePrefixes() { + ArrayList s = new ArrayList(); + for(MapTileRenderer r : renderers) { + s.add("z" + r.getName()); + if(r.isNightAndDayEnabled()) + s.add("z" + r.getName() + "_day"); + } + return s; + } + /* Return negative to flag negative X walk */ + public int baseZoomFileStepSize() { return -zTileWidth; } + + private static final int[] stepseq = { 0, 2, 1, 3 }; + + public int[] zoomFileStepSequence() { return stepseq; } + public String getName() { return "KzedMap"; } diff --git a/src/main/java/org/dynmap/kzedmap/MapTileRenderer.java b/src/main/java/org/dynmap/kzedmap/MapTileRenderer.java index 09390df7..01b1270d 100644 --- a/src/main/java/org/dynmap/kzedmap/MapTileRenderer.java +++ b/src/main/java/org/dynmap/kzedmap/MapTileRenderer.java @@ -15,4 +15,5 @@ public interface MapTileRenderer { boolean isBiomeDataNeeded(); boolean isRawBiomeDataNeeded(); + boolean isNightAndDayEnabled(); } diff --git a/src/main/java/org/dynmap/regions/RegionHandler.java b/src/main/java/org/dynmap/regions/RegionHandler.java index 48aa9616..01ecd248 100644 --- a/src/main/java/org/dynmap/regions/RegionHandler.java +++ b/src/main/java/org/dynmap/regions/RegionHandler.java @@ -72,10 +72,7 @@ public class RegionHandler extends FileHandler { /* If not in list, remove it */ if(!idlist.contains(id)) { regionData.remove(id); - log.info("discard " + id); } - else - log.info("keep " + id); } } try { diff --git a/src/main/java/org/dynmap/utils/FileLockManager.java b/src/main/java/org/dynmap/utils/FileLockManager.java index c3f4d679..a95b4f80 100644 --- a/src/main/java/org/dynmap/utils/FileLockManager.java +++ b/src/main/java/org/dynmap/utils/FileLockManager.java @@ -117,6 +117,7 @@ public class FileLockManager { while(!done) { try { ImageIO.write(img, type, fname); + fname.setLastModified(System.currentTimeMillis()); done = true; } catch (FileNotFoundException fnfx) { /* This seems to be what we get when file is locked by reader */ if(retrycnt < MAX_WRITE_RETRIES) { diff --git a/web/js/flatmap.js b/web/js/flatmap.js index 63d51d52..67633b50 100644 --- a/web/js/flatmap.js +++ b/web/js/flatmap.js @@ -1,5 +1,6 @@ function FlatProjection() {} FlatProjection.prototype = { + extrazoom: 0, fromLatLngToPoint: function(latLng) { return new google.maps.Point(latLng.lat()*config.tileWidth, latLng.lng()*config.tileHeight); }, @@ -7,7 +8,7 @@ FlatProjection.prototype = { return new google.maps.LatLng(point.x/config.tileWidth, point.y/config.tileHeight); }, fromWorldToLatLng: function(x, y, z) { - return new google.maps.LatLng(-z / config.tileWidth, x / config.tileHeight); + return new google.maps.LatLng(-z / config.tileWidth / (1 << this.extrazoom), x / config.tileHeight / (1 << this.extrazoom)); } }; @@ -28,13 +29,25 @@ FlatMapType.prototype = $.extend(new DynMapType(), { var dnprefix = ''; if(this.dynmap.map.mapTypes[this.dynmap.map.mapTypeId].nightandday && this.dynmap.serverday) dnprefix = '_day'; - - if(this.dynmap.world.bigworld) - tileName = this.prefix + dnprefix + '/' + (coord.x >> 5) + '_' + (coord.y >> 5) + - '/128_' + coord.x + '_' + coord.y + '.png'; - else - tileName = this.prefix + dnprefix + '_128_' + coord.x + '_' + coord.y + '.png'; - imgSize = Math.pow(2, 7+zoom); + var extrazoom = this.dynmap.world.extrazoomout; + if(zoom < extrazoom) { + var scale = 1 << (extrazoom-zoom); + var zprefix = "zzzzzzzzzzzz".substring(0, extrazoom-zoom); + if(this.dynmap.world.bigworld) + tileName = this.prefix + dnprefix + '_128/' + ((scale*coord.x) >> 5) + '_' + ((scale*coord.y) >> 5) + + '/' + zprefix + "_" + (scale*coord.x) + '_' + (scale*coord.y) + '.png'; + else + tileName = zprefix + this.prefix + dnprefix + '_128_' + (scale*coord.x) + '_' + (scale*coord.y) + '.png'; + imgSize = 128; + } + else { + if(this.dynmap.world.bigworld) + tileName = this.prefix + dnprefix + '_128/' + (coord.x >> 5) + '_' + (coord.y >> 5) + + '/' + coord.x + '_' + coord.y + '.png'; + else + tileName = this.prefix + dnprefix + '_128_' + coord.x + '_' + coord.y + '.png'; + imgSize = Math.pow(2, 7+zoom-extrazoom); + } var tile = $('
') .addClass('tile') .css({ @@ -58,7 +71,15 @@ FlatMapType.prototype = $.extend(new DynMapType(), { }, updateTileSize: function(zoom) { var size; - size = Math.pow(2, 7+zoom); + var extrazoom = this.dynmap.world.extrazoomout; + this.projection.extrazoom = extrazoom; + this.maxZoom = 3 + extrazoom; + if (zoom <= extrazoom) { + size = 128; + } + else { + size = Math.pow(2, 7+zoom-extrazoom); + } this.tileSize = new google.maps.Size(size, size); } }); diff --git a/web/js/kzedmaps.js b/web/js/kzedmaps.js index 6cda0d67..971af58e 100644 --- a/web/js/kzedmaps.js +++ b/web/js/kzedmaps.js @@ -1,5 +1,6 @@ function KzedProjection() {} KzedProjection.prototype = { + extrazoom: 0, fromLatLngToPoint: function(latLng) { var x = latLng.lng() * config.tileWidth; var y = latLng.lat() * config.tileHeight; @@ -18,9 +19,10 @@ KzedProjection.prototype = { var dz = +z; var px = dx + dz; var py = dx - dz - dy; + var scale = 2 << this.extrazoom; - var lng = -px / config.tileWidth / 2 + 0.5; - var lat = py / config.tileHeight / 2; + var lng = -px / config.tileWidth / scale + (1.0 / scale); + var lat = py / config.tileHeight / scale; return new google.maps.LatLng(lat, lng); } @@ -45,25 +47,27 @@ KzedMapType.prototype = $.extend(new DynMapType(), { var dnprefix = ''; if(this.dynmap.map.mapTypes[this.dynmap.map.mapTypeId].nightandday && this.dynmap.serverday) - dnprefix = '_day'; - - if (zoom == 0) { + dnprefix = '_day'; + var extrazoom = this.dynmap.world.extrazoomout; + if (zoom <= extrazoom) { + var zpre = 'zzzzzzzzzzzzzzzz'.substring(0, extrazoom-zoom+1); // Most zoomed out tiles. tileSize = 128; - imgSize = tileSize; + imgSize = tileSize; + var tilescale = 2 << (extrazoom-zoom); if (this.dynmap.world.bigworld) { - tileName = 'z' + this.prefix + dnprefix + '/' + ((-coord.x * tileSize*2)>>12) + - '_' + ((coord.y * tileSize*2) >> 12) + '/' + - (-coord.x * tileSize*2) + '_' + (coord.y * tileSize*2) + '.png'; + tileName = zpre + this.prefix + dnprefix + '/' + ((-coord.x * tileSize*tilescale)>>12) + + '_' + ((coord.y * tileSize*tilescale) >> 12) + '/' + + (-coord.x * tileSize*tilescale) + '_' + (coord.y * tileSize*tilescale) + '.png'; } else { - tileName = 'z' + this.prefix + dnprefix + '_' + (-coord.x * tileSize*2) + '_' + (coord.y * tileSize*2) + '.png'; + tileName = zpre + this.prefix + dnprefix + '_' + (-coord.x * tileSize*tilescale) + '_' + (coord.y * tileSize*tilescale) + '.png'; } } else { // Other zoom levels. tileSize = 128; - imgSize = Math.pow(2, 6+zoom); + imgSize = Math.pow(2, 6+zoom-extrazoom); if(this.dynmap.world.bigworld) { tileName = this.prefix + dnprefix + '/' + ((-coord.x*tileSize) >> 12) + '_' + ((coord.y*tileSize)>>12) + '/' + @@ -109,10 +113,13 @@ KzedMapType.prototype = $.extend(new DynMapType(), { }, updateTileSize: function(zoom) { var size; - if (zoom == 0) { + var extrazoom = this.dynmap.world.extrazoomout; + this.projection.extrazoom = extrazoom; + this.maxZoom = 3 + extrazoom; + if (zoom <= extrazoom) { size = 128; } else { - size = Math.pow(2, 6+zoom); + size = Math.pow(2, 6+zoom-extrazoom); } this.tileSize = new google.maps.Size(size, size); }