From d2c947653d05c3d969fb52414e764bb720b7da56 Mon Sep 17 00:00:00 2001 From: Mike Primm Date: Fri, 27 May 2011 00:56:56 -0500 Subject: [PATCH] Add night view via ambientlight setting on Flat and Surface maps, add night-and-day setting to generate both night (default) and day (prefix+'_day') versions of tiles, and add transparency to generated PNG files --- configuration.txt | 18 ++ src/main/java/org/dynmap/ChunkSnapshot.java | 46 +++ src/main/java/org/dynmap/Color.java | 23 +- .../java/org/dynmap/CraftChunkSnapshot.java | 2 +- src/main/java/org/dynmap/DynmapWorld.java | 2 + src/main/java/org/dynmap/MapChunkCache.java | 213 +++++++++++-- src/main/java/org/dynmap/MapManager.java | 22 +- src/main/java/org/dynmap/MapTile.java | 2 + src/main/java/org/dynmap/flat/FlatMap.java | 131 +++++--- .../org/dynmap/kzedmap/CaveTileRenderer.java | 26 +- .../dynmap/kzedmap/DefaultTileRenderer.java | 297 ++++++++++++++---- .../dynmap/kzedmap/HighlightTileRenderer.java | 22 +- src/main/java/org/dynmap/kzedmap/KzedMap.java | 2 +- .../java/org/dynmap/kzedmap/KzedMapTile.java | 5 + .../org/dynmap/kzedmap/KzedZoomedMapTile.java | 5 + 15 files changed, 645 insertions(+), 171 deletions(-) create mode 100644 src/main/java/org/dynmap/ChunkSnapshot.java diff --git a/configuration.txt b/configuration.txt index 35d0ada2..c4b6b5d5 100644 --- a/configuration.txt +++ b/configuration.txt @@ -125,6 +125,9 @@ templates: title: "Flat" prefix: flat colorscheme: default + # To render a world as a "night view", set shadowstrength and ambientlight + # shadowstrength: 1.0 + # ambientlight: 4 - class: org.dynmap.kzedmap.KzedMap renderers: - class: org.dynmap.kzedmap.DefaultTileRenderer @@ -135,6 +138,8 @@ templates: colorscheme: default # Add shadows to world (based on top-down shadows from chunk data) # shadowstrength: 1.0 + # To render a world as a "night view", set shadowstrength and ambientlight + # ambientlight: 4 # Sets the icon to 'images/block_custom.png' # icon: custom #- class: org.dynmap.kzedmap.HighlightTileRenderer @@ -181,6 +186,14 @@ worlds: # title: "World" # Use 'enabled: false' to disable a certain world. # enabled: false + # # If world isn't contiguous chunks (due to teleporting, for example), fullrender needs to be given other locations to scan for tiles on each patch of chunks + # fullrenderlocations: + # - x: 10000 + # y: 64 + # z: 20000 + # - x: -15000 + # y: 64 + # z: -5000 # Use 'template: mycustomtemplate' to use the properties specified in the template 'mycustomtemplate' to this world. Default it is set to the environment-name (normal or nether). # template: mycustomtemplate # Rest of comes from template - uncomment to tailor for world specifically @@ -194,6 +207,9 @@ worlds: # title: "Flat" # prefix: flat # colorscheme: default + # # To render a world as a "night view", set shadowstrength and ambientlight + # # shadowstrength: 1.0 + # # ambientlight: 4 # - class: org.dynmap.kzedmap.KzedMap # renderers: # - class: org.dynmap.kzedmap.DefaultTileRenderer @@ -204,6 +220,8 @@ worlds: # colorscheme: default # # Add shadows to world (based on top-down shadows from chunk data) # # shadowstrength: 1.0 + # # To render a world as a "night view", set shadowstrength and ambientlight + # # ambientlight: 4 # # Sets the icon to 'images/block_custom.png' # # icon: custom # #- class: org.dynmap.kzedmap.HighlightTileRenderer diff --git a/src/main/java/org/dynmap/ChunkSnapshot.java b/src/main/java/org/dynmap/ChunkSnapshot.java new file mode 100644 index 00000000..7dbf2363 --- /dev/null +++ b/src/main/java/org/dynmap/ChunkSnapshot.java @@ -0,0 +1,46 @@ +package org.dynmap; + +/** + * Represents a static, thread-safe snapshot of chunk of blocks + * Purpose is to allow clean, efficient copy of a chunk data to be made, and then handed off for processing in another thread (e.g. map rendering) + */ +public interface ChunkSnapshot { + /** + * Get block type for block at corresponding coordinate in the chunk + * + * @param x 0-15 + * @param y 0-127 + * @param z 0-15 + * @return 0-255 + */ + public int getBlockTypeId(int x, int y, int z); + /** + * Get block data for block at corresponding coordinate in the chunk + * + * @param x 0-15 + * @param y 0-127 + * @param z 0-15 + * @return 0-15 + */ + public int getBlockData(int x, int y, int z); + /** + * Get sky light level for block at corresponding coordinate in the chunk + * + * @param x 0-15 + * @param y 0-127 + * @param z 0-15 + * @return 0-15 + */ + public int getBlockSkyLight(int x, int y, int z); + /** + * Get light level emitted by block at corresponding coordinate in the chunk + * + * @param x 0-15 + * @param y 0-127 + * @param z 0-15 + * @return 0-15 + */ + public int getBlockEmittedLight(int x, int y, int z); + + public int getHighestBlockYAt(int x, int z); +} diff --git a/src/main/java/org/dynmap/Color.java b/src/main/java/org/dynmap/Color.java index 18812e2e..019d8bcf 100644 --- a/src/main/java/org/dynmap/Color.java +++ b/src/main/java/org/dynmap/Color.java @@ -5,7 +5,7 @@ package org.dynmap; * of them during rendering */ public class Color { - /* RGBA value */ + /* ARGB value */ private int val; public static final int TRANSPARENT = 0; @@ -20,16 +20,16 @@ public class Color { setTransparent(); } public final int getRed() { - return (val >> 24) & 0xFF; - } - public final int getGreen() { return (val >> 16) & 0xFF; } - public final int getBlue() { + public final int getGreen() { return (val >> 8) & 0xFF; } + public final int getBlue() { + return val & 0xFF; + } public final int getAlpha() { - return (val & 0xFF); + return ((val >> 24) & 0xFF); } public final boolean isTransparent() { return (val == TRANSPARENT); @@ -41,6 +41,15 @@ public class Color { val = c.val; } public final void setRGBA(int red, int green, int blue, int alpha) { - val = ((red & 0xFF) << 24) | ((green & 0xFF) << 16) | ((blue & 0xFF) << 8) | (alpha & 0xFF); + val = ((alpha & 0xFF) << 24) | ((red & 0xFF) << 16) | ((green & 0xFF) << 8) | (blue & 0xFF); + } + public final int getARGB() { + return val; + } + public final void setARGB(int c) { + val = c; + } + public final int getComponent(int idx) { + return 0xFF & (val >> ((3-idx)*8)); } } diff --git a/src/main/java/org/dynmap/CraftChunkSnapshot.java b/src/main/java/org/dynmap/CraftChunkSnapshot.java index 8ab42381..ccc3bdde 100644 --- a/src/main/java/org/dynmap/CraftChunkSnapshot.java +++ b/src/main/java/org/dynmap/CraftChunkSnapshot.java @@ -4,7 +4,7 @@ package org.dynmap; * Represents a static, thread-safe snapshot of chunk of blocks * Purpose is to allow clean, efficient copy of a chunk data to be made, and then handed off for processing in another thread (e.g. map rendering) */ -public class CraftChunkSnapshot { +public class CraftChunkSnapshot implements ChunkSnapshot { private final int x, z; private final byte[] buf; /* Flat buffer in uncompressed chunk file format */ diff --git a/src/main/java/org/dynmap/DynmapWorld.java b/src/main/java/org/dynmap/DynmapWorld.java index 0973c4e9..e40fdc8d 100644 --- a/src/main/java/org/dynmap/DynmapWorld.java +++ b/src/main/java/org/dynmap/DynmapWorld.java @@ -4,10 +4,12 @@ import java.util.ArrayList; import java.util.List; import org.bukkit.World; +import org.bukkit.Location; public class DynmapWorld { public World world; public List maps = new ArrayList(); public UpdateQueue updates = new UpdateQueue(); public ConfigurationNode configuration; + public List seedloc; } diff --git a/src/main/java/org/dynmap/MapChunkCache.java b/src/main/java/org/dynmap/MapChunkCache.java index bd4efb10..7813e3c3 100644 --- a/src/main/java/org/dynmap/MapChunkCache.java +++ b/src/main/java/org/dynmap/MapChunkCache.java @@ -20,9 +20,164 @@ public class MapChunkCache { private int x_min, x_max, z_min, z_max; private int x_dim; - private CraftChunkSnapshot[] snaparray; /* Index = (x-x_min) + ((z-z_min)*x_dim) */ + private ChunkSnapshot[] snaparray; /* Index = (x-x_min) + ((z-z_min)*x_dim) */ private LinkedList loadedChunks = new LinkedList(); + /** + * Iterator for traversing map chunk cache (base is for non-snapshot) + */ + public class MapIterator { + public int x, y, z; + MapIterator(int x0, int y0, int z0) { + initialize(x0, y0, z0); + } + public void initialize(int x0, int y0, int z0) { + this.x = x0; + this.y = y0; + this.z = z0; + } + public int getBlockTypeID() { + return w.getBlockTypeIdAt(x, y, z); + } + public int getBlockData() { + return w.getBlockAt(x, y, z).getData(); + } + public int getHighestBlockYAt() { + return w.getHighestBlockYAt(x, z); + } + public int getBlockSkyLight() { + return 15; + } + public int getBlockEmittedLight() { + return 0; + } + public void incrementX() { + x++; + } + public void decrementX() { + x--; + } + public void incrementY() { + y++; + } + public void decrementY() { + y--; + } + public void incrementZ() { + z++; + } + public void decrementZ() { + z--; + } + public void setY(int y) { + this.y = y; + } + } + + /** + * Iterator for snapshot mode + */ + public class SnapshotMapIterator extends MapIterator { + private ChunkSnapshot snap; + private int x4, z4; + + public SnapshotMapIterator(int x0, int y0, int z0) { + super(x0, y0, z0); + } + public void initialize(int x0, int y0, int z0) { + super.initialize(x0, y0, z0); + try { + snap = snaparray[((x>>4) - x_min) + ((z>>4) - z_min) * x_dim]; + } catch (ArrayIndexOutOfBoundsException aioobx) { + snap = EMPTY; + } + x4 = x0 & 0xF; + z4 = z0 & 0xF; + } + public int getBlockTypeID() { + return snap.getBlockTypeId(x4, y, z4); + } + public int getBlockData() { + return snap.getBlockData(x4, y, z4); + } + public int getHighestBlockYAt() { + return snap.getHighestBlockYAt(x4, z4); + } + public int getBlockSkyLight() { + return snap.getBlockSkyLight(x4, y, z4); + } + public int getBlockEmittedLight() { + return snap.getBlockEmittedLight(x4, y, z4); + } + public void incrementX() { + x++; x4 = x & 0xF; + if(x4 == 0) { /* Next chunk? */ + try { + snap = snaparray[((x>>4) - x_min) + ((z>>4) - z_min) * x_dim]; + } catch (ArrayIndexOutOfBoundsException aioobx) { + snap = EMPTY; + } + } + } + public void decrementX() { + x--; x4 = x & 0xF; + if(x4 == 15) { /* Next chunk? */ + try { + snap = snaparray[((x>>4) - x_min) + ((z>>4) - z_min) * x_dim]; + } catch (ArrayIndexOutOfBoundsException aioobx) { + snap = EMPTY; + } + } + } + public void incrementY() { + y++; + } + public void decrementY() { + y--; + } + public void incrementZ() { + z++; z4 = z & 0xF; + if(z4 == 0) { /* Next chunk? */ + try { + snap = snaparray[((x>>4) - x_min) + ((z>>4) - z_min) * x_dim]; + } catch (ArrayIndexOutOfBoundsException aioobx) { + snap = EMPTY; + } + } + } + public void decrementZ() { + z--; z4 = z & 0xF; + if(z4 == 15) { /* Next chunk? */ + try { + snap = snaparray[((x>>4) - x_min) + ((z>>4) - z_min) * x_dim]; + } catch (ArrayIndexOutOfBoundsException aioobx) { + snap = EMPTY; + } + } + } + } + /** + * Chunk cache for representing unloaded chunk + */ + private static class EmptyChunk implements ChunkSnapshot { + public final int getBlockTypeId(int x, int y, int z) { + return 0; + } + public final int getBlockData(int x, int y, int z) { + return 0; + } + public final int getBlockSkyLight(int x, int y, int z) { + return 15; + } + public final int getBlockEmittedLight(int x, int y, int z) { + return 0; + } + public final int getHighestBlockYAt(int x, int z) { + return 1; + } + } + + private static final EmptyChunk EMPTY = new EmptyChunk(); /** * Create chunk cache container * @param w - world @@ -75,7 +230,7 @@ public class MapChunkCache { Log.info("Chunk snapshot support disabled"); } if(gethandle != null) { /* We can use caching */ - snaparray = new CraftChunkSnapshot[x_dim * (z_max-z_min+1)]; + snaparray = new ChunkSnapshot[x_dim * (z_max-z_min+1)]; } if(snaparray != null) { // Load the required chunks. @@ -110,6 +265,10 @@ public class MapChunkCache { w.unloadChunk(chunk.x, chunk.z, false, false); } } + for(int i = 0; i < snaparray.length; i++) { + if(snaparray[i] == null) + snaparray[i] = EMPTY; + } } else { /* Else, load and keep them loaded for now */ // Load the required chunks. @@ -154,11 +313,8 @@ public class MapChunkCache { */ public int getBlockTypeID(int x, int y, int z) { if(snaparray != null) { - CraftChunkSnapshot ss = snaparray[((x>>4) - x_min) + ((z>>4) - z_min) * x_dim]; - if(ss == null) - return 0; - else - return ss.getBlockTypeId(x & 0xF, y, z & 0xF); + ChunkSnapshot ss = snaparray[((x>>4) - x_min) + ((z>>4) - z_min) * x_dim]; + return ss.getBlockTypeId(x & 0xF, y, z & 0xF); } else { return w.getBlockTypeIdAt(x, y, z); @@ -169,11 +325,8 @@ public class MapChunkCache { */ public byte getBlockData(int x, int y, int z) { if(snaparray != null) { - CraftChunkSnapshot ss = snaparray[((x>>4) - x_min) + ((z>>4) - z_min) * x_dim]; - if(ss == null) - return 0; - else - return (byte)ss.getBlockData(x & 0xF, y, z & 0xF); + ChunkSnapshot ss = snaparray[((x>>4) - x_min) + ((z>>4) - z_min) * x_dim]; + return (byte)ss.getBlockData(x & 0xF, y, z & 0xF); } else { return w.getBlockAt(x, y, z).getData(); @@ -184,12 +337,8 @@ public class MapChunkCache { */ public int getHighestBlockYAt(int x, int z) { if(snaparray != null) { - CraftChunkSnapshot ss = snaparray[((x>>4) - x_min) + ((z>>4) - z_min) * x_dim]; - if(ss == null) { - return 1; - } - else - return ss.getHighestBlockYAt(x & 0xF, z & 0xF); + ChunkSnapshot ss = snaparray[((x>>4) - x_min) + ((z>>4) - z_min) * x_dim]; + return ss.getHighestBlockYAt(x & 0xF, z & 0xF); } else { return w.getHighestBlockYAt(x, z); @@ -199,15 +348,31 @@ public class MapChunkCache { */ public int getBlockSkyLight(int x, int y, int z) { if(snaparray != null) { - CraftChunkSnapshot ss = snaparray[((x>>4) - x_min) + ((z>>4) - z_min) * x_dim]; - if(ss == null) { - return 15; - } - else - return ss.getBlockSkyLight(x & 0xF, y, z & 0xF); + ChunkSnapshot ss = snaparray[((x>>4) - x_min) + ((z>>4) - z_min) * x_dim]; + return ss.getBlockSkyLight(x & 0xF, y, z & 0xF); } else { return 15; } } + /* Get emitted light level + */ + public int getBlockEmittedLight(int x, int y, int z) { + if(snaparray != null) { + ChunkSnapshot ss = snaparray[((x>>4) - x_min) + ((z>>4) - z_min) * x_dim]; + return ss.getBlockEmittedLight(x & 0xF, y, z & 0xF); + } + else { + return 0; + } + } + /** + * Get cache iterator + */ + public MapIterator getIterator(int x, int y, int z) { + if(snaparray != null) + return new SnapshotMapIterator(x, y, z); + else + return new MapIterator(x, y, z); + } } diff --git a/src/main/java/org/dynmap/MapManager.java b/src/main/java/org/dynmap/MapManager.java index 30b56c66..9b60370c 100644 --- a/src/main/java/org/dynmap/MapManager.java +++ b/src/main/java/org/dynmap/MapManager.java @@ -48,7 +48,7 @@ public class MapManager { private class FullWorldRenderState implements Runnable { DynmapWorld world; /* Which world are we rendering */ - Location loc; /* Start location */ + Location loc; int map_index = -1; /* Which map are we on */ MapType map; HashSet found = null; @@ -100,6 +100,16 @@ public class MapManager { renderQueue.add(mt); } } + if(world.seedloc != null) { + for(Location seed : world.seedloc) { + for (MapTile mt : map.getTiles(seed)) { + if (!found.contains(mt)) { + found.add(mt); + renderQueue.add(mt); + } + } + } + } } tile = renderQueue.pollFirst(); } @@ -215,6 +225,15 @@ public class MapManager { } Log.info("Loaded " + dynmapWorld.maps.size() + " maps of world '" + worldName + "'."); + List loclist = worldConfiguration.getNodes("fullrenderlocations"); + dynmapWorld.seedloc = new ArrayList(); + if(loclist != null) { + for(ConfigurationNode loc : loclist) { + Location lx = new Location(w, loc.getDouble("x", 0), loc.getDouble("y", 64), loc.getDouble("z", 0)); + dynmapWorld.seedloc.add(lx); + } + } + // TODO: Make this less... weird... // Insert the world on the same spot as in the configuration. HashMap indexLookup = new HashMap(); @@ -237,7 +256,6 @@ public class MapManager { } worlds.add(insertIndex, dynmapWorld); } - worldsLookup.put(w.getName(), dynmapWorld); plug_in.events.trigger("worldactivated", dynmapWorld); } diff --git a/src/main/java/org/dynmap/MapTile.java b/src/main/java/org/dynmap/MapTile.java index 8ab828eb..e23ef1cd 100644 --- a/src/main/java/org/dynmap/MapTile.java +++ b/src/main/java/org/dynmap/MapTile.java @@ -16,6 +16,8 @@ public abstract class MapTile { public abstract String getFilename(); + public abstract String getDayFilename(); + public MapTile(World world, MapType map) { this.world = world; this.map = map; diff --git a/src/main/java/org/dynmap/flat/FlatMap.java b/src/main/java/org/dynmap/flat/FlatMap.java index 0bc5877f..a87b0105 100644 --- a/src/main/java/org/dynmap/flat/FlatMap.java +++ b/src/main/java/org/dynmap/flat/FlatMap.java @@ -31,7 +31,8 @@ public class FlatMap extends MapType { private String prefix; private ColorScheme colorScheme; private int maximumHeight = 127; - + private int ambientlight = 15;; + private int shadowscale[] = null; public FlatMap(ConfigurationNode configuration) { this.configuration = configuration; prefix = (String) configuration.get("prefix"); @@ -42,6 +43,25 @@ public class FlatMap extends MapType { if (maximumHeight > 127) maximumHeight = 127; } + o = configuration.get("shadowstrength"); + if(o != null) { + double shadowweight = Double.parseDouble(String.valueOf(o)); + if(shadowweight > 0.0) { + shadowscale = new int[16]; + shadowscale[15] = 256; + /* Normal brightness weight in MC is a 20% relative dropoff per step */ + for(int i = 14; i >= 0; i--) { + double v = shadowscale[i+1] * (1.0 - (0.2 * shadowweight)); + shadowscale[i] = (int)v; + if(shadowscale[i] > 256) shadowscale[i] = 256; + if(shadowscale[i] < 0) shadowscale[i] = 0; + } + } + } + o = configuration.get("ambientlight"); + if(o != null) { + ambientlight = Integer.parseInt(String.valueOf(o)); + } } @Override @@ -88,46 +108,43 @@ public class FlatMap extends MapType { boolean rendered = false; BufferedImage im = KzedMap.allocateBufferedImage(t.size, t.size); - WritableRaster raster = im.getRaster(); - + Color rslt = new Color(); int[] pixel = new int[4]; - for (int x = 0; x < t.size; x++) - for (int y = 0; y < t.size; y++) { - int mx = x + t.x * t.size; - int mz = y + t.y * t.size; - int my; + MapChunkCache.MapIterator mapiter = cache.getIterator(t.x * t.size, 127, t.y * t.size); + for (int x = 0; x < t.size; x++) { + mapiter.initialize(t.x * t.size + x, 127, t.y * t.size); + for (int y = 0; y < t.size; y++, mapiter.incrementZ()) { int blockType; if(isnether) { - /* Scan until we hit air */ - my = 127; - while((blockType = cache.getBlockTypeID(mx, my, mz)) != 0) { - my--; - if(my < 0) { /* Solid - use top */ - my = 127; - blockType = cache.getBlockTypeID(mx, my, mz); + while((blockType = mapiter.getBlockTypeID()) != 0) { + mapiter.decrementY(); + if(mapiter.y < 0) { /* Solid - use top */ + mapiter.setY(127); + blockType = mapiter.getBlockTypeID(); break; } } if(blockType == 0) { /* Hit air - now find non-air */ - while((blockType = cache.getBlockTypeID(mx, my, mz)) == 0) { - my--; - if(my < 0) { - my = 0; + while((blockType = mapiter.getBlockTypeID()) == 0) { + mapiter.decrementY(); + if(mapiter.y < 0) { + mapiter.setY(0); break; } } } } else { - my = cache.getHighestBlockYAt(mx, mz) - 1; + int my = mapiter.getHighestBlockYAt() - 1; if(my > maximumHeight) my = maximumHeight; - blockType = cache.getBlockTypeID(mx, my, mz); + mapiter.setY(my); + blockType = mapiter.getBlockTypeID(); } - byte data = 0; + int data = 0; Color[] colors = colorScheme.colors[blockType]; if(colorScheme.datacolors[blockType] != null) { - data = cache.getBlockData(mx, my, mz); + data = mapiter.getBlockData(); colors = colorScheme.datacolors[blockType][data]; } if (colors == null) @@ -136,40 +153,52 @@ public class FlatMap extends MapType { if (c == null) continue; - boolean below = my < 64; - - // Make height range from 0 - 1 (1 - 0 for below and 0 - 1 above) - float height = (below ? 64 - my : my - 64) / 64.0f; - - // Defines the 'step' in coloring. - float step = 10 / 128.0f; - - // The step applied to height. - float scale = ((int)(height/step))*step; - - // Make the smaller values change the color (slightly) more than the higher values. - scale = (float)Math.pow(scale, 1.1f); - - // Don't let the color go fully white or fully black. - scale *= 0.8f; - pixel[0] = c.getRed(); pixel[1] = c.getGreen(); pixel[2] = c.getBlue(); - if (below) { - pixel[0] -= pixel[0] * scale; - pixel[1] -= pixel[1] * scale; - pixel[2] -= pixel[2] * scale; - } else { - pixel[0] += (255-pixel[0]) * scale; - pixel[1] += (255-pixel[1]) * scale; - pixel[2] += (255-pixel[2]) * scale; + /* If ambient light less than 15, do scaling */ + if((shadowscale != null) && (ambientlight < 15)) { + if(mapiter.y < 127) + mapiter.incrementY(); + int light = Math.max(ambientlight, mapiter.getBlockEmittedLight()); + pixel[0] = (pixel[0] * shadowscale[light]) >> 8; + pixel[1] = (pixel[1] * shadowscale[light]) >> 8; + pixel[2] = (pixel[2] * shadowscale[light]) >> 8; } + else { /* Only do height keying if we're not messing with ambient light */ + boolean below = mapiter.y < 64; - raster.setPixel(t.size-y-1, x, pixel); + // Make height range from 0 - 1 (1 - 0 for below and 0 - 1 above) + float height = (below ? 64 - mapiter.y : mapiter.y - 64) / 64.0f; + + // Defines the 'step' in coloring. + float step = 10 / 128.0f; + + // The step applied to height. + float scale = ((int)(height/step))*step; + + // Make the smaller values change the color (slightly) more than the higher values. + scale = (float)Math.pow(scale, 1.1f); + + // Don't let the color go fully white or fully black. + scale *= 0.8f; + + if (below) { + pixel[0] -= pixel[0] * scale; + pixel[1] -= pixel[1] * scale; + pixel[2] -= pixel[2] * scale; + } else { + pixel[0] += (255-pixel[0]) * scale; + pixel[1] += (255-pixel[1]) * scale; + pixel[2] += (255-pixel[2]) * scale; + } + } + rslt.setRGBA(pixel[0], pixel[1], pixel[2], 255); + im.setRGB(t.size-y-1, x, rslt.getARGB()); rendered = true; } + } /* Hand encoding and writing file off to MapManager */ final File fname = outputFile; final MapTile mtile = tile; @@ -211,6 +240,10 @@ public class FlatMap extends MapType { public String getFilename() { return map.prefix + "_" + size + "_" + -(y+1) + "_" + x + ".png"; } + @Override + public String getDayFilename() { + return map.prefix + "_day_" + size + "_" + -(y+1) + "_" + x + ".png"; + } } @Override diff --git a/src/main/java/org/dynmap/kzedmap/CaveTileRenderer.java b/src/main/java/org/dynmap/kzedmap/CaveTileRenderer.java index 59041e16..42d39799 100644 --- a/src/main/java/org/dynmap/kzedmap/CaveTileRenderer.java +++ b/src/main/java/org/dynmap/kzedmap/CaveTileRenderer.java @@ -12,15 +12,15 @@ public class CaveTileRenderer extends DefaultTileRenderer { } @Override - protected void scan(World world, int x, int y, int z, int seq, boolean isnether, final Color result, - MapChunkCache cache) { + protected void scan(World world, int seq, boolean isnether, final Color result, final Color result_day, + MapChunkCache.MapIterator mapiter) { boolean air = true; result.setTransparent(); for (;;) { - if (y < 0) + if (mapiter.y < 0) return; - int id = cache.getBlockTypeID(x, y, z); + int id = mapiter.getBlockTypeID(); if(isnether) { /* Make ceiling into air in nether */ if(id != 0) id = 0; @@ -30,16 +30,14 @@ public class CaveTileRenderer extends DefaultTileRenderer { switch (seq) { case 0: - x--; + mapiter.decrementX(); break; case 1: - y--; + case 3: + mapiter.decrementY(); break; case 2: - z++; - break; - case 3: - y--; + mapiter.incrementZ(); break; } @@ -65,12 +63,12 @@ public class CaveTileRenderer extends DefaultTileRenderer { int cr, cg, cb; int mult = 256; - if (y < 64) { + if (mapiter.y < 64) { cr = 0; - cg = 64 + y * 3; - cb = 255 - y * 4; + cg = 64 + mapiter.y * 3; + cb = 255 - mapiter.y * 4; } else { - cr = (y - 64) * 4; + cr = (mapiter.y - 64) * 4; cg = 255; cb = 0; } diff --git a/src/main/java/org/dynmap/kzedmap/DefaultTileRenderer.java b/src/main/java/org/dynmap/kzedmap/DefaultTileRenderer.java index 1d3c00a5..a8121ea9 100644 --- a/src/main/java/org/dynmap/kzedmap/DefaultTileRenderer.java +++ b/src/main/java/org/dynmap/kzedmap/DefaultTileRenderer.java @@ -19,6 +19,7 @@ import org.dynmap.Client; import org.dynmap.Color; import org.dynmap.ColorScheme; import org.dynmap.ConfigurationNode; +import org.dynmap.Log; import org.dynmap.MapManager; import org.dynmap.debug.Debug; import org.dynmap.MapChunkCache; @@ -35,6 +36,8 @@ public class DefaultTileRenderer implements MapTileRenderer { protected Color highlightColor = new Color(255, 0, 0); protected int shadowscale[]; /* index=skylight level, value = 256 * scaling value */ + protected int lightscale[]; /* scale skylight level (light = lightscale[skylight] */ + protected boolean night_and_day; /* If true, render both day (prefix+'-day') and night (prefix) tiles */ @Override public String getName() { return name; @@ -54,15 +57,29 @@ public class DefaultTileRenderer implements MapTileRenderer { double shadowweight = Double.parseDouble(String.valueOf(o)); if(shadowweight > 0.0) { shadowscale = new int[16]; - for(int i = 0; i < 16; i++) { - double v = 256.0 * (1.0 - (shadowweight * (15-i) / 15.0)); + shadowscale[15] = 256; + /* Normal brightness weight in MC is a 20% relative dropoff per step */ + for(int i = 14; i >= 0; i--) { + double v = shadowscale[i+1] * (1.0 - (0.2 * shadowweight)); shadowscale[i] = (int)v; if(shadowscale[i] > 256) shadowscale[i] = 256; if(shadowscale[i] < 0) shadowscale[i] = 0; } } } + o = configuration.get("ambientlight"); + if(o != null) { + int v = Integer.parseInt(String.valueOf(o)); + lightscale = new int[16]; + for(int i = 0; i < 16; i++) { + if(i < (15-v)) + lightscale[i] = 0; + else + lightscale[i] = i - (15-v); + } + } colorScheme = ColorScheme.getScheme((String)configuration.get("colorscheme")); + night_and_day = configuration.getBoolean("night-and-day", false); } public boolean render(MapChunkCache cache, KzedMapTile tile, File outputFile) { @@ -70,9 +87,14 @@ public class DefaultTileRenderer implements MapTileRenderer { boolean isnether = (world.getEnvironment() == Environment.NETHER); BufferedImage im = KzedMap.allocateBufferedImage(KzedMap.tileWidth, KzedMap.tileHeight); BufferedImage zim = KzedMap.allocateBufferedImage(KzedMap.tileWidth/2, KzedMap.tileHeight/2); - WritableRaster r = im.getRaster(); - WritableRaster zr = zim.getRaster(); boolean isempty = true; + + BufferedImage im_day = null; + BufferedImage zim_day = null; + if(night_and_day) { + im_day = KzedMap.allocateBufferedImage(KzedMap.tileWidth, KzedMap.tileHeight); + zim_day = KzedMap.allocateBufferedImage(KzedMap.tileWidth/2, KzedMap.tileHeight/2); + } int ix = KzedMap.anchorx + tile.px / 2 + tile.py / 2 - ((127-maximumHeight)/2); int iy = maximumHeight; @@ -86,69 +108,117 @@ public class DefaultTileRenderer implements MapTileRenderer { int x, y; + MapChunkCache.MapIterator mapiter = cache.getIterator(ix, iy, iz); + Color c1 = new Color(); Color c2 = new Color(); - int[] rgb = new int[3*KzedMap.tileWidth]; - int[] zrgb = new int[3*KzedMap.tileWidth/2]; + int[] argb = new int[KzedMap.tileWidth]; + int[] zargb = new int[4*KzedMap.tileWidth/2]; + Color c1_day = null; + Color c2_day = null; + int[] argb_day = null; + int[] zargb_day = null; + if(night_and_day) { + c1_day = new Color(); + c2_day = new Color(); + argb_day = new int[KzedMap.tileWidth]; + zargb_day = new int[4*KzedMap.tileWidth/2]; + } /* draw the map */ for (y = 0; y < KzedMap.tileHeight;) { jx = ix; jz = iz; for (x = KzedMap.tileWidth - 1; x >= 0; x -= 2) { - scan(world, jx, iy, jz, 0, isnether, c1, cache); - scan(world, jx, iy, jz, 2, isnether, c2, cache); - - rgb[3*x] = c1.getRed(); - rgb[3*x+1] = c1.getGreen(); - rgb[3*x+2] = c1.getBlue(); - rgb[3*x-3] = c2.getRed(); - rgb[3*x-2] = c2.getGreen(); - rgb[3*x-1] = c2.getBlue(); + mapiter.initialize(jx, iy, jz); + scan(world, 0, isnether, c1, c1_day, mapiter); + mapiter.initialize(jx, iy, jz); + scan(world, 2, isnether, c2, c2_day, mapiter); + argb[x] = c1.getARGB(); + argb[x-1] = c2.getARGB(); + + if(night_and_day) { + argb_day[x] = c1_day.getARGB(); + argb_day[x-1] = c2_day.getARGB(); + } + isempty = isempty && c1.isTransparent() && c2.isTransparent(); jx++; jz++; } - r.setPixels(0, y, KzedMap.tileWidth, 1, rgb); + im.setRGB(0, y, KzedMap.tileWidth, 1, argb, 0, KzedMap.tileWidth); + if(night_and_day) + im_day.setRGB(0, y, KzedMap.tileWidth, 1, argb_day, 0, KzedMap.tileWidth); /* Sum up zoomed pixels - bilinar filter */ for(x = 0; x < KzedMap.tileWidth / 2; x++) { - zrgb[3*x] = rgb[6*x] + rgb[6*x+3]; - zrgb[3*x+1] = rgb[6*x+1] + rgb[6*x+4]; - zrgb[3*x+2] = rgb[6*x+2] + rgb[6*x+5]; + c1.setARGB(argb[2*x]); + c2.setARGB(argb[2*x+1]); + for(int i = 0; i < 4; i++) + zargb[4*x+i] = c1.getComponent(i) + c2.getComponent(i); } - + if(night_and_day) { + for(x = 0; x < KzedMap.tileWidth / 2; x++) { + c1.setARGB(argb_day[2*x]); + c2.setARGB(argb_day[2*x+1]); + for(int i = 0; i < 4; i++) + zargb_day[4*x+i] = c1.getComponent(i) + c2.getComponent(i); + } + } + y++; jx = ix; jz = iz - 1; for (x = KzedMap.tileWidth - 1; x >= 0; x -= 2) { - scan(world, jx, iy, jz, 2, isnether, c1, cache); + mapiter.initialize(jx, iy, jz); + scan(world, 2, isnether, c1, c1_day, mapiter); jx++; jz++; - scan(world, jx, iy, jz, 0, isnether, c2, cache); + mapiter.initialize(jx, iy, jz); + scan(world, 0, isnether, c2, c2_day, mapiter); - rgb[3*x] = c1.getRed(); - rgb[3*x+1] = c1.getGreen(); - rgb[3*x+2] = c1.getBlue(); - rgb[3*x-3] = c2.getRed(); - rgb[3*x-2] = c2.getGreen(); - rgb[3*x-1] = c2.getBlue(); + argb[x] = c1.getARGB(); + argb[x-1] = c2.getARGB(); + + if(night_and_day) { + argb_day[x] = c1_day.getARGB(); + argb_day[x-1] = c2_day.getARGB(); + } isempty = isempty && c1.isTransparent() && c2.isTransparent(); } - r.setPixels(0, y, KzedMap.tileWidth, 1, rgb); + im.setRGB(0, y, KzedMap.tileWidth, 1, argb, 0, KzedMap.tileWidth); + if(night_and_day) + im_day.setRGB(0, y, KzedMap.tileWidth, 1, argb_day, 0, KzedMap.tileWidth); + /* Finish summing values for zoomed pixels */ + /* Sum up zoomed pixels - bilinar filter */ for(x = 0; x < KzedMap.tileWidth / 2; x++) { - zrgb[3*x] = (zrgb[3*x] + rgb[6*x] + rgb[6*x+3]) >> 2; - zrgb[3*x+1] = (zrgb[3*x+1] + rgb[6*x+1] + rgb[6*x+4]) >> 2; - zrgb[3*x+2] = (zrgb[3*x+2] + rgb[6*x+2] + rgb[6*x+5]) >> 2; + c1.setARGB(argb[2*x]); + c2.setARGB(argb[2*x+1]); + for(int i = 0; i < 4; i++) + zargb[4*x+i] = (zargb[4*x+i] + c1.getComponent(i) + c2.getComponent(i)) >> 2; + c1.setRGBA(zargb[4*x+1], zargb[4*x+2], zargb[4*x+3], zargb[4*x]); + zargb[x] = c1.getARGB(); } - zr.setPixels(0, y/2, KzedMap.tileWidth/2, 1, zrgb); - + if(night_and_day) { + for(x = 0; x < KzedMap.tileWidth / 2; x++) { + c1.setARGB(argb_day[2*x]); + c2.setARGB(argb_day[2*x+1]); + for(int i = 0; i < 4; i++) + zargb_day[4*x+i] = (zargb_day[4*x+i] + c1.getComponent(i) + c2.getComponent(i)) >> 2; + c1.setRGBA(zargb_day[4*x+1], zargb_day[4*x+2], zargb_day[4*x+3], zargb_day[4*x]); + zargb_day[x] = c1.getARGB(); + } + } + zim.setRGB(0, y/2, KzedMap.tileWidth/2, 1, zargb, 0, KzedMap.tileWidth/2); + if(night_and_day) + zim_day.setRGB(0, y/2, KzedMap.tileWidth/2, 1, zargb_day, 0, KzedMap.tileWidth/2); + y++; ix++; @@ -160,13 +230,15 @@ public class DefaultTileRenderer implements MapTileRenderer { final KzedMapTile mtile = tile; final BufferedImage img = im; final BufferedImage zimg = zim; + final BufferedImage img_day = im_day; + final BufferedImage zimg_day = zim_day; final KzedZoomedMapTile zmtile = new KzedZoomedMapTile(mtile.getWorld(), (KzedMap) mtile.getMap(), mtile); final File zoomFile = MapManager.mapman.getTileFile(zmtile); MapManager.mapman.enqueueImageWrite(new Runnable() { public void run() { - doFileWrites(fname, mtile, img, zmtile, zoomFile, zimg); + doFileWrites(fname, mtile, img, img_day, zmtile, zoomFile, zimg, zimg_day); } }); @@ -174,8 +246,9 @@ public class DefaultTileRenderer implements MapTileRenderer { } private void doFileWrites(final File fname, final KzedMapTile mtile, - final BufferedImage img, final KzedZoomedMapTile zmtile, final File zoomFile, - final BufferedImage zimg) { + final BufferedImage img, final BufferedImage img_day, + final KzedZoomedMapTile zmtile, final File zoomFile, + final BufferedImage zimg, final BufferedImage zimg_day) { Debug.debug("saving image " + fname.getPath()); try { ImageIO.write(img, "png", fname); @@ -184,8 +257,19 @@ public class DefaultTileRenderer implements MapTileRenderer { } catch (java.lang.NullPointerException e) { Debug.error("Failed to save image (NullPointerException): " + fname.getPath(), e); } - img.flush(); - + KzedMap.freeBufferedImage(img); + if(img_day != null) { + File dfname = new File(fname.getParent(), mtile.getDayFilename()); + Debug.debug("saving image " + dfname.getPath()); + try { + ImageIO.write(img_day, "png", dfname); + } catch (IOException e) { + Debug.error("Failed to save image: " + dfname.getPath(), e); + } catch (java.lang.NullPointerException e) { + Debug.error("Failed to save image (NullPointerException): " + dfname.getPath(), e); + } + KzedMap.freeBufferedImage(img_day); + } mtile.file = fname; // Since we've already got the new tile, and we're on an async thread, just // make the zoomed tile here @@ -225,8 +309,8 @@ public class DefaultTileRenderer implements MapTileRenderer { } /* blit scaled rendered tile onto zoom-out tile */ - WritableRaster zim = zIm.getRaster(); - zim.setRect(ox, oy, zimg.getRaster()); + int[] pix = zimg.getRGB(0, 0, KzedMap.tileWidth/2, KzedMap.tileHeight/2, null, 0, KzedMap.tileWidth/2); + zIm.setRGB(ox, oy, KzedMap.tileWidth/2, KzedMap.tileHeight/2, pix, 0, KzedMap.tileWidth/2); KzedMap.freeBufferedImage(zimg); /* save zoom-out tile */ @@ -243,23 +327,71 @@ public class DefaultTileRenderer implements MapTileRenderer { KzedMap.freeBufferedImage(zIm); else zIm.flush(); + + if(zimg_day != null) { + File zoomFile_day = new File(zoomFile.getParent(), zmtile.getDayFilename()); + + zIm = null; + try { + zIm = ImageIO.read(zoomFile_day); + } catch (IOException e) { + } catch (IndexOutOfBoundsException e) { + } + + zIm_allocated = false; + if (zIm == null) { + /* create new one */ + zIm = KzedMap.allocateBufferedImage(KzedMap.tileWidth, KzedMap.tileHeight); + zIm_allocated = true; + Debug.debug("New zoom-out tile created " + zmtile.getFilename()); + } else { + Debug.debug("Loaded zoom-out tile from " + zmtile.getFilename()); + } + + /* blit scaled rendered tile onto zoom-out tile */ + pix = zimg_day.getRGB(0, 0, KzedMap.tileWidth/2, KzedMap.tileHeight/2, null, 0, KzedMap.tileWidth/2); + zIm.setRGB(ox, oy, KzedMap.tileWidth/2, KzedMap.tileHeight/2, pix, 0, KzedMap.tileWidth/2); + KzedMap.freeBufferedImage(zimg_day); + + /* save zoom-out tile */ + + try { + ImageIO.write(zIm, "png", zoomFile_day); + Debug.debug("Saved zoom-out tile at " + zoomFile_day.getName()); + } catch (IOException e) { + Debug.error("Failed to save zoom-out tile: " + zoomFile_day.getName(), e); + } catch (java.lang.NullPointerException e) { + Debug.error("Failed to save zoom-out tile (NullPointerException): " + zoomFile_day.getName(), e); + } + if(zIm_allocated) + KzedMap.freeBufferedImage(zIm); + else + zIm.flush(); + } /* Push updates for both files.*/ MapManager.mapman.pushUpdate(mtile.getWorld(), new Client.Tile(mtile.getFilename())); MapManager.mapman.pushUpdate(zmtile.getWorld(), new Client.Tile(zmtile.getFilename())); + if(img_day != null) { + MapManager.mapman.pushUpdate(mtile.getWorld(), + new Client.Tile(mtile.getDayFilename())); + MapManager.mapman.pushUpdate(zmtile.getWorld(), + new Client.Tile(zmtile.getDayFilename())); + } } - protected void scan(World world, int x, int y, int z, int seq, boolean isnether, final Color result, - MapChunkCache cache) { + protected void scan(World world, int seq, boolean isnether, final Color result, final Color result_day, + MapChunkCache.MapIterator mapiter) { int lightlevel = 15; + int lightlevel_day = 15; result.setTransparent(); for (;;) { - if (y < 0) { + if (mapiter.y < 0) { return; } - int id = cache.getBlockTypeID(x, y, z); - byte data = 0; + int id = mapiter.getBlockTypeID(); + int data = 0; if(isnether) { /* Make bedrock ceiling into air in nether */ if(id != 0) { /* Remember first color we see, in case we wind up solid */ @@ -273,22 +405,40 @@ public class DefaultTileRenderer implements MapTileRenderer { } if(id != 0) { /* No update needed for air */ if(colorScheme.datacolors[id] != null) { /* If data colored */ - data = cache.getBlockData(x, y, z); + data = mapiter.getBlockData(); } - if((shadowscale != null) && (y < 127)) { + if((shadowscale != null) && (mapiter.y < 127)) { /* Find light level of previous chunk */ switch(seq) { case 0: - lightlevel = cache.getBlockSkyLight(x, y+1, z); + case 2: + mapiter.incrementY(); break; case 1: - lightlevel = cache.getBlockSkyLight(x+1, y, z); - break; - case 2: - lightlevel = cache.getBlockSkyLight(x, y+1, z); + mapiter.incrementX(); break; case 3: - lightlevel = cache.getBlockSkyLight(x, y, z-1); + mapiter.decrementZ(); + break; + } + lightlevel = lightlevel_day = mapiter.getBlockSkyLight(); + if(lightscale != null) + lightlevel = lightscale[lightlevel]; + if((lightlevel < 15) || (lightlevel_day < 15)) { + int emitted = mapiter.getBlockEmittedLight(); + lightlevel = Math.max(emitted, lightlevel); + lightlevel_day = Math.max(emitted, lightlevel_day); + } + switch(seq) { + case 0: + case 2: + mapiter.decrementY(); + break; + case 1: + mapiter.decrementX(); + break; + case 3: + mapiter.incrementZ(); break; } } @@ -296,16 +446,14 @@ public class DefaultTileRenderer implements MapTileRenderer { switch (seq) { case 0: - x--; + mapiter.decrementX(); break; case 1: - y--; + case 3: + mapiter.decrementY(); break; case 2: - z++; - break; - case 3: - y--; + mapiter.incrementZ(); break; } @@ -331,11 +479,20 @@ public class DefaultTileRenderer implements MapTileRenderer { if(lightlevel < 15) { /* Not full light? */ shadowColor(result, lightlevel); } + if(result_day != null) { + if(lightlevel_day == lightlevel) /* Same light = same result */ + result_day.setColor(result); + else { + result_day.setColor(c); + if(lightlevel_day < 15) + shadowColor(result_day, lightlevel_day); + } + } return; } /* this block is transparent, so recurse */ - scan(world, x, y, z, seq, isnether, result, cache); + scan(world, seq, isnether, result, result_day, mapiter); int cr = c.getRed(); int cg = c.getGreen(); @@ -352,6 +509,23 @@ public class DefaultTileRenderer implements MapTileRenderer { cb *= ca; int na = 255 - ca; result.setRGBA((result.getRed() * na + cr) >> 8, (result.getGreen() * na + cg) >> 8, (result.getBlue() * na + cb) >> 8, 255); + /* Handle day also */ + if(result_day != null) { + cr = c.getRed(); + cg = c.getGreen(); + cb = c.getBlue(); + if(lightlevel_day < 15) { + int scale = shadowscale[lightlevel_day]; + cr = (cr * scale) >> 8; + cg = (cg * scale) >> 8; + cb = (cb * scale) >> 8; + } + cr *= ca; + cg *= ca; + cb *= ca; + result_day.setRGBA((result_day.getRed() * na + cr) >> 8, (result_day.getGreen() * na + cg) >> 8, (result_day.getBlue() * na + cb) >> 8, + 255); + } return; } } @@ -374,6 +548,7 @@ public class DefaultTileRenderer implements MapTileRenderer { s(o, "title", c.getString("title")); s(o, "icon", c.getString("icon")); s(o, "prefix", c.getString("prefix")); + s(o, "nightandday", c.getBoolean("night-and-day", false)); a(worldObject, "maps", o); } } diff --git a/src/main/java/org/dynmap/kzedmap/HighlightTileRenderer.java b/src/main/java/org/dynmap/kzedmap/HighlightTileRenderer.java index 2c379878..8ea72b77 100644 --- a/src/main/java/org/dynmap/kzedmap/HighlightTileRenderer.java +++ b/src/main/java/org/dynmap/kzedmap/HighlightTileRenderer.java @@ -20,15 +20,15 @@ public class HighlightTileRenderer extends DefaultTileRenderer { } @Override - protected void scan(World world, int x, int y, int z, int seq, boolean isnether, final Color result, - MapChunkCache cache) { + protected void scan(World world,int seq, boolean isnether, final Color result, final Color result_day, + MapChunkCache.MapIterator mapiter) { result.setTransparent(); for (;;) { - if (y < 0) { + if (mapiter.y < 0) { break; } - int id = cache.getBlockTypeID(x, y, z); + int id = mapiter.getBlockTypeID(); if(isnether) { /* Make bedrock ceiling into air in nether */ if(id != 0) { /* Remember first color we see, in case we wind up solid */ @@ -40,23 +40,21 @@ public class HighlightTileRenderer extends DefaultTileRenderer { else isnether = false; } - byte data = 0; + int data = 0; if(colorScheme.datacolors[id] != null) { /* If data colored */ - data = cache.getBlockData(x, y, z); + data = mapiter.getBlockData(); } switch (seq) { case 0: - x--; + mapiter.decrementX(); break; case 1: - y--; + case 3: + mapiter.decrementY(); break; case 2: - z++; - break; - case 3: - y--; + mapiter.incrementZ(); break; } diff --git a/src/main/java/org/dynmap/kzedmap/KzedMap.java b/src/main/java/org/dynmap/kzedmap/KzedMap.java index 71e9a346..56e90d00 100644 --- a/src/main/java/org/dynmap/kzedmap/KzedMap.java +++ b/src/main/java/org/dynmap/kzedmap/KzedMap.java @@ -278,7 +278,7 @@ public class KzedMap extends MapType { img.setRGB(0, 0, x, y, zerobuf, 0, 0); } else { - img = new BufferedImage(x, y, BufferedImage.TYPE_INT_RGB); + img = new BufferedImage(x, y, BufferedImage.TYPE_INT_ARGB); } return img; } diff --git a/src/main/java/org/dynmap/kzedmap/KzedMapTile.java b/src/main/java/org/dynmap/kzedmap/KzedMapTile.java index 78453ff2..24349360 100644 --- a/src/main/java/org/dynmap/kzedmap/KzedMapTile.java +++ b/src/main/java/org/dynmap/kzedmap/KzedMapTile.java @@ -26,6 +26,11 @@ public class KzedMapTile extends MapTile { return renderer.getName() + "_" + px + "_" + py + ".png"; } + @Override + public String getDayFilename() { + return renderer.getName() + "_day_" + px + "_" + py + ".png"; + } + @Override public int hashCode() { return getFilename().hashCode() ^ getWorld().hashCode(); diff --git a/src/main/java/org/dynmap/kzedmap/KzedZoomedMapTile.java b/src/main/java/org/dynmap/kzedmap/KzedZoomedMapTile.java index d0362d81..9d7a28ff 100644 --- a/src/main/java/org/dynmap/kzedmap/KzedZoomedMapTile.java +++ b/src/main/java/org/dynmap/kzedmap/KzedZoomedMapTile.java @@ -9,6 +9,11 @@ public class KzedZoomedMapTile extends MapTile { return "z" + originalTile.renderer.getName() + "_" + getTileX() + "_" + getTileY() + ".png"; } + @Override + public String getDayFilename() { + return "z" + originalTile.renderer.getName() + "_day_" + getTileX() + "_" + getTileY() + ".png"; + } + public KzedMapTile originalTile; public KzedZoomedMapTile(World world, KzedMap map, KzedMapTile original) {