mirror of
https://github.com/webbukkit/dynmap.git
synced 2024-12-25 10:07:37 +01:00
Server-side multiworld support with several improvements overal.
This commit is contained in:
parent
2fd91ef94b
commit
5b0171c459
108
src/main/java/org/dynmap/AsynchronousQueue.java
Normal file
108
src/main/java/org/dynmap/AsynchronousQueue.java
Normal file
@ -0,0 +1,108 @@
|
||||
package org.dynmap;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class AsynchronousQueue<T> {
|
||||
protected static final Logger log = Logger.getLogger("Minecraft");
|
||||
|
||||
private Object lock = new Object();
|
||||
private Thread thread;
|
||||
private LinkedList<T> queue = new LinkedList<T>();
|
||||
private Set<T> set = new HashSet<T>();
|
||||
private Handler<T> handler;
|
||||
private int dequeueTime;
|
||||
|
||||
public AsynchronousQueue(Handler<T> handler, int dequeueTime) {
|
||||
this.handler = handler;
|
||||
this.dequeueTime = dequeueTime;
|
||||
}
|
||||
|
||||
public boolean push(T t) {
|
||||
synchronized (lock) {
|
||||
if (set.add(t)) {
|
||||
queue.addLast(t);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private T pop() {
|
||||
synchronized (lock) {
|
||||
try {
|
||||
T t = queue.removeFirst();
|
||||
if (!set.remove(t)) {
|
||||
// This should never happen.
|
||||
}
|
||||
return t;
|
||||
} catch (NoSuchElementException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return set.size();
|
||||
}
|
||||
|
||||
public void start() {
|
||||
synchronized (lock) {
|
||||
thread = new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
running();
|
||||
}
|
||||
});
|
||||
thread.start();
|
||||
try {
|
||||
thread.setPriority(Thread.MIN_PRIORITY);
|
||||
} catch (SecurityException e) {
|
||||
log.info("Failed to set minimum priority for worker thread!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
synchronized (lock) {
|
||||
if (thread == null)
|
||||
return;
|
||||
Thread oldThread = thread;
|
||||
thread = null;
|
||||
|
||||
log.info("Stopping map renderer...");
|
||||
|
||||
try {
|
||||
oldThread.join();
|
||||
} catch (InterruptedException e) {
|
||||
log.info("Waiting for map renderer to stop is interrupted");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void running() {
|
||||
try {
|
||||
while (Thread.currentThread() == thread) {
|
||||
T t = pop();
|
||||
if (t != null) {
|
||||
handler.handle(t);
|
||||
}
|
||||
sleep(dequeueTime);
|
||||
}
|
||||
|
||||
} catch (Exception ex) {
|
||||
log.log(Level.SEVERE, "Exception on rendering-thread", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void sleep(int time) {
|
||||
try {
|
||||
Thread.sleep(time);
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
}
|
@ -16,13 +16,13 @@ public class DynmapBlockListener extends BlockListener {
|
||||
@Override
|
||||
public void onBlockPlace(BlockPlaceEvent event) {
|
||||
Block blockPlaced = event.getBlockPlaced();
|
||||
mgr.touch(blockPlaced.getX(), blockPlaced.getY(), blockPlaced.getZ());
|
||||
mgr.touch(blockPlaced.getLocation());
|
||||
}
|
||||
|
||||
public void onBlockDamage(BlockDamageEvent event) {
|
||||
if (event.getDamageLevel() == BlockDamageLevel.BROKEN) {
|
||||
Block blockBroken = event.getBlock();
|
||||
mgr.touch(blockBroken.getX(), blockBroken.getY(), blockBroken.getZ());
|
||||
mgr.touch(blockBroken.getLocation());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ public class DynmapPlayerListener extends PlayerListener {
|
||||
|
||||
if (split[1].equals("render")) {
|
||||
Player player = event.getPlayer();
|
||||
mgr.touch(player.getLocation().getBlockX(), player.getLocation().getBlockY(), player.getLocation().getBlockZ());
|
||||
mgr.touch(player.getLocation());
|
||||
event.setCancelled(true);
|
||||
} else if (split[1].equals("hide")) {
|
||||
if (split.length == 2) {
|
||||
|
@ -17,7 +17,8 @@ import org.bukkit.plugin.PluginDescriptionFile;
|
||||
import org.bukkit.plugin.PluginLoader;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.bukkit.util.config.Configuration;
|
||||
import org.dynmap.debug.BukkitPlayerDebugger;
|
||||
import org.dynmap.debug.Debug;
|
||||
import org.dynmap.debug.LogDebugger;
|
||||
import org.dynmap.web.HttpServer;
|
||||
import org.dynmap.web.handlers.ClientConfigurationHandler;
|
||||
import org.dynmap.web.handlers.ClientUpdateHandler;
|
||||
@ -32,8 +33,7 @@ public class DynmapPlugin extends JavaPlugin {
|
||||
private PlayerList playerList;
|
||||
private Configuration configuration;
|
||||
|
||||
private BukkitPlayerDebugger debugger = new BukkitPlayerDebugger(this);
|
||||
|
||||
public static File tilesDirectory;
|
||||
public static File dataRoot;
|
||||
|
||||
public DynmapPlugin(PluginLoader pluginLoader, Server instance, PluginDescriptionFile desc, File folder, File plugin, ClassLoader cLoader) {
|
||||
@ -54,15 +54,18 @@ public class DynmapPlugin extends JavaPlugin {
|
||||
}
|
||||
|
||||
public void onEnable() {
|
||||
Debug.addDebugger(new LogDebugger());
|
||||
|
||||
configuration = new Configuration(new File(this.getDataFolder(), "configuration.txt"));
|
||||
configuration.load();
|
||||
|
||||
debugger.enable();
|
||||
tilesDirectory = getFile(configuration.getString("tilespath", "web/tiles"));
|
||||
|
||||
playerList = new PlayerList(getServer());
|
||||
playerList.load();
|
||||
|
||||
mapManager = new MapManager(getWorld(), debugger, configuration);
|
||||
mapManager.startManager();
|
||||
mapManager = new MapManager(configuration);
|
||||
mapManager.startRendering();
|
||||
|
||||
InetAddress bindAddress;
|
||||
{
|
||||
@ -78,8 +81,8 @@ public class DynmapPlugin extends JavaPlugin {
|
||||
int port = configuration.getInt("webserver-port", 8123);
|
||||
|
||||
webServer = new HttpServer(bindAddress, port);
|
||||
webServer.handlers.put("/", new FilesystemHandler(mapManager.webDirectory));
|
||||
webServer.handlers.put("/tiles/", new FilesystemHandler(mapManager.tileDirectory));
|
||||
webServer.handlers.put("/", new FilesystemHandler(getFile(configuration.getString("webpath", "web"))));
|
||||
webServer.handlers.put("/tiles/", new FilesystemHandler(tilesDirectory));
|
||||
webServer.handlers.put("/up/", new ClientUpdateHandler(mapManager, playerList, getWorld()));
|
||||
webServer.handlers.put("/up/configuration", new ClientConfigurationHandler((Map<?, ?>) configuration.getProperty("web")));
|
||||
|
||||
@ -93,13 +96,13 @@ public class DynmapPlugin extends JavaPlugin {
|
||||
}
|
||||
|
||||
public void onDisable() {
|
||||
mapManager.stopManager();
|
||||
mapManager.stopRendering();
|
||||
|
||||
if (webServer != null) {
|
||||
webServer.shutdown();
|
||||
webServer = null;
|
||||
}
|
||||
debugger.disable();
|
||||
Debug.clearDebuggers();
|
||||
}
|
||||
|
||||
public void registerEvents() {
|
||||
@ -111,4 +114,18 @@ public class DynmapPlugin extends JavaPlugin {
|
||||
getServer().getPluginManager().registerEvent(Event.Type.PLAYER_COMMAND, playerListener, Priority.Normal, this);
|
||||
getServer().getPluginManager().registerEvent(Event.Type.PLAYER_CHAT, playerListener, Priority.Normal, this);
|
||||
}
|
||||
|
||||
private static File combinePaths(File parent, String path) {
|
||||
return combinePaths(parent, new File(path));
|
||||
}
|
||||
|
||||
private static File combinePaths(File parent, File path) {
|
||||
if (path.isAbsolute())
|
||||
return path;
|
||||
return new File(parent, path.getPath());
|
||||
}
|
||||
|
||||
public File getFile(String path) {
|
||||
return combinePaths(DynmapPlugin.dataRoot, path);
|
||||
}
|
||||
}
|
||||
|
26
src/main/java/org/dynmap/Event.java
Normal file
26
src/main/java/org/dynmap/Event.java
Normal file
@ -0,0 +1,26 @@
|
||||
package org.dynmap;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class Event<T> {
|
||||
private List<Listener<T>> listeners = new LinkedList<Listener<T>>();
|
||||
|
||||
public synchronized void addListener(Listener<T> l) {
|
||||
listeners.add(l);
|
||||
}
|
||||
|
||||
public synchronized void removeListener(Listener<T> l) {
|
||||
listeners.remove(l);
|
||||
}
|
||||
|
||||
public synchronized void trigger(T t) {
|
||||
for (Listener<T> l : listeners) {
|
||||
l.triggered(t);
|
||||
}
|
||||
}
|
||||
|
||||
public interface Listener<T> {
|
||||
void triggered(T t);
|
||||
}
|
||||
}
|
5
src/main/java/org/dynmap/Handler.java
Normal file
5
src/main/java/org/dynmap/Handler.java
Normal file
@ -0,0 +1,5 @@
|
||||
package org.dynmap;
|
||||
|
||||
public interface Handler<T> {
|
||||
void handle(T t);
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
package org.dynmap;
|
||||
|
||||
public class MapLocation {
|
||||
public float x;
|
||||
public float y;
|
||||
}
|
@ -3,84 +3,48 @@ package org.dynmap;
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.util.config.ConfigurationNode;
|
||||
import org.dynmap.debug.Debugger;
|
||||
import org.dynmap.debug.Debug;
|
||||
|
||||
public class MapManager extends Thread {
|
||||
public class MapManager {
|
||||
protected static final Logger log = Logger.getLogger("Minecraft");
|
||||
|
||||
private World world;
|
||||
private Debugger debugger;
|
||||
private MapType[] maps;
|
||||
public StaleQueue staleQueue;
|
||||
private MapType[] mapTypes;
|
||||
public AsynchronousQueue<MapTile> tileQueue;
|
||||
public UpdateQueue updateQueue;
|
||||
public PlayerList playerList;
|
||||
|
||||
/* lock for our data structures */
|
||||
public static final Object lock = new Object();
|
||||
|
||||
/* whether the worker thread should be running now */
|
||||
private boolean running = false;
|
||||
|
||||
/* path to image tile directory */
|
||||
public File tileDirectory;
|
||||
|
||||
/* web files location */
|
||||
public File webDirectory;
|
||||
|
||||
/* bind web server to ip-address */
|
||||
public String bindaddress = "0.0.0.0";
|
||||
|
||||
/* port to run web server on */
|
||||
public int serverport = 8123;
|
||||
|
||||
/* time to pause between rendering tiles (ms) */
|
||||
public int renderWait = 500;
|
||||
|
||||
public boolean loadChunks = true;
|
||||
|
||||
public void debug(String msg) {
|
||||
debugger.debug(msg);
|
||||
}
|
||||
|
||||
private static File combinePaths(File parent, String path) {
|
||||
return combinePaths(parent, new File(path));
|
||||
}
|
||||
|
||||
private static File combinePaths(File parent, File path) {
|
||||
if (path.isAbsolute())
|
||||
return path;
|
||||
return new File(parent, path.getPath());
|
||||
}
|
||||
|
||||
public MapManager(World world, Debugger debugger, ConfigurationNode configuration) {
|
||||
this.world = world;
|
||||
this.debugger = debugger;
|
||||
this.staleQueue = new StaleQueue();
|
||||
public MapManager(ConfigurationNode configuration) {
|
||||
this.updateQueue = new UpdateQueue();
|
||||
|
||||
tileDirectory = combinePaths(DynmapPlugin.dataRoot, configuration.getString("tilespath", "web/tiles"));
|
||||
webDirectory = combinePaths(DynmapPlugin.dataRoot, configuration.getString("webpath", "web"));
|
||||
renderWait = (int) (configuration.getDouble("renderinterval", 0.5) * 1000);
|
||||
loadChunks = configuration.getBoolean("loadchunks", true);
|
||||
|
||||
if (!tileDirectory.isDirectory())
|
||||
tileDirectory.mkdirs();
|
||||
|
||||
maps = loadMapTypes(configuration);
|
||||
this.tileQueue = new AsynchronousQueue<MapTile>(new Handler<MapTile>() {
|
||||
@Override
|
||||
public void handle(MapTile t) {
|
||||
render(t);
|
||||
}
|
||||
}, (int) (configuration.getDouble("renderinterval", 0.5) * 1000));
|
||||
|
||||
mapTypes = loadMapTypes(configuration);
|
||||
|
||||
tileQueue.start();
|
||||
}
|
||||
|
||||
void renderFullWorld(Location l) {
|
||||
debugger.debug("Full render starting...");
|
||||
for (MapType map : maps) {
|
||||
World world = l.getWorld();
|
||||
log.info("Full render starting...");
|
||||
for (MapType map : mapTypes) {
|
||||
int requiredChunkCount = 200;
|
||||
HashSet<MapTile> found = new HashSet<MapTile>();
|
||||
HashSet<MapTile> rendered = new HashSet<MapTile>();
|
||||
@ -114,10 +78,10 @@ public class MapManager extends Thread {
|
||||
loadedChunks.add(chunk);
|
||||
}
|
||||
|
||||
if (map.render(tile)) {
|
||||
if (render(tile)) {
|
||||
found.remove(tile);
|
||||
rendered.add(tile);
|
||||
updateQueue.pushUpdate(new Client.Tile(tile.getName()));
|
||||
updateQueue.pushUpdate(new Client.Tile(tile.getFilename()));
|
||||
for (MapTile adjTile : map.getAdjecentTiles(tile)) {
|
||||
if (!found.contains(adjTile) && !rendered.contains(adjTile)) {
|
||||
found.add(adjTile);
|
||||
@ -135,10 +99,17 @@ public class MapManager extends Thread {
|
||||
world.unloadChunk(c.x, c.z, false, true);
|
||||
}
|
||||
}
|
||||
debugger.debug("Full render finished.");
|
||||
log.info("Full render finished.");
|
||||
}
|
||||
|
||||
private MapType[] loadMapTypes(ConfigurationNode configuration) {
|
||||
Event.Listener<MapTile> invalitateListener = new Event.Listener<MapTile>() {
|
||||
@Override
|
||||
public void triggered(MapTile t) {
|
||||
invalidateTile(t);
|
||||
}
|
||||
};
|
||||
|
||||
List<?> configuredMaps = (List<?>) configuration.getProperty("maps");
|
||||
ArrayList<MapType> mapTypes = new ArrayList<MapType>();
|
||||
for (Object configuredMapObj : configuredMaps) {
|
||||
@ -148,83 +119,24 @@ public class MapManager extends Thread {
|
||||
String typeName = (String) configuredMap.get("class");
|
||||
log.info("Loading map '" + typeName.toString() + "'...");
|
||||
Class<?> mapTypeClass = Class.forName(typeName);
|
||||
Constructor<?> constructor = mapTypeClass.getConstructor(MapManager.class, World.class, Debugger.class, Map.class);
|
||||
MapType mapType = (MapType) constructor.newInstance(this, world, debugger, configuredMap);
|
||||
Constructor<?> constructor = mapTypeClass.getConstructor(Map.class);
|
||||
MapType mapType = (MapType) constructor.newInstance(configuredMap);
|
||||
mapType.onTileInvalidated.addListener(invalitateListener);
|
||||
mapTypes.add(mapType);
|
||||
} catch (Exception e) {
|
||||
debugger.error("Error loading map", e);
|
||||
log.log(Level.SEVERE, "Error loading maptype", e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
MapType[] result = new MapType[mapTypes.size()];
|
||||
mapTypes.toArray(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/* initialize and start map manager */
|
||||
public void startManager() {
|
||||
synchronized (lock) {
|
||||
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() {
|
||||
synchronized (lock) {
|
||||
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() {
|
||||
try {
|
||||
log.info("Map renderer has started.");
|
||||
|
||||
while (running) {
|
||||
MapTile t = staleQueue.popStaleTile();
|
||||
if (t != null) {
|
||||
debugger.debug("Rendering tile " + t + "...");
|
||||
boolean isNonEmptyTile = t.getMap().render(t);
|
||||
updateQueue.pushUpdate(new Client.Tile(t.getName()));
|
||||
|
||||
try {
|
||||
Thread.sleep(renderWait);
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
Thread.sleep(500);
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.info("Map renderer has stopped.");
|
||||
} catch (Exception ex) {
|
||||
debugger.error("Exception on rendering-thread: " + ex.toString());
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void touch(int x, int y, int z) {
|
||||
for (int i = 0; i < maps.length; i++) {
|
||||
MapTile[] tiles = maps[i].getTiles(new Location(world, x, y, z));
|
||||
|
||||
public void touch(Location l) {
|
||||
Debug.debug("Touched " + l.toString());
|
||||
for (int i = 0; i < mapTypes.length; i++) {
|
||||
MapTile[] tiles = mapTypes[i].getTiles(l);
|
||||
for (int j = 0; j < tiles.length; j++) {
|
||||
invalidateTile(tiles[j]);
|
||||
}
|
||||
@ -232,7 +144,32 @@ public class MapManager extends Thread {
|
||||
}
|
||||
|
||||
public void invalidateTile(MapTile tile) {
|
||||
debugger.debug("Invalidating tile " + tile.getName());
|
||||
staleQueue.pushStaleTile(tile);
|
||||
Debug.debug("Invalidating tile " + tile.getFilename());
|
||||
tileQueue.push(tile);
|
||||
}
|
||||
|
||||
public void startRendering() {
|
||||
tileQueue.start();
|
||||
}
|
||||
|
||||
public void stopRendering() {
|
||||
tileQueue.stop();
|
||||
}
|
||||
|
||||
public boolean render(MapTile tile) {
|
||||
return tile.getMap().render(tile, getTileFile(tile));
|
||||
}
|
||||
|
||||
|
||||
private HashMap<World, File> worldTileDirectories = new HashMap<World, File>();
|
||||
private File getTileFile(MapTile tile) {
|
||||
World world = tile.getWorld();
|
||||
File worldTileDirectory = worldTileDirectories.get(world);
|
||||
if (worldTileDirectory == null) {
|
||||
worldTileDirectory = new File(DynmapPlugin.tilesDirectory, tile.getWorld().getName());
|
||||
worldTileDirectory.mkdirs();
|
||||
worldTileDirectories.put(world, worldTileDirectory);
|
||||
}
|
||||
return new File(worldTileDirectory, tile.getFilename());
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,28 @@
|
||||
package org.dynmap;
|
||||
|
||||
import org.bukkit.World;
|
||||
|
||||
public abstract class MapTile {
|
||||
private World world;
|
||||
private MapType map;
|
||||
|
||||
public World getWorld() {
|
||||
return world;
|
||||
}
|
||||
|
||||
public MapType getMap() {
|
||||
return map;
|
||||
}
|
||||
|
||||
public abstract String getName();
|
||||
public abstract String getFilename();
|
||||
|
||||
public MapTile(MapType map) {
|
||||
public MapTile(World world, MapType map) {
|
||||
this.world = world;
|
||||
this.map = map;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getFilename().hashCode() ^ getWorld().hashCode();
|
||||
}
|
||||
}
|
||||
|
@ -1,41 +1,17 @@
|
||||
package org.dynmap;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.dynmap.debug.Debugger;
|
||||
|
||||
public abstract class MapType {
|
||||
private MapManager manager;
|
||||
|
||||
public MapManager getMapManager() {
|
||||
return manager;
|
||||
}
|
||||
|
||||
private World world;
|
||||
|
||||
public World getWorld() {
|
||||
return world;
|
||||
}
|
||||
|
||||
private Debugger debugger;
|
||||
|
||||
public Debugger getDebugger() {
|
||||
return debugger;
|
||||
}
|
||||
|
||||
public MapType(MapManager manager, World world, Debugger debugger) {
|
||||
this.manager = manager;
|
||||
this.world = world;
|
||||
this.debugger = debugger;
|
||||
}
|
||||
|
||||
public Event<MapTile> onTileInvalidated = new Event<MapTile>();
|
||||
|
||||
public abstract MapTile[] getTiles(Location l);
|
||||
|
||||
public abstract MapTile[] getAdjecentTiles(MapTile tile);
|
||||
|
||||
public abstract DynmapChunk[] getRequiredChunks(MapTile tile);
|
||||
|
||||
public abstract boolean render(MapTile tile);
|
||||
|
||||
public abstract boolean isRendered(MapTile tile);
|
||||
public abstract boolean render(MapTile tile, File outputFile);
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
package org.dynmap.debug;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.entity.Player;
|
||||
@ -11,14 +9,9 @@ import org.bukkit.event.Event.Priority;
|
||||
import org.bukkit.event.player.PlayerChatEvent;
|
||||
import org.bukkit.event.player.PlayerEvent;
|
||||
import org.bukkit.event.player.PlayerListener;
|
||||
import org.bukkit.plugin.PluginDescriptionFile;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
public class BukkitPlayerDebugger implements Debugger {
|
||||
protected static final Logger log = Logger.getLogger("Minecraft");
|
||||
|
||||
private boolean isLogging = false;
|
||||
|
||||
private JavaPlugin plugin;
|
||||
private HashSet<Player> debugees = new HashSet<Player>();
|
||||
private String debugCommand;
|
||||
@ -28,10 +21,10 @@ public class BukkitPlayerDebugger implements Debugger {
|
||||
public BukkitPlayerDebugger(JavaPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
|
||||
PluginDescriptionFile pdfFile = plugin.getDescription();
|
||||
debugCommand = "/debug_" + pdfFile.getName();
|
||||
undebugCommand = "/undebug_" + pdfFile.getName();
|
||||
prepend = pdfFile.getName() + ": ";
|
||||
String name = "dynmap";
|
||||
debugCommand = "/debug_" + name;
|
||||
undebugCommand = "/undebug_" + name;
|
||||
prepend = name + ": ";
|
||||
}
|
||||
|
||||
public synchronized void enable() {
|
||||
@ -63,19 +56,15 @@ public class BukkitPlayerDebugger implements Debugger {
|
||||
|
||||
public synchronized void debug(String message) {
|
||||
sendToDebuggees(message);
|
||||
if (isLogging)
|
||||
log.info(prepend + message);
|
||||
}
|
||||
|
||||
public synchronized void error(String message) {
|
||||
sendToDebuggees(prepend + ChatColor.RED + message);
|
||||
log.log(Level.SEVERE, prepend + message);
|
||||
}
|
||||
|
||||
public synchronized void error(String message, Throwable thrown) {
|
||||
sendToDebuggees(prepend + ChatColor.RED + message);
|
||||
sendToDebuggees(thrown.toString());
|
||||
log.log(Level.SEVERE, prepend + message);
|
||||
}
|
||||
|
||||
protected class CommandListener extends PlayerListener {
|
||||
|
32
src/main/java/org/dynmap/debug/Debug.java
Normal file
32
src/main/java/org/dynmap/debug/Debug.java
Normal file
@ -0,0 +1,32 @@
|
||||
package org.dynmap.debug;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class Debug {
|
||||
private static List<Debugger> debuggers = new LinkedList<Debugger>();
|
||||
|
||||
public synchronized static void addDebugger(Debugger d) {
|
||||
debuggers.add(d);
|
||||
}
|
||||
|
||||
public synchronized static void removeDebugger(Debugger d) {
|
||||
debuggers.remove(d);
|
||||
}
|
||||
|
||||
public synchronized static void clearDebuggers() {
|
||||
debuggers.clear();
|
||||
}
|
||||
|
||||
public synchronized static void debug(String message) {
|
||||
for(Debugger d : debuggers) d.debug(message);
|
||||
}
|
||||
|
||||
public synchronized static void error(String message) {
|
||||
for(Debugger d : debuggers) d.error(message);
|
||||
}
|
||||
|
||||
public synchronized static void error(String message, Throwable thrown) {
|
||||
for(Debugger d : debuggers) d.error(message, thrown);
|
||||
}
|
||||
}
|
25
src/main/java/org/dynmap/debug/LogDebugger.java
Normal file
25
src/main/java/org/dynmap/debug/LogDebugger.java
Normal file
@ -0,0 +1,25 @@
|
||||
package org.dynmap.debug;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class LogDebugger implements Debugger {
|
||||
protected static final Logger log = Logger.getLogger("Minecraft");
|
||||
private static String prepend = "dynmap: ";
|
||||
|
||||
@Override
|
||||
public void debug(String message) {
|
||||
log.info(prepend + message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(String message) {
|
||||
log.log(Level.SEVERE, prepend + message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(String message, Throwable thrown) {
|
||||
log.log(Level.SEVERE, prepend + message);
|
||||
}
|
||||
|
||||
}
|
@ -4,12 +4,11 @@ import java.awt.Color;
|
||||
import java.util.Map;
|
||||
|
||||
import org.bukkit.World;
|
||||
import org.dynmap.debug.Debugger;
|
||||
|
||||
public class CaveTileRenderer extends DefaultTileRenderer {
|
||||
|
||||
public CaveTileRenderer(Debugger debugger, Map<String, Object> configuration) {
|
||||
super(debugger, configuration);
|
||||
public CaveTileRenderer(Map<String, Object> configuration) {
|
||||
super(configuration);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -10,24 +10,23 @@ import java.util.Map;
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import org.bukkit.World;
|
||||
import org.dynmap.debug.Debugger;
|
||||
import org.dynmap.debug.Debug;
|
||||
|
||||
public class DefaultTileRenderer implements MapTileRenderer {
|
||||
protected static Color translucent = new Color(0, 0, 0, 0);
|
||||
private String name;
|
||||
protected Debugger debugger;
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public DefaultTileRenderer(Debugger debugger, Map<String, Object> configuration) {
|
||||
this.debugger = debugger;
|
||||
public DefaultTileRenderer(Map<String, Object> configuration) {
|
||||
name = (String) configuration.get("prefix");
|
||||
}
|
||||
|
||||
public boolean render(KzedMapTile tile, String path) {
|
||||
World world = tile.getMap().getWorld();
|
||||
public boolean render(KzedMapTile tile, File outputFile) {
|
||||
World world = tile.getWorld();
|
||||
BufferedImage im = new BufferedImage(KzedMap.tileWidth, KzedMap.tileHeight, BufferedImage.TYPE_INT_RGB);
|
||||
|
||||
WritableRaster r = im.getRaster();
|
||||
@ -92,9 +91,11 @@ public class DefaultTileRenderer implements MapTileRenderer {
|
||||
}
|
||||
|
||||
/* save the generated tile */
|
||||
saveTile(tile, im, path);
|
||||
saveImage(im, outputFile);
|
||||
im.flush();
|
||||
((KzedMap) tile.getMap()).invalidateTile(new KzedZoomedMapTile((KzedMap) tile.getMap(), tile));
|
||||
|
||||
tile.file = outputFile;
|
||||
((KzedMap) tile.getMap()).invalidateTile(new KzedZoomedMapTile(world, (KzedMap) tile.getMap(), tile));
|
||||
|
||||
return !isempty;
|
||||
}
|
||||
@ -154,23 +155,15 @@ public class DefaultTileRenderer implements MapTileRenderer {
|
||||
}
|
||||
|
||||
/* save rendered tile, update zoom-out tile */
|
||||
public void saveTile(KzedMapTile tile, BufferedImage im, String path) {
|
||||
String tilePath = getPath(tile, path);
|
||||
|
||||
debugger.debug("saving tile " + tilePath);
|
||||
|
||||
public void saveImage(BufferedImage im, File outputFile) {
|
||||
Debug.debug("saving image " + outputFile.getPath());
|
||||
/* save image */
|
||||
try {
|
||||
File file = new File(tilePath);
|
||||
ImageIO.write(im, "png", file);
|
||||
ImageIO.write(im, "png", outputFile);
|
||||
} catch (IOException e) {
|
||||
debugger.error("Failed to save tile: " + tilePath, e);
|
||||
Debug.error("Failed to save image: " + outputFile.getPath(), e);
|
||||
} catch (java.lang.NullPointerException e) {
|
||||
debugger.error("Failed to save tile (NullPointerException): " + tilePath, e);
|
||||
Debug.error("Failed to save image (NullPointerException): " + outputFile.getPath(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public static String getPath(KzedMapTile tile, String outputPath) {
|
||||
return new File(new File(outputPath), tile.getName() + ".png").getPath();
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import org.dynmap.DynmapChunk;
|
||||
import org.dynmap.MapManager;
|
||||
import org.dynmap.MapTile;
|
||||
import org.dynmap.MapType;
|
||||
import org.dynmap.debug.Debug;
|
||||
import org.dynmap.debug.Debugger;
|
||||
|
||||
public class KzedMap extends MapType {
|
||||
@ -43,14 +44,13 @@ public class KzedMap extends MapType {
|
||||
MapTileRenderer[] renderers;
|
||||
ZoomedTileRenderer zoomrenderer;
|
||||
|
||||
public KzedMap(MapManager manager, World world, Debugger debugger, Map<String, Object> configuration) {
|
||||
super(manager, world, debugger);
|
||||
public KzedMap(Map<String, Object> configuration) {
|
||||
if (colors == null) {
|
||||
colors = loadColorSet("colors.txt");
|
||||
}
|
||||
|
||||
renderers = loadRenderers(configuration);
|
||||
zoomrenderer = new ZoomedTileRenderer(debugger, configuration);
|
||||
zoomrenderer = new ZoomedTileRenderer(configuration);
|
||||
}
|
||||
|
||||
private MapTileRenderer[] loadRenderers(Map<String, Object> configuration) {
|
||||
@ -63,11 +63,12 @@ public class KzedMap extends MapType {
|
||||
String typeName = (String) configuredRenderer.get("class");
|
||||
log.info("Loading renderer '" + typeName.toString() + "'...");
|
||||
Class<?> mapTypeClass = Class.forName(typeName);
|
||||
Constructor<?> constructor = mapTypeClass.getConstructor(Debugger.class, Map.class);
|
||||
MapTileRenderer mapTileRenderer = (MapTileRenderer) constructor.newInstance(getDebugger(), configuredRenderer);
|
||||
Constructor<?> constructor = mapTypeClass.getConstructor(Map.class);
|
||||
MapTileRenderer mapTileRenderer = (MapTileRenderer) constructor.newInstance(configuredRenderer);
|
||||
renderers.add(mapTileRenderer);
|
||||
} catch (Exception e) {
|
||||
getDebugger().error("Error loading renderer", e);
|
||||
Debug.error("Error loading renderer", e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
MapTileRenderer[] result = new MapTileRenderer[renderers.size()];
|
||||
@ -77,6 +78,8 @@ public class KzedMap extends MapType {
|
||||
|
||||
@Override
|
||||
public MapTile[] getTiles(Location l) {
|
||||
World world = l.getWorld();
|
||||
|
||||
int x = l.getBlockX();
|
||||
int y = l.getBlockY();
|
||||
int z = l.getBlockZ();
|
||||
@ -92,7 +95,7 @@ public class KzedMap extends MapType {
|
||||
|
||||
ArrayList<MapTile> tiles = new ArrayList<MapTile>();
|
||||
|
||||
addTile(tiles, tx, ty);
|
||||
addTile(tiles, world, tx, ty);
|
||||
|
||||
boolean ledge = tilex(px - 4) != tx;
|
||||
boolean tedge = tiley(py - 4) != ty;
|
||||
@ -100,22 +103,22 @@ public class KzedMap extends MapType {
|
||||
boolean bedge = tiley(py + 4) != ty;
|
||||
|
||||
if (ledge)
|
||||
addTile(tiles, tx - tileWidth, ty);
|
||||
addTile(tiles, world, tx - tileWidth, ty);
|
||||
if (redge)
|
||||
addTile(tiles, tx + tileWidth, ty);
|
||||
addTile(tiles, world, tx + tileWidth, ty);
|
||||
if (tedge)
|
||||
addTile(tiles, tx, ty - tileHeight);
|
||||
addTile(tiles, world, tx, ty - tileHeight);
|
||||
if (bedge)
|
||||
addTile(tiles, tx, ty + tileHeight);
|
||||
addTile(tiles, world, tx, ty + tileHeight);
|
||||
|
||||
if (ledge && tedge)
|
||||
addTile(tiles, tx - tileWidth, ty - tileHeight);
|
||||
addTile(tiles, world, tx - tileWidth, ty - tileHeight);
|
||||
if (ledge && bedge)
|
||||
addTile(tiles, tx - tileWidth, ty + tileHeight);
|
||||
addTile(tiles, world, tx - tileWidth, ty + tileHeight);
|
||||
if (redge && tedge)
|
||||
addTile(tiles, tx + tileWidth, ty - tileHeight);
|
||||
addTile(tiles, world, tx + tileWidth, ty - tileHeight);
|
||||
if (redge && bedge)
|
||||
addTile(tiles, tx + tileWidth, ty + tileHeight);
|
||||
addTile(tiles, world, tx + tileWidth, ty + tileHeight);
|
||||
|
||||
MapTile[] result = new MapTile[tiles.size()];
|
||||
tiles.toArray(result);
|
||||
@ -126,24 +129,25 @@ public class KzedMap extends MapType {
|
||||
public MapTile[] getAdjecentTiles(MapTile tile) {
|
||||
if (tile instanceof KzedMapTile) {
|
||||
KzedMapTile t = (KzedMapTile) tile;
|
||||
World world = tile.getWorld();
|
||||
MapTileRenderer renderer = t.renderer;
|
||||
return new MapTile[] {
|
||||
new KzedMapTile(this, renderer, t.px - tileWidth, t.py),
|
||||
new KzedMapTile(this, renderer, t.px + tileWidth, t.py),
|
||||
new KzedMapTile(this, renderer, t.px, t.py - tileHeight),
|
||||
new KzedMapTile(this, renderer, t.px, t.py + tileHeight) };
|
||||
new KzedMapTile(world, this, renderer, t.px - tileWidth, t.py),
|
||||
new KzedMapTile(world, this, renderer, t.px + tileWidth, t.py),
|
||||
new KzedMapTile(world, this, renderer, t.px, t.py - tileHeight),
|
||||
new KzedMapTile(world, this, renderer, t.px, t.py + tileHeight) };
|
||||
}
|
||||
return new MapTile[0];
|
||||
}
|
||||
|
||||
public void addTile(ArrayList<MapTile> tiles, int px, int py) {
|
||||
public void addTile(ArrayList<MapTile> tiles, World world, int px, int py) {
|
||||
for (int i = 0; i < renderers.length; i++) {
|
||||
tiles.add(new KzedMapTile(this, renderers[i], px, py));
|
||||
tiles.add(new KzedMapTile(world, this, renderers[i], px, py));
|
||||
}
|
||||
}
|
||||
|
||||
public void invalidateTile(MapTile tile) {
|
||||
getMapManager().invalidateTile(tile);
|
||||
onTileInvalidated.trigger(tile);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -174,21 +178,12 @@ public class KzedMap extends MapType {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean render(MapTile tile) {
|
||||
public boolean render(MapTile tile, File outputFile) {
|
||||
if (tile instanceof KzedZoomedMapTile) {
|
||||
zoomrenderer.render((KzedZoomedMapTile) tile, getMapManager().tileDirectory.getAbsolutePath());
|
||||
zoomrenderer.render((KzedZoomedMapTile) tile, outputFile);
|
||||
return true;
|
||||
} else if (tile instanceof KzedMapTile) {
|
||||
return ((KzedMapTile) tile).renderer.render((KzedMapTile) tile, getMapManager().tileDirectory.getAbsolutePath());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRendered(MapTile tile) {
|
||||
if (tile instanceof KzedMapTile) {
|
||||
File tileFile = new File(DefaultTileRenderer.getPath((KzedMapTile) tile, getMapManager().tileDirectory.getAbsolutePath()));
|
||||
return tileFile.exists();
|
||||
return ((KzedMapTile) tile).renderer.render((KzedMapTile) tile, outputFile);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -235,10 +230,10 @@ public class KzedMap extends MapType {
|
||||
/* load colorset */
|
||||
File cfile = new File(colorsetpath);
|
||||
if (cfile.isFile()) {
|
||||
getDebugger().debug("Loading colors from '" + colorsetpath + "'...");
|
||||
Debug.debug("Loading colors from '" + colorsetpath + "'...");
|
||||
stream = new FileInputStream(cfile);
|
||||
} else {
|
||||
getDebugger().debug("Loading colors from jar...");
|
||||
Debug.debug("Loading colors from jar...");
|
||||
stream = KzedMap.class.getResourceAsStream("/colors.txt");
|
||||
}
|
||||
|
||||
@ -270,7 +265,7 @@ public class KzedMap extends MapType {
|
||||
}
|
||||
scanner.close();
|
||||
} catch (Exception e) {
|
||||
getDebugger().error("Could not load colors", e);
|
||||
Debug.error("Could not load colors", e);
|
||||
return null;
|
||||
}
|
||||
return colors;
|
||||
|
@ -1,25 +1,20 @@
|
||||
package org.dynmap.kzedmap;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import java.io.File;
|
||||
import org.bukkit.World;
|
||||
import org.dynmap.MapTile;
|
||||
|
||||
public class KzedMapTile extends MapTile {
|
||||
protected static final Logger log = Logger.getLogger("Minecraft");
|
||||
|
||||
public KzedMap map;
|
||||
|
||||
public MapTileRenderer renderer;
|
||||
|
||||
/* projection position */
|
||||
public int px, py;
|
||||
|
||||
/* minecraft space origin */
|
||||
public int mx, my, mz;
|
||||
|
||||
// Hack.
|
||||
public File file = null;
|
||||
|
||||
/* create new MapTile */
|
||||
public KzedMapTile(KzedMap map, MapTileRenderer renderer, int px, int py) {
|
||||
super(map);
|
||||
public KzedMapTile(World world, KzedMap map, MapTileRenderer renderer, int px, int py) {
|
||||
super(world, map);
|
||||
this.map = map;
|
||||
this.renderer = renderer;
|
||||
this.px = px;
|
||||
@ -31,12 +26,13 @@ public class KzedMapTile extends MapTile {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return renderer.getName() + "_" + px + "_" + py;
|
||||
public String getFilename() {
|
||||
return renderer.getName() + "_" + px + "_" + py + ".png";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getName().hashCode();
|
||||
return getFilename().hashCode() ^ getWorld().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -48,11 +44,10 @@ public class KzedMapTile extends MapTile {
|
||||
}
|
||||
|
||||
public boolean equals(KzedMapTile o) {
|
||||
return o.getName().equals(getName());
|
||||
return o.px == px && o.py == py && o.getWorld().equals(getWorld());
|
||||
}
|
||||
|
||||
/* return a simple string representation... */
|
||||
public String toString() {
|
||||
return getName();
|
||||
return getWorld().getName() + ":" + getFilename();
|
||||
}
|
||||
}
|
||||
|
@ -2,18 +2,19 @@ package org.dynmap.kzedmap;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
import org.bukkit.World;
|
||||
import org.dynmap.MapTile;
|
||||
|
||||
public class KzedZoomedMapTile extends MapTile {
|
||||
@Override
|
||||
public String getName() {
|
||||
return "z" + originalTile.renderer.getName() + "_" + getTileX() + "_" + getTileY();
|
||||
public String getFilename() {
|
||||
return "z" + originalTile.renderer.getName() + "_" + getTileX() + "_" + getTileY() + ".png";
|
||||
}
|
||||
|
||||
public KzedMapTile originalTile;
|
||||
|
||||
public KzedZoomedMapTile(KzedMap map, KzedMapTile original) {
|
||||
super(map);
|
||||
public KzedZoomedMapTile(World world, KzedMap map, KzedMapTile original) {
|
||||
super(world, map);
|
||||
this.originalTile = original;
|
||||
}
|
||||
|
||||
@ -42,7 +43,7 @@ public class KzedZoomedMapTile extends MapTile {
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getName().hashCode();
|
||||
return getFilename().hashCode() ^ getWorld().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1,7 +1,9 @@
|
||||
package org.dynmap.kzedmap;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public interface MapTileRenderer {
|
||||
String getName();
|
||||
|
||||
boolean render(KzedMapTile tile, String path);
|
||||
boolean render(KzedMapTile tile, File outputFile);
|
||||
}
|
||||
|
@ -6,19 +6,14 @@ import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import org.dynmap.debug.Debugger;
|
||||
import org.dynmap.debug.Debug;
|
||||
|
||||
public class ZoomedTileRenderer {
|
||||
protected Debugger debugger;
|
||||
|
||||
public ZoomedTileRenderer(Debugger debugger, Map<String, Object> configuration) {
|
||||
this.debugger = debugger;
|
||||
public ZoomedTileRenderer(Map<String, Object> configuration) {
|
||||
}
|
||||
|
||||
public void render(KzedZoomedMapTile zt, String outputPath) {
|
||||
public void render(KzedZoomedMapTile zt, File outputPath) {
|
||||
KzedMapTile originalTile = zt.originalTile;
|
||||
int px = originalTile.px;
|
||||
int py = originalTile.py;
|
||||
@ -27,17 +22,17 @@ public class ZoomedTileRenderer {
|
||||
|
||||
BufferedImage image = null;
|
||||
try {
|
||||
image = ImageIO.read(new File(new File(outputPath), originalTile.getName() + ".png"));
|
||||
image = ImageIO.read(originalTile.file);
|
||||
} catch (IOException e) {
|
||||
}
|
||||
|
||||
if (image == null) {
|
||||
debugger.debug("Could not load original tile, won't render zoom-out tile.");
|
||||
Debug.debug("Could not load original tile, won't render zoom-out tile.");
|
||||
return;
|
||||
}
|
||||
|
||||
BufferedImage zIm = null;
|
||||
File zoomFile = new File(new File(outputPath), zt.getName() + ".png");
|
||||
File zoomFile = outputPath;
|
||||
try {
|
||||
zIm = ImageIO.read(zoomFile);
|
||||
} catch (IOException e) {
|
||||
@ -46,9 +41,9 @@ public class ZoomedTileRenderer {
|
||||
if (zIm == null) {
|
||||
/* create new one */
|
||||
zIm = new BufferedImage(KzedMap.tileWidth, KzedMap.tileHeight, BufferedImage.TYPE_INT_RGB);
|
||||
debugger.debug("New zoom-out tile created " + zt.getName());
|
||||
Debug.debug("New zoom-out tile created " + zt.getFilename());
|
||||
} else {
|
||||
debugger.debug("Loaded zoom-out tile from " + zt.getName());
|
||||
Debug.debug("Loaded zoom-out tile from " + zt.getFilename());
|
||||
}
|
||||
|
||||
/* update zoom-out tile */
|
||||
@ -77,11 +72,11 @@ public class ZoomedTileRenderer {
|
||||
/* save zoom-out tile */
|
||||
try {
|
||||
ImageIO.write(zIm, "png", zoomFile);
|
||||
debugger.debug("Saved zoom-out tile at " + zoomFile.getName());
|
||||
Debug.debug("Saved zoom-out tile at " + zoomFile.getName());
|
||||
} catch (IOException e) {
|
||||
debugger.error("Failed to save zoom-out tile: " + zoomFile.getName(), e);
|
||||
Debug.error("Failed to save zoom-out tile: " + zoomFile.getName(), e);
|
||||
} catch (java.lang.NullPointerException e) {
|
||||
debugger.error("Failed to save zoom-out tile (NullPointerException): " + zoomFile.getName(), e);
|
||||
Debug.error("Failed to save zoom-out tile (NullPointerException): " + zoomFile.getName(), e);
|
||||
}
|
||||
zIm.flush();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user