package org.dynmap.markers.impl; import java.awt.image.BufferedImage; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; import javax.imageio.ImageIO; import org.dynmap.ConfigurationNode; import org.dynmap.DynmapCore; import org.dynmap.DynmapLocation; import org.dynmap.DynmapWorld; import org.dynmap.Event; import org.dynmap.Log; import org.dynmap.MapManager; import org.dynmap.Client; import org.dynmap.Client.ComponentMessage; import org.dynmap.common.DynmapCommandSender; import org.dynmap.common.DynmapPlayer; import org.dynmap.hdmap.HDPerspective; import org.dynmap.markers.AreaMarker; import org.dynmap.markers.CircleMarker; import org.dynmap.markers.EnterExitMarker; import org.dynmap.markers.EnterExitMarker.EnterExitText; import org.dynmap.markers.Marker; import org.dynmap.markers.MarkerAPI; import org.dynmap.markers.MarkerDescription; import org.dynmap.markers.MarkerIcon; import org.dynmap.markers.MarkerIcon.MarkerSize; import org.dynmap.markers.MarkerSet; import org.dynmap.markers.PlayerSet; import org.dynmap.markers.PolyLineMarker; import org.dynmap.utils.BufferOutputStream; import org.dynmap.web.Json; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.function.Supplier; /** * Implementation class for MarkerAPI - should not be called directly */ public class MarkerAPIImpl implements MarkerAPI, Event.Listener { private ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); private File markerpersist; private File markerpersist_old; private File markerdir; /* Local store for markers (internal) */ private HashMap markericons = new HashMap(); private ConcurrentHashMap markersets = new ConcurrentHashMap(); private HashMap> pointaccum = new HashMap>(); private HashMap playersets = new HashMap(); private DynmapCore core; static MarkerAPIImpl api; private Map>> tabCompletions = null; /* Built-in icons */ private static final String[] builtin_icons = { "anchor", "bank", "basket", "bed", "beer", "bighouse", "blueflag", "bomb", "bookshelf", "bricks", "bronzemedal", "bronzestar", "building", "cake", "camera", "cart", "caution", "chest", "church", "coins", "comment", "compass", "construction", "cross", "cup", "cutlery", "default", "diamond", "dog", "door", "down", "drink", "exclamation", "factory", "fire", "flower", "gear", "goldmedal", "goldstar", "greenflag", "hammer", "heart", "house", "key", "king", "left", "lightbulb", "lighthouse", "lock", "minecart", "orangeflag", "pin", "pinkflag", "pirateflag", "pointdown", "pointleft", "pointright", "pointup", "portal", "purpleflag", "queen", "redflag", "right", "ruby", "scales", "skull", "shield", "sign", "silvermedal", "silverstar", "star", "sun", "temple", "theater", "tornado", "tower", "tree", "truck", "up", "walk", "warning", "world", "wrench", "yellowflag", "offlineuser" }; /* Component messages for client updates */ public static class MarkerComponentMessage extends ComponentMessage { public String ctype = "markers"; } public static class MarkerUpdated extends MarkerComponentMessage { public String msg; public double x, y, z; public String id; public String label; public String icon; public String set; public boolean markup; public String desc; public String dim; public int minzoom; public int maxzoom; public MarkerUpdated(Marker m, boolean deleted) { this.id = m.getMarkerID(); this.label = m.getLabel(); this.x = m.getX(); this.y = m.getY(); this.z = m.getZ(); this.set = m.getMarkerSet().getMarkerSetID(); this.icon = m.getMarkerIcon().getMarkerIconID(); this.markup = true; // We are markup format all the time now this.desc = m.getDescription(); this.dim = m.getMarkerIcon().getMarkerIconSize().getSize(); this.minzoom = m.getMinZoom(); this.maxzoom = m.getMaxZoom(); if(deleted) msg = "markerdeleted"; else msg = "markerupdated"; } @Override public boolean equals(Object o) { if(o instanceof MarkerUpdated) { MarkerUpdated m = (MarkerUpdated)o; return m.id.equals(id) && m.set.equals(set); } return false; } @Override public int hashCode() { return id.hashCode() ^ set.hashCode(); } } public static class AreaMarkerUpdated extends MarkerComponentMessage { public String msg; public double ytop, ybottom; public double[] x; public double[] z; public int weight; public double opacity; public String color; public double fillopacity; public String fillcolor; public String id; public String label; public String set; public String desc; public int minzoom; public int maxzoom; public boolean markup; public AreaMarkerUpdated(AreaMarker m, boolean deleted) { this.id = m.getMarkerID(); this.label = m.getLabel(); this.ytop = m.getTopY(); this.ybottom = m.getBottomY(); int cnt = m.getCornerCount(); x = new double[cnt]; z = new double[cnt]; for(int i = 0; i < cnt; i++) { x[i] = m.getCornerX(i); z[i] = m.getCornerZ(i); } color = String.format("#%06X", m.getLineColor()); weight = m.getLineWeight(); opacity = m.getLineOpacity(); fillcolor = String.format("#%06X", m.getFillColor()); fillopacity = m.getFillOpacity(); desc = m.getDescription(); this.minzoom = m.getMinZoom(); this.maxzoom = m.getMaxZoom(); this.markup = true; // We are markup format all the time now this.set = m.getMarkerSet().getMarkerSetID(); if(deleted) msg = "areadeleted"; else msg = "areaupdated"; } @Override public boolean equals(Object o) { if(o instanceof AreaMarkerUpdated) { AreaMarkerUpdated m = (AreaMarkerUpdated)o; return m.id.equals(id) && m.set.equals(set); } return false; } @Override public int hashCode() { return id.hashCode() ^ set.hashCode(); } } public static class PolyLineMarkerUpdated extends MarkerComponentMessage { public String msg; public double[] x; public double[] y; public double[] z; public int weight; public double opacity; public String color; public String id; public String label; public String set; public String desc; public int minzoom; public int maxzoom; public boolean markup; public PolyLineMarkerUpdated(PolyLineMarker m, boolean deleted) { this.id = m.getMarkerID(); this.label = m.getLabel(); this.markup = true; // We are markup format all the time now int cnt = m.getCornerCount(); x = new double[cnt]; y = new double[cnt]; z = new double[cnt]; for(int i = 0; i < cnt; i++) { x[i] = m.getCornerX(i); y[i] = m.getCornerY(i); z[i] = m.getCornerZ(i); } color = String.format("#%06X", m.getLineColor()); weight = m.getLineWeight(); opacity = m.getLineOpacity(); desc = m.getDescription(); this.minzoom = m.getMinZoom(); this.maxzoom = m.getMaxZoom(); this.set = m.getMarkerSet().getMarkerSetID(); if(deleted) msg = "linedeleted"; else msg = "lineupdated"; } @Override public boolean equals(Object o) { if(o instanceof PolyLineMarkerUpdated) { PolyLineMarkerUpdated m = (PolyLineMarkerUpdated)o; return m.id.equals(id) && m.set.equals(set); } return false; } @Override public int hashCode() { return id.hashCode() ^ set.hashCode(); } } public static class CircleMarkerUpdated extends MarkerComponentMessage { public String msg; public double x; public double y; public double z; public double xr; public double zr; public int weight; public double opacity; public String color; public double fillopacity; public String fillcolor; public String id; public String label; public String set; public String desc; public int minzoom; public int maxzoom; public boolean markup; public CircleMarkerUpdated(CircleMarker m, boolean deleted) { this.id = m.getMarkerID(); this.label = m.getLabel(); this.x = m.getCenterX(); this.y = m.getCenterY(); this.z = m.getCenterZ(); this.xr = m.getRadiusX(); this.zr = m.getRadiusZ(); this.markup = true; // We are markup format all the time now color = String.format("#%06X", m.getLineColor()); weight = m.getLineWeight(); opacity = m.getLineOpacity(); fillcolor = String.format("#%06X", m.getFillColor()); fillopacity = m.getFillOpacity(); desc = m.getDescription(); this.minzoom = m.getMinZoom(); this.maxzoom = m.getMaxZoom(); this.set = m.getMarkerSet().getMarkerSetID(); if(deleted) msg = "circledeleted"; else msg = "circleupdated"; } @Override public boolean equals(Object o) { if(o instanceof CircleMarkerUpdated) { CircleMarkerUpdated m = (CircleMarkerUpdated)o; return m.id.equals(id) && m.set.equals(set); } return false; } @Override public int hashCode() { return id.hashCode() ^ set.hashCode(); } } public static class MarkerSetUpdated extends MarkerComponentMessage { public String msg; public String id; public String label; public int layerprio; public int minzoom; public int maxzoom; public Boolean showlabels; public MarkerSetUpdated(MarkerSet markerset, boolean deleted) { this.id = markerset.getMarkerSetID(); this.label = markerset.getMarkerSetLabel(); this.layerprio = markerset.getLayerPriority(); this.minzoom = markerset.getMinZoom(); this.maxzoom = markerset.getMaxZoom(); this.showlabels = markerset.getLabelShow(); if(deleted) msg = "setdeleted"; else msg = "setupdated"; } @Override public boolean equals(Object o) { if(o instanceof MarkerSetUpdated) { MarkerSetUpdated m = (MarkerSetUpdated)o; return m.id.equals(id); } return false; } @Override public int hashCode() { return id.hashCode(); } } private boolean stop = false; private ConcurrentHashMap dirty_worlds = new ConcurrentHashMap(); private boolean dirty_markers = false; private class DoFileWrites implements Runnable { public void run() { if(stop) return; lock.readLock().lock(); Set dirty = new HashSet(dirty_worlds.keySet()); dirty_worlds.clear(); try { /* Write markers first - drives JSON updates too */ if (dirty_markers) { doSaveMarkers(); dirty_markers = false; } /* Process any dirty worlds */ if (!dirty.isEmpty()) { for(String world : dirty) { writeMarkersFile(world); } } } finally { lock.readLock().unlock(); } core.getServer().scheduleServerTask(this, 20); } } /** * Singleton initializer * @param core - core object * @return API object */ public static MarkerAPIImpl initializeMarkerAPI(DynmapCore core) { if(api != null) { api.cleanup(core); } api = new MarkerAPIImpl(); api.core = core; /* Initialize persistence file name */ api.markerpersist = new File(core.getDataFolder(), "markers.yml"); api.markerpersist_old = new File(core.getDataFolder(), "markers.yml.old"); /* Fill in default icons and sets, if needed */ for(int i = 0; i < builtin_icons.length; i++) { String id = builtin_icons[i]; api.createBuiltinMarkerIcon(id, id); } /* Load persistence */ api.loadMarkers(); /* Initialize default marker set, if needed */ MarkerSet set = api.getMarkerSet(MarkerSet.DEFAULT); if(set == null) { set = api.createMarkerSet(MarkerSet.DEFAULT, "Markers", null, true); } /* Build paths for markers */ api.markerdir = new File(core.getDataFolder(), "markers"); if(api.markerdir.isDirectory() == false) { if(api.markerdir.mkdirs() == false) { /* Create directory if needed */ Log.severe("Error creating markers directory - " + api.markerdir.getPath()); } } return api; } /** * Singleton initializer complete (after rendder pool available * @param core - core object * @return API object */ public static void completeInitializeMarkerAPI(MarkerAPIImpl api) { MapManager.scheduleDelayedJob(new Runnable() { public void run() { /* Now publish marker files to the tiles directory */ for(MarkerIcon ico : api.getMarkerIcons()) { api.publishMarkerIcon(ico); } /* Freshen files */ api.freshenMarkerFiles(); /* Add listener so we update marker files for other worlds as they become active */ api.core.events.addListener("worldactivated", api); api.scheduleWriteJob(); /* Start write job */ Log.info("Finish marker initialization"); } }, 0); } /** * Generates a map of field:value argument tab completion suggestions for every /dmarker subcommand * This is quite long as there are a lot of arguments to deal with, and don't have Java 9 map literals */ private void initTabCompletions() { //Static values String[] emptyValue = new String[]{}; String[] booleanValue = new String[]{"true", "false"}; String[] typeValue = new String[]{"icon", "area", "line", "circle"}; Supplier emptySupplier = () -> emptyValue; Supplier booleanSupplier = () -> booleanValue; //Dynamic values Supplier iconSupplier = () -> markericons.keySet().toArray(new String[0]); Supplier markerSetSupplier = () -> markersets.keySet().toArray(new String[0]); Supplier worldSupplier = () -> core.mapManager.getWorlds().stream().map(DynmapWorld::getName).toArray(String[]::new); //Arguments used in multiple commands Map> labelArg = Collections.singletonMap("label", emptySupplier); Map> idArg = Collections.singletonMap("id", emptySupplier); Map> newLabelArg = Collections.singletonMap("newlabel", emptySupplier); Map> markerSetArg = Collections.singletonMap("set", markerSetSupplier); Map> newSetArg = Collections.singletonMap("newset", markerSetSupplier); Map> fileArg = Collections.singletonMap("file", emptySupplier); //Arguments used in commands taking a location Map> locationArgs = new LinkedHashMap<>(); locationArgs.put("x", emptySupplier); locationArgs.put("y", emptySupplier); locationArgs.put("z", emptySupplier); locationArgs.put("world", worldSupplier); //Args shared with all add/update commands Map> sharedArgs = new LinkedHashMap<>(labelArg); sharedArgs.putAll(idArg); //Args shared with all add/update commands affecting objects visible on the map Map> mapObjectArgs = new LinkedHashMap<>(sharedArgs); mapObjectArgs.put("minzoom", emptySupplier); mapObjectArgs.put("maxzoom", emptySupplier); //Args for marker set add/update commands Map> setArgs = new LinkedHashMap<>(mapObjectArgs); setArgs.put("prio", emptySupplier); setArgs.put("hide", booleanSupplier); setArgs.put("showlabel", booleanSupplier); setArgs.put("deficon", iconSupplier); //Args for marker add/update commands Map> markerArgs = new LinkedHashMap<>(mapObjectArgs); markerArgs.putAll(markerSetArg); markerArgs.put("markup", booleanSupplier); markerArgs.put("icon", iconSupplier); markerArgs.putAll(locationArgs); //Args for area/line/circle add/update commands Map> shapeArgs = new LinkedHashMap<>(mapObjectArgs); shapeArgs.putAll(markerSetArg); shapeArgs.put("markup", booleanSupplier); shapeArgs.put("weight", emptySupplier); shapeArgs.put("color", emptySupplier); shapeArgs.put("opacity", emptySupplier); //Args for area/circle add/update commands Map> filledShapeArgs = new LinkedHashMap<>(shapeArgs); filledShapeArgs.put("fillcolor", emptySupplier); filledShapeArgs.put("fillopacity", emptySupplier); filledShapeArgs.put("greeting", emptySupplier); filledShapeArgs.put("greetingsub", emptySupplier); filledShapeArgs.put("farewell", emptySupplier); filledShapeArgs.put("farewellsub", emptySupplier); filledShapeArgs.put("boost", booleanSupplier); filledShapeArgs.putAll(locationArgs); //Args for area add/update commands Map> areaArgs = new LinkedHashMap<>(filledShapeArgs); areaArgs.put("ytop", emptySupplier); areaArgs.put("ybottom", emptySupplier); //Args for circle add/update commands Map> circleArgs = new LinkedHashMap<>(filledShapeArgs); circleArgs.put("radius", emptySupplier); circleArgs.put("radiusx", emptySupplier); circleArgs.put("radiusz", emptySupplier); //Args for icon add/update commands Map> iconArgs = new LinkedHashMap<>(sharedArgs); iconArgs.putAll(fileArg); //Args for updateset command Map> updateSetArgs = new LinkedHashMap<>(setArgs); updateSetArgs.putAll(newLabelArg); //Args for update (marker) command Map> updateMarkerArgs = new LinkedHashMap<>(markerArgs); updateMarkerArgs.putAll(newLabelArg); updateMarkerArgs.putAll(newSetArg); //Args for updateline command Map> updateLineArgs = new LinkedHashMap<>(shapeArgs); updateLineArgs.putAll(newLabelArg); updateLineArgs.putAll(newSetArg); //Args for updatearea command Map> updateAreaArgs = new LinkedHashMap<>(areaArgs); updateAreaArgs.putAll(newLabelArg); updateAreaArgs.putAll(newSetArg); //Args for updatecircle command Map> updateCircleArgs = new LinkedHashMap<>(circleArgs); updateCircleArgs.putAll(newLabelArg); updateCircleArgs.putAll(newSetArg); //Args for updateicon command Map> updateIconArgs = new LinkedHashMap<>(iconArgs); updateIconArgs.putAll(newLabelArg); //Args for movehere command Map> moveHereArgs = new LinkedHashMap<>(sharedArgs); moveHereArgs.putAll(markerSetArg); //Args for marker/area/circle/line delete commands Map> deleteArgs = new LinkedHashMap<>(sharedArgs); deleteArgs.putAll(markerSetArg); //Args for label/desc commands Map> descArgs = new LinkedHashMap<>(sharedArgs); descArgs.putAll(markerSetArg); descArgs.put("type", () -> typeValue); //Args for label/desc import commands Map> importArgs = new LinkedHashMap<>(descArgs); importArgs.putAll(fileArg); //Args for appendesc command Map> appendArgs = new LinkedHashMap<>(descArgs); appendArgs.put("desc", emptySupplier); tabCompletions = new HashMap<>(); tabCompletions.put("add", markerArgs); tabCompletions.put("addicon", iconArgs); tabCompletions.put("addarea", areaArgs); tabCompletions.put("addline", shapeArgs); //No unique args tabCompletions.put("addcircle", circleArgs); tabCompletions.put("addset", setArgs); tabCompletions.put("update", updateMarkerArgs); tabCompletions.put("updateicon", updateIconArgs); tabCompletions.put("updatearea", updateAreaArgs); tabCompletions.put("updateline", updateLineArgs); tabCompletions.put("updatecircle", updateCircleArgs); tabCompletions.put("updateset", updateSetArgs); tabCompletions.put("movehere", moveHereArgs); tabCompletions.put("delete", deleteArgs); tabCompletions.put("deleteicon", sharedArgs); //Doesn't have set: arg tabCompletions.put("deletearea", deleteArgs); tabCompletions.put("deleteline", deleteArgs); tabCompletions.put("deletecircle", deleteArgs); tabCompletions.put("deleteset", sharedArgs); //Doesn't have set: arg tabCompletions.put("list", markerSetArg); tabCompletions.put("listareas", markerSetArg); tabCompletions.put("listlines", markerSetArg); tabCompletions.put("listcircles", markerSetArg); tabCompletions.put("getdesc", descArgs); tabCompletions.put("importdesc", importArgs); tabCompletions.put("resetdesc", descArgs); tabCompletions.put("getlabel", descArgs); tabCompletions.put("importlabel", importArgs); tabCompletions.put("appenddesc", appendArgs); } public void scheduleWriteJob() { core.getServer().scheduleServerTask(new DoFileWrites(), 20); } /** * Cleanup * @param plugin - core object */ public void cleanup(DynmapCore plugin) { plugin.events.removeListener("worldactivated", api); stop = true; lock.readLock().lock(); try { if(dirty_markers) { doSaveMarkers(); dirty_markers = false; } } finally { lock.readLock().unlock(); } lock.writeLock().lock(); try { for(MarkerIconImpl icn : markericons.values()) icn.cleanup(); markericons.clear(); for(MarkerSetImpl set : markersets.values()) set.cleanup(); markersets.clear(); } finally { lock.writeLock().unlock(); } } private MarkerIcon createBuiltinMarkerIcon(String id, String label) { if(markericons.containsKey(id)) return null; /* Exists? */ MarkerIconImpl ico = new MarkerIconImpl(id, label, true); markericons.put(id, ico); /* Add to set */ return ico; } void publishMarkerIcon(MarkerIcon ico) { byte[] buf = new byte[512]; InputStream in = null; File infile = new File(markerdir, ico.getMarkerIconID() + ".png"); /* Get source file name */ BufferedImage im = null; if(ico.isBuiltIn()) { in = getClass().getResourceAsStream("/markers/" + ico.getMarkerIconID() + ".png"); } else if(infile.canRead()) { /* If it exists and is readable */ try { im = ImageIO.read(infile); } catch (IOException e) { } catch (IndexOutOfBoundsException e) { } if(im != null) { MarkerIconImpl icon = (MarkerIconImpl)ico; int w = im.getWidth(); /* Get width */ if(w <= 8) { /* Small size? */ icon.setMarkerIconSize(MarkerSize.MARKER_8x8); } else if(w > 16) { icon.setMarkerIconSize(MarkerSize.MARKER_32x32); } else { icon.setMarkerIconSize(MarkerSize.MARKER_16x16); } im.flush(); } try { in = new FileInputStream(infile); } catch (IOException iox) { Log.severe("Error opening marker " + infile.getPath() + " - " + iox); } } if(in == null) { /* Not found, use default marker */ in = getClass().getResourceAsStream("/markers/marker.png"); if(in == null) { return; } } /* Copy to destination */ try { BufferOutputStream bos = new BufferOutputStream(); int len; while((len = in.read(buf)) > 0) { bos.write(buf, 0, len); } core.getDefaultMapStorage().setMarkerImage(ico.getMarkerIconID(), bos); } catch (IOException iox) { Log.severe("Error writing marker to tilespath"); } finally { if(in != null) try { in.close(); } catch (IOException x){} } } @Override public Set getMarkerSets() { return new HashSet(markersets.values()); } @Override public MarkerSet getMarkerSet(String id) { return markersets.get(id); } @Override public MarkerSet createMarkerSet(String id, String lbl, Set iconlimit, boolean persistent) { if(markersets.containsKey(id)) return null; /* Exists? */ MarkerSetImpl set = new MarkerSetImpl(id, lbl, iconlimit, persistent); markersets.put(id, set); /* Add to list */ if(persistent) { saveMarkers(); } markerSetUpdated(set, MarkerUpdate.CREATED); /* Notify update */ return set; } @Override public Set getMarkerIcons() { return new HashSet(markericons.values()); } @Override public MarkerIcon getMarkerIcon(String id) { return markericons.get(id); } boolean loadMarkerIconStream(String id, InputStream in) { /* Copy icon resource into marker directory */ File f = new File(markerdir, id + ".png"); FileOutputStream fos = null; try { byte[] buf = new byte[512]; int len; fos = new FileOutputStream(f); while((len = in.read(buf)) > 0) { fos.write(buf, 0, len); } } catch (IOException iox) { Log.severe("Error copying marker - " + f.getPath()); return false; } finally { if(fos != null) try { fos.close(); } catch (IOException x) {} } return true; } @Override public MarkerIcon createMarkerIcon(String id, String label, InputStream marker_png) { if(markericons.containsKey(id)) return null; /* Exists? */ MarkerIconImpl ico = new MarkerIconImpl(id, label, false); /* Copy icon resource into marker directory */ if(!loadMarkerIconStream(id, marker_png)) return null; markericons.put(id, ico); /* Add to set */ /* Publish the marker */ publishMarkerIcon(ico); saveMarkers(); /* Save results */ return ico; } static MarkerIconImpl getMarkerIconImpl(String id) { if(api != null) { return api.markericons.get(id); } return null; } @Override public Set getPlayerSets() { return new HashSet(playersets.values()); } @Override public PlayerSet getPlayerSet(String id) { return playersets.get(id); } @Override public PlayerSet createPlayerSet(String id, boolean symmetric, Set players, boolean persistent) { if(playersets.containsKey(id)) return null; /* Exists? */ PlayerSetImpl set = new PlayerSetImpl(id, symmetric, players, persistent); playersets.put(id, set); /* Add to list */ if(persistent) { saveMarkers(); } playerSetUpdated(set, MarkerUpdate.CREATED); /* Notify update */ return set; } /** * Save persistence for markers */ static void saveMarkers() { if(api != null) { api.dirty_markers = true; } } private void doSaveMarkers() { if(api != null) { final ConfigurationNode conf = new ConfigurationNode(api.markerpersist); /* Make configuration object */ /* First, save icon definitions */ HashMap icons = new HashMap(); conf.put("isSafe", true); // Mark as safe (sanitized) for(String id : api.markericons.keySet()) { MarkerIconImpl ico = api.markericons.get(id); Map dat = ico.getPersistentData(); if(dat != null) { icons.put(id, dat); } } conf.put("icons", icons); /* Then, save persistent sets */ HashMap sets = new HashMap(); for(String id : api.markersets.keySet()) { MarkerSetImpl set = api.markersets.get(id); if(set.isMarkerSetPersistent()) { Map dat = set.getPersistentData(); if(dat != null) { sets.put(id, dat); } } } conf.put("sets", sets); /* Then, save persistent player sets */ HashMap psets = new HashMap(); for(String id : api.playersets.keySet()) { PlayerSetImpl set = api.playersets.get(id); if(set.isPersistentSet()) { Map dat = set.getPersistentData(); if(dat != null) { psets.put(id, dat); } } } conf.put("playersets", psets); MapManager.scheduleDelayedJob(new Runnable() { public void run() { /* And shift old file file out */ if(api.markerpersist_old.exists()) api.markerpersist_old.delete(); if(api.markerpersist.exists()) api.markerpersist.renameTo(api.markerpersist_old); /* And write it out */ if(!conf.save()) Log.severe("Error writing markers - " + api.markerpersist.getPath()); } }, 0); /* Refresh JSON files */ api.freshenMarkerFiles(); } } private void freshenMarkerFiles() { if(MapManager.mapman != null) { for(DynmapWorld w : MapManager.mapman.worlds) { dirty_worlds.put(w.getName(),""); } } } /** * Load persistence */ private boolean loadMarkers() { ConfigurationNode conf = new ConfigurationNode(api.markerpersist); /* Make configuration object */ conf.load(); /* Load persistence */ lock.writeLock().lock(); boolean isSafe = conf.getBoolean("isSafe", false); try { /* Get icons */ ConfigurationNode icons = conf.getNode("icons"); if(icons == null) return false; for(String id : icons.keySet()) { MarkerIconImpl ico = new MarkerIconImpl(id); if(ico.loadPersistentData(icons.getNode(id), isSafe)) { markericons.put(id, ico); } } /* Get marker sets */ ConfigurationNode sets = conf.getNode("sets"); if(sets != null) { for(String id: sets.keySet()) { MarkerSetImpl set = new MarkerSetImpl(id); if(set.loadPersistentData(sets.getNode(id), isSafe)) { markersets.put(id, set); } } } /* Get player sets */ ConfigurationNode psets = conf.getNode("playersets"); if(psets != null) { for(String id: psets.keySet()) { PlayerSetImpl set = new PlayerSetImpl(id); if(set.loadPersistentData(sets.getNode(id), isSafe)) { playersets.put(id, set); } } } } finally { lock.writeLock().unlock(); } return true; } enum MarkerUpdate { CREATED, UPDATED, DELETED }; /** * Signal marker update * @param marker - updated marker * @param update - type of update */ static void markerUpdated(MarkerImpl marker, MarkerUpdate update) { /* Freshen marker file for the world for this marker */ if(api != null) api.dirty_worlds.put(marker.getNormalizedWorld(),""); /* Enqueue client update */ if(MapManager.mapman != null) MapManager.mapman.pushUpdate(marker.getNormalizedWorld(), new MarkerUpdated(marker, update == MarkerUpdate.DELETED)); } /** * Signal area marker update * @param marker - updated marker * @param update - type of update */ static void areaMarkerUpdated(AreaMarkerImpl marker, MarkerUpdate update) { /* Freshen marker file for the world for this marker */ if(api != null) api.dirty_worlds.put(marker.getNormalizedWorld(),""); /* Enqueue client update */ if(MapManager.mapman != null) MapManager.mapman.pushUpdate(marker.getNormalizedWorld(), new AreaMarkerUpdated(marker, update == MarkerUpdate.DELETED)); } /** * Signal poly-line marker update * @param marker - updated marker * @param update - type of update */ static void polyLineMarkerUpdated(PolyLineMarkerImpl marker, MarkerUpdate update) { /* Freshen marker file for the world for this marker */ if(api != null) api.dirty_worlds.put(marker.getNormalizedWorld(),""); /* Enqueue client update */ if(MapManager.mapman != null) MapManager.mapman.pushUpdate(marker.getNormalizedWorld(), new PolyLineMarkerUpdated(marker, update == MarkerUpdate.DELETED)); } /** * Signal circle marker update * @param marker - updated marker * @param update - type of update */ static void circleMarkerUpdated(CircleMarkerImpl marker, MarkerUpdate update) { /* Freshen marker file for the world for this marker */ if(api != null) api.dirty_worlds.put(marker.getNormalizedWorld(),""); /* Enqueue client update */ if(MapManager.mapman != null) MapManager.mapman.pushUpdate(marker.getNormalizedWorld(), new CircleMarkerUpdated(marker, update == MarkerUpdate.DELETED)); } /** * Signal marker set update * @param markerset - updated marker set * @param update - type of update */ static void markerSetUpdated(MarkerSetImpl markerset, MarkerUpdate update) { /* Freshen all marker files */ if(api != null) api.freshenMarkerFiles(); /* Enqueue client update */ if(MapManager.mapman != null) MapManager.mapman.pushUpdate(new MarkerSetUpdated(markerset, update == MarkerUpdate.DELETED)); } /** * Signal player set update * @param playerset - updated player set * @param update - type of update */ static void playerSetUpdated(PlayerSetImpl pset, MarkerUpdate update) { if(api != null) api.core.events.trigger("playersetupdated", null); } /** * Remove marker set */ static void removeMarkerSet(MarkerSetImpl markerset) { if(api != null) { api.markersets.remove(markerset.getMarkerSetID()); /* Remove set from list */ if(markerset.isMarkerSetPersistent()) { /* If persistent */ MarkerAPIImpl.saveMarkers(); /* Drive save */ } markerSetUpdated(markerset, MarkerUpdate.DELETED); /* Signal delete of set */ } } /** * Remove player set */ static void removePlayerSet(PlayerSetImpl pset) { if(api != null) { api.playersets.remove(pset.getSetID()); /* Remove set from list */ if(pset.isPersistentSet()) { /* If persistent */ MarkerAPIImpl.saveMarkers(); /* Drive save */ } playerSetUpdated(pset, MarkerUpdate.DELETED); /* Signal delete of set */ } } private static boolean processAreaArgs(DynmapCommandSender sender, AreaMarker marker, Map parms) { String val = null; String val2 = null; try { double ytop = marker.getTopY(); double ybottom = marker.getBottomY(); int scolor = marker.getLineColor(); int fcolor = marker.getFillColor(); double sopacity = marker.getLineOpacity(); double fopacity = marker.getFillOpacity(); int sweight = marker.getLineWeight(); boolean boost = marker.getBoostFlag(); int minzoom = marker.getMinZoom(); int maxzoom = marker.getMaxZoom(); EnterExitText greet = marker.getGreetingText(); EnterExitText farew = marker.getFarewellText(); val = parms.get(ARG_STROKECOLOR); if(val != null) scolor = Integer.parseInt(val, 16); val = parms.get(ARG_FILLCOLOR); if(val != null) fcolor = Integer.parseInt(val, 16); val = parms.get(ARG_STROKEOPACITY); if(val != null) sopacity = Double.parseDouble(val); val = parms.get(ARG_FILLOPACITY); if(val != null) fopacity = Double.parseDouble(val); val = parms.get(ARG_STROKEWEIGHT); if(val != null) sweight = Integer.parseInt(val); val = parms.get(ARG_YTOP); if(val != null) ytop = Double.parseDouble(val); val = parms.get(ARG_YBOTTOM); if(val != null) ybottom = Double.parseDouble(val); val = parms.get(ARG_MINZOOM); if (val != null) minzoom = Integer.parseInt(val); val = parms.get(ARG_MAXZOOM); if (val != null) maxzoom = Integer.parseInt(val); val = parms.get(ARG_BOOST); if(val != null) { if(api.core.checkPlayerPermission(sender, "marker.boost")) { boost = val.equals("true"); } else { sender.sendMessage("No permission to set boost flag"); return false; } } marker.setLineStyle(sweight, sopacity, scolor); marker.setFillStyle(fopacity, fcolor); if(ytop >= ybottom) marker.setRangeY(ytop, ybottom); else marker.setRangeY(ybottom, ytop); marker.setBoostFlag(boost); marker.setMinZoom(minzoom); marker.setMaxZoom(maxzoom); // Handle greeting val = parms.get(ARG_GREETING); val2 = parms.get(ARG_GREETINGSUB); if ((val != null) || (val2 != null)) { String title = (val != null) ? ((val.length() > 0) ? val : null) : ((greet != null) ? greet.title : null); String subtitle = (val2 != null) ? ((val2.length() > 0) ? val2 : null) : ((greet != null) ? greet.subtitle : null); marker.setGreetingText(title, subtitle); } // Handle farewell val = parms.get(ARG_FAREWELL); val2 = parms.get(ARG_FAREWELLSUB); if ((val != null) || (val2 != null)) { String title = (val != null) ? ((val.length() > 0) ? val : null) : ((farew != null) ? farew.title : null); String subtitle = (val2 != null) ? ((val2.length() > 0) ? val2 : null) : ((farew != null) ? farew.subtitle : null); marker.setFarewellText(title, subtitle); } } catch (NumberFormatException nfx) { sender.sendMessage("Invalid parameter format: " + val); return false; } return true; } private static boolean processPolyArgs(DynmapCommandSender sender, PolyLineMarker marker, Map parms) { String val = null; try { int scolor = marker.getLineColor(); double sopacity = marker.getLineOpacity(); int sweight = marker.getLineWeight(); int minzoom = marker.getMinZoom(); int maxzoom = marker.getMaxZoom(); val = parms.get(ARG_STROKECOLOR); if(val != null) scolor = Integer.parseInt(val, 16); val = parms.get(ARG_STROKEOPACITY); if(val != null) sopacity = Double.parseDouble(val); val = parms.get(ARG_STROKEWEIGHT); if(val != null) sweight = Integer.parseInt(val); val = parms.get(ARG_MINZOOM); if(val != null) minzoom = Integer.parseInt(val); val = parms.get(ARG_MAXZOOM); if(val != null) maxzoom = Integer.parseInt(val); marker.setLineStyle(sweight, sopacity, scolor); marker.setMinZoom(minzoom); marker.setMaxZoom(maxzoom); } catch (NumberFormatException nfx) { sender.sendMessage("Invalid parameter format: " + val); return false; } return true; } private static boolean processCircleArgs(DynmapCommandSender sender, CircleMarker marker, Map parms) { String val = null, val2 = null; try { int scolor = marker.getLineColor(); int fcolor = marker.getFillColor(); double sopacity = marker.getLineOpacity(); double fopacity = marker.getFillOpacity(); int sweight = marker.getLineWeight(); double xr = marker.getRadiusX(); double zr = marker.getRadiusZ(); double x = marker.getCenterX(); double y = marker.getCenterY(); double z = marker.getCenterZ(); String world = marker.getWorld(); boolean boost = marker.getBoostFlag(); int minzoom = marker.getMinZoom(); int maxzoom = marker.getMaxZoom(); EnterExitText greet = marker.getGreetingText(); EnterExitText farew = marker.getFarewellText(); val = parms.get(ARG_STROKECOLOR); if(val != null) scolor = Integer.parseInt(val, 16); val = parms.get(ARG_FILLCOLOR); if(val != null) fcolor = Integer.parseInt(val, 16); val = parms.get(ARG_STROKEOPACITY); if(val != null) sopacity = Double.parseDouble(val); val = parms.get(ARG_FILLOPACITY); if(val != null) fopacity = Double.parseDouble(val); val = parms.get(ARG_STROKEWEIGHT); if(val != null) sweight = Integer.parseInt(val); val = parms.get(ARG_X); if(val != null) x = Double.parseDouble(val); val = parms.get(ARG_Y); if(val != null) y = Double.parseDouble(val); val = parms.get(ARG_Z); if(val != null) z = Double.parseDouble(val); val = parms.get(ARG_WORLD); if(val != null) world = val; val = parms.get(ARG_RADIUSX); if(val != null) xr = Double.parseDouble(val); val = parms.get(ARG_RADIUSZ); if(val != null) zr = Double.parseDouble(val); val = parms.get(ARG_RADIUS); if(val != null) xr = zr = Double.parseDouble(val); val = parms.get(ARG_BOOST); if(val != null) { if(api.core.checkPlayerPermission(sender, "marker.boost")) { boost = val.equals("true"); } else { sender.sendMessage("No permission to set boost flag"); return false; } } val = parms.get(ARG_MINZOOM); if (val != null) { minzoom = Integer.parseInt(val); } val = parms.get(ARG_MAXZOOM); if (val != null) { maxzoom = Integer.parseInt(val); } marker.setCenter(world, x, y, z); marker.setLineStyle(sweight, sopacity, scolor); marker.setFillStyle(fopacity, fcolor); marker.setRadius(xr, zr); marker.setBoostFlag(boost); marker.setMinZoom(minzoom); marker.setMaxZoom(maxzoom); // Handle greeting val = parms.get(ARG_GREETING); val2 = parms.get(ARG_GREETINGSUB); if ((val != null) || (val2 != null)) { String title = (val != null) ? ((val.length() > 0) ? val : null) : ((greet != null) ? greet.title : null); String subtitle = (val2 != null) ? ((val2.length() > 0) ? val2 : null) : ((greet != null) ? greet.subtitle : null); marker.setGreetingText(title, subtitle); } // Handle farewell val = parms.get(ARG_FAREWELL); val2 = parms.get(ARG_FAREWELLSUB); if ((val != null) || (val2 != null)) { String title = (val != null) ? ((val.length() > 0) ? val : null) : ((farew != null) ? farew.title : null); String subtitle = (val2 != null) ? ((val2.length() > 0) ? val2 : null) : ((farew != null) ? farew.subtitle : null); marker.setFarewellText(title, subtitle); } } catch (NumberFormatException nfx) { sender.sendMessage("Invalid parameter format: " + val); return false; } return true; } private static final Set commands = new HashSet(Arrays.asList(new String[] { "add", "movehere", "update", "delete", "list", "icons", "addset", "updateset", "deleteset", "listsets", "addicon", "updateicon", "deleteicon", "addcorner", "clearcorners", "addarea", "listareas", "deletearea", "updatearea", "addline", "listlines", "deleteline", "updateline", "addcircle", "listcircles", "deletecircle", "updatecircle", "getdesc", "resetdesc", "appenddesc", "importdesc", "getlabel", "importlabel" })); private static final String ARG_LABEL = "label"; private static final String ARG_MARKUP = "markup"; private static final String ARG_ID = "id"; private static final String ARG_TYPE = "type"; private static final String ARG_NEWLABEL = "newlabel"; private static final String ARG_FILE = "file"; private static final String ARG_HIDE = "hide"; private static final String ARG_ICON = "icon"; private static final String ARG_DEFICON = "deficon"; private static final String ARG_SET = "set"; private static final String ARG_NEWSET = "newset"; private static final String ARG_PRIO = "prio"; private static final String ARG_MINZOOM = "minzoom"; private static final String ARG_MAXZOOM = "maxzoom"; private static final String ARG_STROKEWEIGHT = "weight"; private static final String ARG_STROKECOLOR = "color"; private static final String ARG_STROKEOPACITY = "opacity"; private static final String ARG_FILLCOLOR = "fillcolor"; private static final String ARG_FILLOPACITY = "fillopacity"; private static final String ARG_YTOP = "ytop"; private static final String ARG_YBOTTOM = "ybottom"; private static final String ARG_RADIUSX = "radiusx"; private static final String ARG_RADIUSZ = "radiusz"; private static final String ARG_RADIUS = "radius"; private static final String ARG_SHOWLABEL = "showlabels"; private static final String ARG_X = "x"; private static final String ARG_Y = "y"; private static final String ARG_Z = "z"; private static final String ARG_WORLD = "world"; private static final String ARG_BOOST = "boost"; private static final String ARG_DESC = "desc"; private static final String ARG_GREETING = "greeting"; private static final String ARG_GREETINGSUB = "greetingsub"; private static final String ARG_FAREWELL = "farewell"; private static final String ARG_FAREWELLSUB = "farewellsub"; /* Parse argument strings : handle 'attrib:value' and quoted strings */ private static Map parseArgs(String[] args, DynmapCommandSender snd) { HashMap rslt = new HashMap(); /* Build command line, so we can parse our way - make sure there is trailing space */ String cmdline = ""; for(int i = 1; i < args.length; i++) { cmdline += args[i] + " "; } boolean inquote = false; StringBuilder sb = new StringBuilder(); String varid = null; for(int i = 0; i < cmdline.length(); i++) { char c = cmdline.charAt(i); if(inquote) { /* If in quote, accumulate until end or another quote */ if(c == '\"') { /* End quote */ inquote = false; if(varid == null) { /* No varid? */ rslt.put(ARG_LABEL, sb.toString()); } else { rslt.put(varid, sb.toString()); varid = null; } sb.setLength(0); } else { sb.append(c); } } else if(c == '\"') { /* Start of quote? */ inquote = true; } else if(c == ':') { /* var:value */ varid = sb.toString(); /* Save variable ID */ sb.setLength(0); } else if(c == ' ') { /* Ending space? */ if(varid == null) { /* No varid? */ if(sb.length() > 0) { rslt.put(ARG_LABEL, sb.toString()); } } else { rslt.put(varid, sb.toString()); varid = null; } sb.setLength(0); } else { sb.append(c); } } if(inquote) { /* If still in quote, syntax error */ snd.sendMessage("Error: unclosed doublequote"); return null; } return rslt; } public static boolean onCommand(DynmapCore plugin, DynmapCommandSender sender, String cmd, String commandLabel, String[] args) { if(api == null) { sender.sendMessage("Markers component is not enabled."); return false; } if(args.length == 0) return false; DynmapPlayer player = null; if (sender instanceof DynmapPlayer) player = (DynmapPlayer) sender; /* Check if valid command */ String c = args[0]; if (!commands.contains(c)) { return false; } /* Process commands read commands */ api.lock.readLock().lock(); try { /* List markers */ if(c.equals("list") && plugin.checkPlayerPermission(sender, "marker.list")) { return processListMarker(plugin, sender, cmd, commandLabel, args); } /* List icons */ else if(c.equals("icons") && plugin.checkPlayerPermission(sender, "marker.icons")) { return processListIcon(plugin, sender, cmd, commandLabel, args); } /* List sets */ else if(c.equals("listsets") && plugin.checkPlayerPermission(sender, "marker.listsets")) { return processListSet(plugin, sender, cmd, commandLabel, args); } /* List areas */ else if(c.equals("listareas") && plugin.checkPlayerPermission(sender, "marker.listareas")) { return processListArea(plugin, sender, cmd, commandLabel, args); } /* List poly-lines */ else if(c.equals("listlines") && plugin.checkPlayerPermission(sender, "marker.listlines")) { return processListLine(plugin, sender, cmd, commandLabel, args); } /* List circles */ else if(c.equals("listcircles") && plugin.checkPlayerPermission(sender, "marker.listcircles")) { return processListCircle(plugin, sender, cmd, commandLabel, args); } /* Get label for given item - must have ID and type parameter */ else if(c.equals("getlabel") && plugin.checkPlayerPermission(sender, "marker.getlabel")) { return processGetLabel(plugin, sender, cmd, commandLabel, args); } } finally { api.lock.readLock().unlock(); } // Handle modify commands api.lock.writeLock().lock(); try { if(c.equals("add") && api.core.checkPlayerPermission(sender, "marker.add")) { return processAddMarker(plugin, sender, cmd, commandLabel, args, player); } /* Update position of bookmark - must have ID parameter */ else if(c.equals("movehere") && plugin.checkPlayerPermission(sender, "marker.movehere")) { return processMoveHere(plugin, sender, cmd, commandLabel, args, player); } /* Update other attributes of marker - must have ID parameter */ else if(c.equals("update") && plugin.checkPlayerPermission(sender, "marker.update")) { return processUpdateMarker(plugin, sender, cmd, commandLabel, args); } /* Delete marker - must have ID parameter */ else if(c.equals("delete") && plugin.checkPlayerPermission(sender, "marker.delete")) { return processDeleteMarker(plugin, sender, cmd, commandLabel, args); } else if(c.equals("addset") && plugin.checkPlayerPermission(sender, "marker.addset")) { return processAddSet(plugin, sender, cmd, commandLabel, args, player); } else if(c.equals("updateset") && plugin.checkPlayerPermission(sender, "marker.updateset")) { return processUpdateSet(plugin, sender, cmd, commandLabel, args); } else if(c.equals("deleteset") && plugin.checkPlayerPermission(sender, "marker.deleteset")) { return processDeleteSet(plugin, sender, cmd, commandLabel, args); } /* Add new icon */ else if(c.equals("addicon") && plugin.checkPlayerPermission(sender, "marker.addicon")) { return processAddIcon(plugin, sender, cmd, commandLabel, args); } else if(c.equals("updateicon") && plugin.checkPlayerPermission(sender, "marker.updateicon")) { return processUpdateIcon(plugin, sender, cmd, commandLabel, args); } else if(c.equals("deleteicon") && plugin.checkPlayerPermission(sender, "marker.deleteicon")) { return processDeleteIcon(plugin, sender, cmd, commandLabel, args); } /* Add point to accumulator */ else if(c.equals("addcorner") && plugin.checkPlayerPermission(sender, "marker.addarea")) { return processAddCorner(plugin, sender, cmd, commandLabel, args, player); } else if(c.equals("clearcorners") && plugin.checkPlayerPermission(sender, "marker.addarea")) { return processClearCorners(plugin, sender, cmd, commandLabel, args, player); } else if(c.equals("addarea") && plugin.checkPlayerPermission(sender, "marker.addarea")) { return processAddArea(plugin, sender, cmd, commandLabel, args, player); } /* Delete area - must have ID parameter */ else if(c.equals("deletearea") && plugin.checkPlayerPermission(sender, "marker.deletearea")) { return processDeleteArea(plugin, sender, cmd, commandLabel, args); } /* Update other attributes of area - must have ID parameter */ else if(c.equals("updatearea") && plugin.checkPlayerPermission(sender, "marker.updatearea")) { return processUpdateArea(plugin, sender, cmd, commandLabel, args); } else if(c.equals("addline") && plugin.checkPlayerPermission(sender, "marker.addline")) { return processAddLine(plugin, sender, cmd, commandLabel, args, player); } /* Delete poly-line - must have ID parameter */ else if(c.equals("deleteline") && plugin.checkPlayerPermission(sender, "marker.deleteline")) { return processDeleteLine(plugin, sender, cmd, commandLabel, args); } /* Update other attributes of poly-line - must have ID parameter */ else if(c.equals("updateline") && plugin.checkPlayerPermission(sender, "marker.updateline")) { return processUpdateLine(plugin, sender, cmd, commandLabel, args); } else if(c.equals("addcircle") && plugin.checkPlayerPermission(sender, "marker.addcircle")) { return processAddCircle(plugin, sender, cmd, commandLabel, args, player); } /* Delete circle - must have ID parameter */ else if(c.equals("deletecircle") && plugin.checkPlayerPermission(sender, "marker.deletecircle")) { return processDeleteCircle(plugin, sender, cmd, commandLabel, args); } /* Update other attributes of circle - must have ID parameter */ else if(c.equals("updatecircle") && plugin.checkPlayerPermission(sender, "marker.updatecircle")) { return processUpdateCircle(plugin, sender, cmd, commandLabel, args); } /* Get description for given item - must have ID and type parameter */ else if(c.equals("getdesc") && plugin.checkPlayerPermission(sender, "marker.getdesc")) { return processGetDesc(plugin, sender, cmd, commandLabel, args); } /* Reset description for given item - must have ID and type parameter */ else if(c.equals("resetdesc") && plugin.checkPlayerPermission(sender, "marker.resetdesc")) { return processResetDesc(plugin, sender, cmd, commandLabel, args); } /* Append to description for given item - must have ID and type parameter */ else if(c.equals("appenddesc") && plugin.checkPlayerPermission(sender, "marker.appenddesc")) { return processAppendDesc(plugin, sender, cmd, commandLabel, args); } /* Import description for given item from file - must have ID and type parameter */ else if(c.equals("importdesc") && plugin.checkPlayerPermission(sender, "marker.importdesc")) { return processImportDesc(plugin, sender, cmd, commandLabel, args); } /* Import description for given item from file - must have ID and type parameter */ else if(c.equals("importlabel") && plugin.checkPlayerPermission(sender, "marker.importlabel")) { return processImportLabel(plugin, sender, cmd, commandLabel, args); } else { return false; } } finally { api.lock.writeLock().unlock(); } } public List getTabCompletions(DynmapCommandSender sender, String[] args, DynmapCore core) { /* Re-parse args - handle doublequotes */ args = DynmapCore.parseArgs(args, sender, true); if (args == null || args.length <= 1) { return Collections.emptyList(); } if (tabCompletions == null) { initTabCompletions(); } String cmd = args[0]; if (cmd.equals("addcorner") && core.checkPlayerPermission(sender, "marker.addarea")) { if (args.length == 5) { return core.getWorldSuggestions(args[4]); } } else if (core.checkPlayerPermission(sender, "marker." + cmd) && tabCompletions.containsKey(cmd)) { return core.getFieldValueSuggestions(args, tabCompletions.get(cmd)); } return Collections.emptyList(); } private static boolean processAddMarker(DynmapCore plugin, DynmapCommandSender sender, String cmd, String commandLabel, String[] args, DynmapPlayer player) { String id, setid, label, iconid, markup; String x, y, z, world, normalized_world; if(args.length > 1) { /* Parse arguements */ Map parms = parseArgs(args, sender); if(parms == null) return true; iconid = parms.get(ARG_ICON); setid = parms.get(ARG_SET); id = parms.get(ARG_ID); label = parms.get(ARG_LABEL); markup = parms.get(ARG_MARKUP); x = parms.get(ARG_X); y = parms.get(ARG_Y); z = parms.get(ARG_Z); String minzoom = parms.get(ARG_MINZOOM); int min_zoom = -1; if (minzoom != null) { try { min_zoom = Integer.parseInt(minzoom); } catch (NumberFormatException nfx) { sender.sendMessage("Invalid minzoom: " + minzoom); return true; } } String maxzoom = parms.get(ARG_MAXZOOM); int max_zoom = -1; if (maxzoom != null) { try { max_zoom = Integer.parseInt(maxzoom); } catch (NumberFormatException nfx) { sender.sendMessage("Invalid maxzoom: " + maxzoom); return true; } } world = DynmapWorld.normalizeWorldName(parms.get(ARG_WORLD)); if(world != null) { normalized_world = DynmapWorld.normalizeWorldName(world); if(api.core.getWorld(normalized_world) == null) { sender.sendMessage("Invalid world ID: " + world); return true; } } DynmapLocation loc = null; if((x == null) && (y == null) && (z == null) && (world == null)) { if(player == null) { sender.sendMessage("Must be issued by player, or x, y, z, and world parameters are required"); return true; } loc = player.getLocation(); } else if((x != null) && (y != null) && (z != null) && (world != null)) { try { loc = new DynmapLocation(world, Double.valueOf(x), Double.valueOf(y), Double.valueOf(z)); } catch (NumberFormatException nfx) { sender.sendMessage("Coordinates x, y, and z must be numbers"); return true; } } else { sender.sendMessage("Must be issued by player, or x, y, z, and world parameters are required"); return true; } /* Fill in defaults for missing parameters */ if(setid == null) { setid = MarkerSet.DEFAULT; } /* Add new marker */ MarkerSet set = api.getMarkerSet(setid); if(set == null) { sender.sendMessage("Error: invalid set - " + setid); return true; } // Prevent adding persistent markers to a non-persistent set if (!set.isMarkerSetPersistent()) { sender.sendMessage("Error: cannot add to non-persistent marker set - set is likely plugin owned"); return true; } MarkerIcon ico = null; if(iconid == null) { ico = set.getDefaultMarkerIcon(); } if(ico == null) { if(iconid == null) { iconid = MarkerIcon.DEFAULT; } ico = api.getMarkerIcon(iconid); } if(ico == null) { sender.sendMessage("Error: invalid icon - " + iconid); return true; } if (minzoom != null) { } boolean isMarkup = "true".equals(markup); Marker m = set.createMarker(id, label, isMarkup, loc.world, loc.x, loc.y, loc.z, ico, true); if(m == null) { sender.sendMessage("Error creating marker"); } else { if (min_zoom >= 0) { m.setMinZoom(min_zoom); } if (max_zoom >= 0) { m.setMaxZoom(max_zoom); } sender.sendMessage("Added marker id:'" + m.getMarkerID() + "' (" + m.getLabel() + ") to set '" + set.getMarkerSetID() + "'"); } } else { sender.sendMessage("Marker label required"); } return true; } private static boolean processMoveHere(DynmapCore plugin, DynmapCommandSender sender, String cmd, String commandLabel, String[] args, DynmapPlayer player) { String id, label, setid; if(player == null) { sender.sendMessage("Command can only be used by player"); } else if(args.length > 1) { /* Parse arguements */ Map parms = parseArgs(args, sender); if(parms == null) return true; id = parms.get(ARG_ID); label = parms.get(ARG_LABEL); setid = parms.get(ARG_SET); if((id == null) && (label == null)) { sender.sendMessage("