dynmap-neoforge/src/main/java/org/dynmap/flat/FlatMap.java

339 lines
14 KiB
Java

package org.dynmap.flat;
import static org.dynmap.JSONUtils.a;
import static org.dynmap.JSONUtils.s;
import java.awt.image.DataBufferInt;
import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
import java.awt.image.ColorModel;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.World.Environment;
import org.dynmap.Client;
import org.dynmap.Color;
import org.dynmap.ColorScheme;
import org.dynmap.ConfigurationNode;
import org.dynmap.DynmapChunk;
import org.dynmap.MapManager;
import org.dynmap.TileHashManager;
import org.dynmap.MapTile;
import org.dynmap.MapType;
import org.dynmap.debug.Debug;
import org.dynmap.kzedmap.KzedMap;
import org.dynmap.kzedmap.KzedMap.KzedBufferedImage;
import org.dynmap.MapChunkCache;
import org.dynmap.utils.FileLockManager;
import org.json.simple.JSONObject;
public class FlatMap extends MapType {
private ConfigurationNode configuration;
private String prefix;
private ColorScheme colorScheme;
private int maximumHeight = 127;
private int ambientlight = 15;;
private int shadowscale[] = null;
private boolean night_and_day; /* If true, render both day (prefix+'-day') and night (prefix) tiles */
public FlatMap(ConfigurationNode configuration) {
this.configuration = configuration;
prefix = (String) configuration.get("prefix");
colorScheme = ColorScheme.getScheme((String) configuration.get("colorscheme"));
Object o = configuration.get("maximumheight");
if (o != null) {
maximumHeight = Integer.parseInt(String.valueOf(o));
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));
}
night_and_day = configuration.getBoolean("night-and-day", false);
}
@Override
public MapTile[] getTiles(Location l) {
return new MapTile[] { new FlatMapTile(l.getWorld(), this, (int) Math.floor(l.getBlockX() / 128.0), (int) Math.floor(l.getBlockZ() / 128.0), 128) };
}
@Override
public MapTile[] getAdjecentTiles(MapTile tile) {
FlatMapTile t = (FlatMapTile) tile;
World w = t.getWorld();
int x = t.x;
int y = t.y;
int s = t.size;
return new MapTile[] {
new FlatMapTile(w, this, x, y - 1, s),
new FlatMapTile(w, this, x + 1, y, s),
new FlatMapTile(w, this, x, y + 1, s),
new FlatMapTile(w, this, x - 1, y, s) };
}
@Override
public DynmapChunk[] getRequiredChunks(MapTile tile) {
FlatMapTile t = (FlatMapTile) tile;
int chunksPerTile = t.size / 16;
int sx = t.x * chunksPerTile;
int sz = t.y * chunksPerTile;
DynmapChunk[] result = new DynmapChunk[chunksPerTile * chunksPerTile];
int index = 0;
for (int x = 0; x < chunksPerTile; x++)
for (int z = 0; z < chunksPerTile; z++) {
result[index] = new DynmapChunk(sx + x, sz + z);
index++;
}
return result;
}
@Override
public boolean render(MapChunkCache cache, MapTile tile, File outputFile) {
FlatMapTile t = (FlatMapTile) tile;
World w = t.getWorld();
boolean isnether = (w.getEnvironment() == Environment.NETHER) && (maximumHeight == 127);
boolean rendered = false;
Color rslt = new Color();
int[] pixel = new int[3];
int[] pixel_day = new int[3];
KzedBufferedImage im = KzedMap.allocateBufferedImage(t.size, t.size);
int[] argb_buf = im.argb_buf;
KzedBufferedImage im_day = null;
int[] argb_buf_day = null;
if(night_and_day) {
im_day = KzedMap.allocateBufferedImage(t.size, t.size);
argb_buf_day = im_day.argb_buf;
}
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;
mapiter.setY(127);
if(isnether) {
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 = mapiter.getBlockTypeID()) == 0) {
mapiter.decrementY();
if(mapiter.y < 0) {
mapiter.setY(0);
break;
}
}
}
}
else {
int my = mapiter.getHighestBlockYAt();
if(my > maximumHeight) my = maximumHeight;
mapiter.setY(my);
blockType = mapiter.getBlockTypeID();
if(blockType == 0) { /* If air, go down one - fixes ice */
my--;
if(my < 0)
continue;
mapiter.setY(my);
blockType = mapiter.getBlockTypeID();
}
}
int data = 0;
Color[] colors = colorScheme.colors[blockType];
if(colorScheme.datacolors[blockType] != null) {
data = mapiter.getBlockData();
colors = colorScheme.datacolors[blockType][data];
}
if (colors == null)
continue;
Color c = colors[0];
if (c == null)
continue;
pixel[0] = c.getRed();
pixel[1] = c.getGreen();
pixel[2] = c.getBlue();
/* If ambient light less than 15, do scaling */
if((shadowscale != null) && (ambientlight < 15)) {
if(mapiter.y < 127)
mapiter.incrementY();
if(night_and_day) { /* Use unscaled color for day (no shadows from above) */
pixel_day[0] = pixel[0];
pixel_day[1] = pixel[1];
pixel_day[2] = pixel[2];
}
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;
// 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;
}
if(night_and_day) {
pixel_day[0] = pixel[0];
pixel_day[1] = pixel[1];
pixel_day[2] = pixel[2];
}
}
rslt.setRGBA(pixel[0], pixel[1], pixel[2], 255);
argb_buf[(t.size-y-1) + (x*t.size)] = rslt.getARGB();
if(night_and_day) {
rslt.setRGBA(pixel_day[0], pixel_day[1], pixel_day[2], 255);
argb_buf_day[(t.size-y-1) + (x*t.size)] = rslt.getARGB();
}
rendered = true;
}
}
/* Test to see if we're unchanged from older tile */
FileLockManager.getWriteLock(outputFile);
TileHashManager hashman = MapManager.mapman.hashman;
long crc = hashman.calculateTileHash(argb_buf);
boolean tile_update = false;
if((!outputFile.exists()) || (crc != hashman.getImageHashCode(tile.getKey(), null, t.x, t.y))) {
/* Wrap buffer as buffered image */
Debug.debug("saving image " + outputFile.getPath());
try {
ImageIO.write(im.buf_img, "png", outputFile);
} catch (IOException e) {
Debug.error("Failed to save image: " + outputFile.getPath(), e);
} catch (java.lang.NullPointerException e) {
Debug.error("Failed to save image (NullPointerException): " + outputFile.getPath(), e);
}
MapManager.mapman.pushUpdate(tile.getWorld(), new Client.Tile(tile.getFilename()));
hashman.updateHashCode(tile.getKey(), null, t.x, t.y, crc);
tile_update = true;
}
else {
Debug.debug("skipping image " + outputFile.getPath() + " - hash match");
}
KzedMap.freeBufferedImage(im);
FileLockManager.releaseWriteLock(outputFile);
MapManager.mapman.updateStatistics(tile, null, true, tile_update, !rendered);
/* If day too, handle it */
if(night_and_day) {
File dayfile = new File(outputFile.getParent(), tile.getDayFilename());
FileLockManager.getWriteLock(dayfile);
crc = hashman.calculateTileHash(argb_buf_day);
if((!dayfile.exists()) || (crc != hashman.getImageHashCode(tile.getKey(), "day", t.x, t.y))) {
Debug.debug("saving image " + dayfile.getPath());
try {
ImageIO.write(im_day.buf_img, "png", dayfile);
} catch (IOException e) {
Debug.error("Failed to save image: " + dayfile.getPath(), e);
} catch (java.lang.NullPointerException e) {
Debug.error("Failed to save image (NullPointerException): " + dayfile.getPath(), e);
}
MapManager.mapman.pushUpdate(tile.getWorld(), new Client.Tile(tile.getDayFilename()));
hashman.updateHashCode(tile.getKey(), "day", t.x, t.y, crc);
tile_update = true;
}
else {
Debug.debug("skipping image " + dayfile.getPath() + " - hash match");
tile_update = false;
}
KzedMap.freeBufferedImage(im_day);
FileLockManager.releaseWriteLock(dayfile);
MapManager.mapman.updateStatistics(tile, "day", true, tile_update, !rendered);
}
return rendered;
}
public String getName() {
return prefix;
}
public static class FlatMapTile extends MapTile {
FlatMap map;
public int x;
public int y;
public int size;
public FlatMapTile(World world, FlatMap map, int x, int y, int size) {
super(world, map);
this.map = map;
this.x = x;
this.y = y;
this.size = size;
}
@Override
public String getFilename() {
return map.prefix + "_" + size + "_" + -(y+1) + "_" + x + ".png";
}
@Override
public String getDayFilename() {
return map.prefix + "_day_" + size + "_" + -(y+1) + "_" + x + ".png";
}
public String toString() {
return getWorld().getName() + ":" + getFilename();
}
}
@Override
public void buildClientConfiguration(JSONObject worldObject) {
ConfigurationNode c = configuration;
JSONObject o = new JSONObject();
s(o, "type", "FlatMapType");
s(o, "name", c.getString("name"));
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);
}
}