diff --git a/src/main/java/org/dynmap/markers/AreaMarker.java b/src/main/java/org/dynmap/markers/AreaMarker.java new file mode 100644 index 00000000..9cdb0248 --- /dev/null +++ b/src/main/java/org/dynmap/markers/AreaMarker.java @@ -0,0 +1,123 @@ +package org.dynmap.markers; + +/** + * This defines the public interface to an area marker object, for use with the MarkerAPI + */ +public interface AreaMarker extends GenericMarker { + /** + * Get the marker's label + */ + public String getLabel(); + /** + * Update the marker's label (plain text) + */ + public void setLabel(String lbl); + /** + * Update the marker's label and markup flag + * @param label - label string + * @param markup - if true, label is processed as HTML (innerHTML for used for label); false implies plaintext + */ + public void setLabel(String lbl, boolean markup); + /** + * Test if marker label is processed as HTML + */ + public boolean isLabelMarkup(); + /** + * Set marker description (HTML markup shown in popup when clicked) + * @param desc - HTML markup description + */ + public void setDescription(String desc); + /** + * Get marker description + * @return descrption + */ + public String getDescription(); + /** + * Get top Y coordinate + * @return coordinate + */ + public double getTopY(); + /** + * Get bottom Y coordinate + * @return coordinate + */ + public double getBottomY(); + /** + * Set Y coordinate range + * @param ytop - y coordinate of top + * @param ybottom - y coordinate of bottom (=top for 2D) + */ + public void setRangeY(double ytop, double ybottom); + /** + * Get corner location count + */ + public int getCornerCount(); + /** + * Get X coordinate of corner N + * @param n - corner index + * @return coordinate + */ + public double getCornerX(int n); + /** + * Get Z coordinate of corner N + * @param n - corner index + * @return coordinate + */ + public double getCornerZ(int n); + /** + * Set coordinates of corner N + * @param n - index of corner: append new corner if >= corner count, else replace existing + * @param x - x coordinate + * @param z - z coordinate + */ + public void setCornerLocation(int n, double x, double z); + /** + * Set/replace all corners + * @param x - list of x coordinates + * @param z - list of z coordinates + */ + public void setCornerLocations(double[] x, double[] z); + /** + * Delete corner N - shift corners after N forward + * @param n - index of corner + */ + public void deleteCorner(int n); + /** + * Set line style + * @param weight - stroke weight + * @param opacity - stroke opacity + * @param color - stroke color (0xRRGGBB) + */ + public void setLineStyle(int weight, double opacity, int color); + /** + * Get line weight + * @return weight + */ + public int getLineWeight(); + /** + * Get line opacity + * @return opacity (0.0-1.0) + */ + public double getLineOpacity(); + /** + * Get line color + * @return color (0xRRGGBB) + */ + public int getLineColor(); + /** + * Set fill style + * @param opacity - fill color opacity + * @param color - fill color (0xRRGGBB) + */ + public void setFillStyle(double opacity, int color); + /** + * Get fill opacity + * @return opacity (0.0-1.0) + */ + public double getFillOpacity(); + /** + * Get fill color + * @return color (0xRRGGBB) + */ + public int getFillColor(); +} diff --git a/src/main/java/org/dynmap/markers/GenericMarker.java b/src/main/java/org/dynmap/markers/GenericMarker.java new file mode 100644 index 00000000..2c45cb12 --- /dev/null +++ b/src/main/java/org/dynmap/markers/GenericMarker.java @@ -0,0 +1,32 @@ +package org.dynmap.markers; + +import org.bukkit.Location; + +/** + * This defines the public interface to a generic marker object, for use with the MarkerAPI + */ +public interface GenericMarker { + /** + * Get ID of the marker (unique string within the MarkerSet) + * @return id of marker + */ + public String getMarkerID(); + /** + * Get the marker set for the marker + * @return marker set + */ + public MarkerSet getMarkerSet(); + /** + * Delete the marker + */ + public void deleteMarker(); + /** + * Get marker's world ID + * @return world id + */ + public String getWorld(); + /** + * Test if marker is persistent + */ + public boolean isPersistentMarker(); +} diff --git a/src/main/java/org/dynmap/markers/Marker.java b/src/main/java/org/dynmap/markers/Marker.java index 754a56fd..e902994f 100644 --- a/src/main/java/org/dynmap/markers/Marker.java +++ b/src/main/java/org/dynmap/markers/Marker.java @@ -5,26 +5,7 @@ import org.bukkit.Location; /** * This defines the public interface to a marker object, for use with the MarkerAPI */ -public interface Marker { - /** - * Get ID of the marker (unique string within the MarkerSet) - * @return id of marker - */ - public String getMarkerID(); - /** - * Get the marker set for the marker - * @return marker set - */ - public MarkerSet getMarkerSet(); - /** - * Delete the marker - */ - public void deleteMarker(); - /** - * Get marker's world ID - * @return world id - */ - public String getWorld(); +public interface Marker extends GenericMarker { /** * Get marker's X coordinate * @return x coordinate @@ -59,10 +40,6 @@ public interface Marker { * @return true if new marker icon set, false if not allowed */ public boolean setMarkerIcon(MarkerIcon icon); - /** - * Test if marker is persistent - */ - public boolean isPersistentMarker(); /** * Get the marker's label */ diff --git a/src/main/java/org/dynmap/markers/MarkerSet.java b/src/main/java/org/dynmap/markers/MarkerSet.java index 490f535d..e5667fac 100644 --- a/src/main/java/org/dynmap/markers/MarkerSet.java +++ b/src/main/java/org/dynmap/markers/MarkerSet.java @@ -15,6 +15,11 @@ public interface MarkerSet { * @return set of markers (set is copy - safe to iterate) */ public Set getMarkers(); + /** + * Get set of all area markers currently in the set + * @return set of area markers (set is copy - safe to iterate) + */ + public Set getAreaMarkers(); /** * Create a new marker in the marker set * @@ -56,6 +61,31 @@ public interface MarkerSet { * @return marker, or null if none found */ public Marker findMarkerByLabel(String lbl); + /** + * Create area marker + * @param id - marker ID + * @param lbl - label + * @param markup - if true, label is HTML markup + * @param world - world id + * @param x - x coord list + * @param z - z coord list + * @param ytop - top y coordinate + * @param ybottom - bottom y coordinate + * @param persistent - true if persistent + */ + public AreaMarker createAreaMarker(String id, String lbl, boolean markup, String world, double x[], double z[], double ytop, double ybottom, boolean persistent); + /** + * Get area marker by ID + * @param id - ID of the area marker + * @return marker, or null if cannot be found + */ + public AreaMarker findAreaMarker(String id); + /** + * Find area marker by label - best matching substring + * @param lbl - label to find (same = best match) + * @return marker, or null if none found + */ + public AreaMarker findAreaMarkerByLabel(String lbl); /** * Get ID of marker set - unique among marker sets * @return ID diff --git a/src/main/java/org/dynmap/markers/impl/AreaMarkerImpl.java b/src/main/java/org/dynmap/markers/impl/AreaMarkerImpl.java new file mode 100644 index 00000000..73ec0db3 --- /dev/null +++ b/src/main/java/org/dynmap/markers/impl/AreaMarkerImpl.java @@ -0,0 +1,326 @@ +package org.dynmap.markers.impl; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.bukkit.Location; +import org.bukkit.util.config.ConfigurationNode; +import org.dynmap.markers.AreaMarker; +import org.dynmap.markers.MarkerIcon; +import org.dynmap.markers.MarkerSet; +import org.dynmap.markers.impl.MarkerAPIImpl.MarkerUpdate; + +class AreaMarkerImpl implements AreaMarker { + private String markerid; + private String label; + private boolean markup; + private String desc; + private MarkerSetImpl markerset; + private String world; + private boolean ispersistent; + private ArrayList corners; + private int lineweight = 3; + private double lineopacity = 0.8; + private int linecolor = 0xFF0000; + private double fillopacity = 0.35; + private int fillcolor = 0xFF0000; + private double ytop = 64.0; + private double ybottom = 64.0; + + private static class Coord { + double x, z; + Coord(double x, double z) { + this.x = x; this.z = z; + } + } + + /** + * Create area marker + * @param id - marker ID + * @param lbl - label + * @param markup - if true, label is HTML markup + * @param world - world id + * @param x - x coord list + * @param z - z coord list + * @param ytop - top y coordinate + * @param ybottom - bottom y coordinate + * @param persistent - true if persistent + * @param set - marker set + */ + AreaMarkerImpl(String id, String lbl, boolean markup, String world, double x[], double z[], double ytop, double ybottom, boolean persistent, MarkerSetImpl set) { + markerid = id; + if(lbl != null) + label = lbl; + else + label = id; + this.markup = markup; + this.corners = new ArrayList(); + for(int i = 0; i < x.length; i++) { + this.corners.add(new Coord(x[i], z[i])); + } + this.ytop = ytop; + this.ybottom = ybottom; + this.world = world; + this.desc = null; + ispersistent = persistent; + markerset = set; + } + /** + * Make bare area marker - used for persistence load + * @param id - marker ID + * @param set - marker set + */ + AreaMarkerImpl(String id, MarkerSetImpl set) { + markerid = id; + markerset = set; + label = id; + markup = false; + desc = null; + corners = new ArrayList(); + world = "world"; + } + /** + * Load marker from configuration node + * @param node - configuration node + */ + boolean loadPersistentData(ConfigurationNode node) { + label = node.getString("label", markerid); + markup = node.getBoolean("markup", false); + ytop = node.getDouble("ytop", 64.0); + ybottom = node.getDouble("ybottom", 64.0); + List xx = node.getDoubleList("x", null); + List zz = node.getDoubleList("z", null); + corners.clear(); + if((xx != null) && (zz != null)) { + for(int i = 0; (i < xx.size()) && (i < zz.size()); i++) + corners.add(new Coord(xx.get(i), zz.get(i))); + } + world = node.getString("world", "world"); + desc = node.getString("desc", null); + lineweight = node.getInt("strokeWeight", 3); + lineopacity = node.getDouble("strokeOpacity", 0.8); + linecolor = node.getInt("strokeColor", 0xFF0000); + fillopacity = node.getDouble("fillOpacity", 0.35); + fillcolor = node.getInt("fillColor", 0xFF0000); + ispersistent = true; /* Loaded from config, so must be */ + + return true; + } + + void cleanup() { + corners.clear(); + markerset = null; + } + + @Override + public String getMarkerID() { + return markerid; + } + + @Override + public MarkerSet getMarkerSet() { + return markerset; + } + + @Override + public void deleteMarker() { + markerset.removeAreaMarker(this); /* Remove from our marker set (notified by set) */ + cleanup(); + } + + @Override + public boolean isPersistentMarker() { + return ispersistent; + } + + @Override + public String getLabel() { + return label; + } + + @Override + public void setLabel(String lbl) { + setLabel(lbl, false); + } + + @Override + public void setLabel(String lbl, boolean markup) { + label = lbl; + this.markup = markup; + MarkerAPIImpl.areaMarkerUpdated(this, MarkerUpdate.UPDATED); + if(ispersistent) + MarkerAPIImpl.saveMarkers(); + } + + /** + * Get configuration node to be saved + * @return node + */ + Map getPersistentData() { + if(!ispersistent) /* Nothing if not persistent */ + return null; + HashMap node = new HashMap(); + node.put("label", label); + node.put("markup", markup); + double[] xx = new double[corners.size()]; + double[] zz = new double[corners.size()]; + for(int i = 0; i < xx.length; i++) { + xx[i] = corners.get(i).x; + zz[i] = corners.get(i).z; + } + node.put("x", xx); + node.put("ytop", Double.valueOf(ytop)); + node.put("ybottom", Double.valueOf(ybottom)); + node.put("z", zz); + node.put("world", world); + if(desc != null) + node.put("desc", desc); + node.put("stokeWeight", lineweight); + node.put("strokeOpacity", lineopacity); + node.put("strokeColor", linecolor); + node.put("fillOpacity", fillopacity); + node.put("fillColor", fillcolor); + + return node; + } + @Override + public String getWorld() { + return world; + } + @Override + public boolean isLabelMarkup() { + return markup; + } + @Override + public void setDescription(String desc) { + if((this.desc == null) || (this.desc.equals(desc) == false)) { + this.desc = desc; + MarkerAPIImpl.areaMarkerUpdated(this, MarkerUpdate.UPDATED); + if(ispersistent) + MarkerAPIImpl.saveMarkers(); + } + } + /** + * Get marker description + * @return descrption + */ + public String getDescription() { + return this.desc; + } + @Override + public double getTopY() { + return ytop; + } + @Override + public double getBottomY() { + return ybottom; + } + @Override + public void setRangeY(double ytop, double ybottom) { + if((this.ytop != ytop) || (this.ybottom != ybottom)) { + this.ytop = ytop; + this.ybottom = ybottom; + MarkerAPIImpl.areaMarkerUpdated(this, MarkerUpdate.UPDATED); + if(ispersistent) + MarkerAPIImpl.saveMarkers(); + } + } + @Override + public int getCornerCount() { + return corners.size(); + } + @Override + public double getCornerX(int n) { + Coord c = corners.get(n); + if(c != null) + return c.x; + return 0; + } + @Override + public double getCornerZ(int n) { + Coord c = corners.get(n); + if(c != null) + return c.z; + return 0; + } + @Override + public void setCornerLocation(int n, double x, double z) { + Coord c; + if(n >= corners.size()) { + corners.add(new Coord(x, z)); + } + else { + c = corners.get(n); + if((c.x == x) && (c.z == z)) + return; + c.x = x; + c.z = z; + } + MarkerAPIImpl.areaMarkerUpdated(this, MarkerUpdate.UPDATED); + if(ispersistent) + MarkerAPIImpl.saveMarkers(); + } + @Override + public void deleteCorner(int n) { + if(n < corners.size()) { + corners.remove(n); + MarkerAPIImpl.areaMarkerUpdated(this, MarkerUpdate.UPDATED); + if(ispersistent) + MarkerAPIImpl.saveMarkers(); + } + } + @Override + public void setCornerLocations(double[] x, double[] z) { + corners.clear(); + for(int i = 0; (i < x.length) && (i < z.length); i++) { + corners.add(new Coord(x[i], z[i])); + } + MarkerAPIImpl.areaMarkerUpdated(this, MarkerUpdate.UPDATED); + if(ispersistent) + MarkerAPIImpl.saveMarkers(); + } + @Override + public void setLineStyle(int weight, double opacity, int color) { + if((weight != lineweight) || (opacity != lineopacity) || (color != linecolor)) { + lineweight = weight; + lineopacity = opacity; + linecolor = color; + MarkerAPIImpl.areaMarkerUpdated(this, MarkerUpdate.UPDATED); + if(ispersistent) + MarkerAPIImpl.saveMarkers(); + } + } + @Override + public int getLineWeight() { + return lineweight; + } + @Override + public double getLineOpacity() { + return lineopacity; + } + @Override + public int getLineColor() { + return linecolor; + } + @Override + public void setFillStyle(double opacity, int color) { + if((opacity != fillopacity) || (color != fillcolor)) { + fillopacity = opacity; + fillcolor = color; + MarkerAPIImpl.areaMarkerUpdated(this, MarkerUpdate.UPDATED); + if(ispersistent) + MarkerAPIImpl.saveMarkers(); + } + } + @Override + public double getFillOpacity() { + return fillopacity; + } + @Override + public int getFillColor() { + return fillcolor; + } +} diff --git a/src/main/java/org/dynmap/markers/impl/MarkerAPIImpl.java b/src/main/java/org/dynmap/markers/impl/MarkerAPIImpl.java index 5e16901c..e7983355 100644 --- a/src/main/java/org/dynmap/markers/impl/MarkerAPIImpl.java +++ b/src/main/java/org/dynmap/markers/impl/MarkerAPIImpl.java @@ -31,6 +31,7 @@ import org.dynmap.Event; import org.dynmap.Log; import org.dynmap.MapManager; import org.dynmap.Client.ComponentMessage; +import org.dynmap.markers.AreaMarker; import org.dynmap.markers.Marker; import org.dynmap.markers.MarkerAPI; import org.dynmap.markers.MarkerIcon; @@ -90,7 +91,47 @@ public class MarkerAPIImpl implements MarkerAPI, Event.Listener { msg = "markerupdated"; } } - + + public static class AreaMarkerUpdated extends MarkerComponentMessage { + public String msg; + public double ytop, ybottom; + public double[] x; + public double[] z; + public int strokeWeight; + public double strokeOpacity; + public int strokeColor; + public double fillOpacity; + public int fillColor; + public String id; + public String label; + public String set; + + 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); + } + strokeColor = m.getLineColor(); + strokeWeight = m.getLineWeight(); + strokeOpacity = m.getFillOpacity(); + fillColor = m.getFillColor(); + fillOpacity = m.getFillOpacity(); + + this.set = m.getMarkerSet().getMarkerSetID(); + if(deleted) + msg = "areamarkerdeleted"; + else + msg = "areamarkerupdated"; + } + } + public static class MarkerSetUpdated extends MarkerComponentMessage { public String msg; public String id; @@ -386,6 +427,19 @@ public class MarkerAPIImpl implements MarkerAPI, Event.Listener { if(MapManager.mapman != null) MapManager.mapman.pushUpdate(marker.getWorld(), 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.writeMarkersFile(marker.getWorld()); + /* Enqueue client update */ + if(MapManager.mapman != null) + MapManager.mapman.pushUpdate(marker.getWorld(), new AreaMarkerUpdated(marker, update == MarkerUpdate.DELETED)); + } /** * Signal marker set update * @param markerset - updated marker set @@ -1060,6 +1114,31 @@ public class MarkerAPIImpl implements MarkerAPI, Event.Listener { markers.put(m.getMarkerID(), mdata); } msdata.put("markers", markers); /* Add markers to set data */ + + HashMap areas = new HashMap(); + for(AreaMarker m : ms.getAreaMarkers()) { + if(m.getWorld().equals(wname) == false) continue; + + HashMap mdata = new HashMap(); + int cnt = m.getCornerCount(); + double xx[] = new double[cnt]; + double zz[] = new double[cnt]; + for(int i = 0; i < cnt; i++) { + xx[i] = m.getCornerX(i); + zz[i] = m.getCornerZ(i); + } + mdata.put("x", xx); + mdata.put("ytop", m.getTopY()); + mdata.put("ybottom", m.getBottomY()); + mdata.put("z", zz); + mdata.put("label", m.getLabel()); + mdata.put("markup", m.isLabelMarkup()); + if(m.getDescription() != null) + mdata.put("desc", m.getDescription()); + /* Add to markers */ + areas.put(m.getMarkerID(), mdata); + } + msdata.put("areas", areas); /* Add areamarkers to set data */ markerdata.put(ms.getMarkerSetID(), msdata); /* Add marker set data to world marker data */ } diff --git a/src/main/java/org/dynmap/markers/impl/MarkerSetImpl.java b/src/main/java/org/dynmap/markers/impl/MarkerSetImpl.java index 05ff6c73..67ca9035 100644 --- a/src/main/java/org/dynmap/markers/impl/MarkerSetImpl.java +++ b/src/main/java/org/dynmap/markers/impl/MarkerSetImpl.java @@ -10,6 +10,7 @@ import java.util.Set; import org.bukkit.Location; import org.bukkit.util.config.ConfigurationNode; import org.dynmap.Log; +import org.dynmap.markers.AreaMarker; import org.dynmap.markers.Marker; import org.dynmap.markers.MarkerIcon; import org.dynmap.markers.MarkerSet; @@ -17,6 +18,7 @@ import org.dynmap.markers.impl.MarkerAPIImpl.MarkerUpdate; class MarkerSetImpl implements MarkerSet { private HashMap markers = new HashMap(); + private HashMap areamarkers = new HashMap(); private String setid; private String label; private HashMap allowedicons = null; @@ -49,6 +51,8 @@ class MarkerSetImpl implements MarkerSet { void cleanup() { for(MarkerImpl m : markers.values()) m.cleanup(); + for(AreaMarkerImpl m : areamarkers.values()) + m.cleanup(); markers.clear(); } @@ -57,6 +61,11 @@ class MarkerSetImpl implements MarkerSet { return new HashSet(markers.values()); } + @Override + public Set getAreaMarkers() { + return new HashSet(areamarkers.values()); + } + @Override public Marker createMarker(String id, String label, String world, double x, double y, double z, MarkerIcon icon, boolean is_persistent) { return createMarker(id, label, false, world, x, y, z, icon, is_persistent); @@ -196,6 +205,18 @@ class MarkerSetImpl implements MarkerSet { } MarkerAPIImpl.markerUpdated(marker, MarkerUpdate.DELETED); } + /** + * Remove marker from set + * + * @param marker + */ + void removeAreaMarker(AreaMarkerImpl marker) { + markers.remove(marker.getMarkerID()); /* Remove from set */ + if(ispersistent && marker.isPersistentMarker()) { /* If persistent */ + MarkerAPIImpl.saveMarkers(); /* Drive save */ + } + MarkerAPIImpl.areaMarkerUpdated(marker, MarkerUpdate.DELETED); + } /** * Get configuration node to be saved @@ -211,6 +232,13 @@ class MarkerSetImpl implements MarkerSet { node.put(id, m.getPersistentData()); } } + HashMap anode = new HashMap(); + for(String id : areamarkers.keySet()) { + AreaMarkerImpl m = areamarkers.get(id); + if(m.isPersistentMarker()) { + anode.put(id, m.getPersistentData()); + } + } /* Make top level node */ HashMap setnode = new HashMap(); setnode.put("label", label); @@ -219,6 +247,7 @@ class MarkerSetImpl implements MarkerSet { setnode.put("allowedicons", allowed); } setnode.put("markers", node); + setnode.put("areas", anode); setnode.put("hide", hide_by_def); setnode.put("layerprio", prio); return setnode; @@ -243,6 +272,19 @@ class MarkerSetImpl implements MarkerSet { } } } + ConfigurationNode areamarkernode = node.getNode("areas"); + if(areamarkernode != null) { + for(String id : areamarkernode.getKeys()) { + AreaMarkerImpl marker = new AreaMarkerImpl(id, this); /* Make and load marker */ + if(marker.loadPersistentData(areamarkernode.getNode(id))) { + areamarkers.put(id, marker); + } + else { + Log.info("Error loading area marker '" + id + "' for set '" + setid + "'"); + marker.cleanup(); + } + } + } List allowed = node.getStringList("allowedicons", null); if(allowed != null) { for(String id : allowed) { @@ -286,4 +328,46 @@ class MarkerSetImpl implements MarkerSet { return this.prio; } + @Override + public AreaMarker createAreaMarker(String id, String lbl, boolean markup, String world, double[] x, double[] z, double ytop, double ybottom, boolean persistent) { + if(id == null) { /* If not defined, generate unique one */ + int i = 0; + do { + i++; + id = "area_" + i; + } while(areamarkers.containsKey(id)); + } + if(areamarkers.containsKey(id)) return null; /* Duplicate ID? */ + /* Create marker */ + persistent = persistent && this.ispersistent; + AreaMarkerImpl marker = new AreaMarkerImpl(id, label, markup, world, x, z, ytop, ybottom, persistent, this); + areamarkers.put(id, marker); /* Add to set */ + if(persistent) + MarkerAPIImpl.saveMarkers(); + + MarkerAPIImpl.areaMarkerUpdated(marker, MarkerUpdate.CREATED); /* Signal create */ + + return marker; + } + + @Override + public AreaMarker findAreaMarker(String id) { + return areamarkers.get(id); + } + + @Override + public AreaMarker findAreaMarkerByLabel(String lbl) { + AreaMarker match = null; + int matchlen = Integer.MAX_VALUE; + for(AreaMarker m : areamarkers.values()) { + if(m.getLabel().contains(lbl)) { + if(matchlen > m.getLabel().length()) { + match = m; + matchlen = m.getLabel().length(); + } + } + } + return match; + } + }