com.google.code.gson
gson
@@ -134,5 +146,9 @@
maven.sk89q.com
http://maven.sk89q.com/repo/
+
+ repo.mikeprimm.com
+ http://repo.mikeprimm.com/
+
diff --git a/src/main/java/com/massivecraft/factions/Conf.java b/src/main/java/com/massivecraft/factions/Conf.java
index 19e2be41..b49aa6f8 100644
--- a/src/main/java/com/massivecraft/factions/Conf.java
+++ b/src/main/java/com/massivecraft/factions/Conf.java
@@ -1,5 +1,7 @@
package com.massivecraft.factions;
+import com.google.common.collect.ImmutableMap;
+import com.massivecraft.factions.integration.dynmap.DynmapStyle;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.entity.EntityType;
@@ -246,6 +248,85 @@ public class Conf {
public static double econCostNeutral = 0.0;
public static double econCostNoBoom = 0.0;
+
+ // -------------------------------------------- //
+ // INTEGRATION: DYNMAP
+ // -------------------------------------------- //
+
+ // Should the dynmap intagration be used?
+ public static boolean dynmapUse = false;
+
+ // Name of the Factions layer
+ public static String dynmapLayerName = "Factions";
+
+ // Should the layer be visible per default
+ public static boolean dynmapLayerVisible = true;
+
+ // Ordering priority in layer menu (low goes before high - default is 0)
+ public static int dynmapLayerPriority = 2;
+
+ // (optional) set minimum zoom level before layer is visible (0 = default, always visible)
+ public static int dynmapLayerMinimumZoom = 0;
+
+ // Format for popup - substitute values for macros
+ public static String dynmapDescription =
+ "\n"
+ + "%name%
\n"
+ + "%description%
"
+ + "
\n"
+ + "Leader: %players.leader%
\n"
+ + "Admins: %players.admins.count%
\n"
+ + "Moderators: %players.moderators.count%
\n"
+ + "Members: %players.normals.count%
\n"
+ + "TOTAL: %players.count%
\n"
+ + "\n"
+ + "Bank: %money%
\n"
+ + "
\n"
+ + "
";
+
+ // Enable the %money% macro. Only do this if you know your economy manager is thread-safe.
+ public static boolean dynmapDescriptionMoney = false;
+
+ // Allow players in faction to see one another on Dynmap (only relevant if Dynmap has 'player-info-protected' enabled)
+ public static boolean dynmapVisibilityByFaction = true;
+
+ // Optional setting to limit which regions to show.
+ // If empty all regions are shown.
+ // Specify Faction either by name or UUID.
+ // To show all regions on a given world, add 'world:' to the list.
+ public static Set dynmapVisibleFactions = new HashSet();
+
+ // Optional setting to hide specific Factions.
+ // Specify Faction either by name or UUID.
+ // To hide all regions on a given world, add 'world:' to the list.
+ public static Set dynmapHiddenFactions = new HashSet();
+
+ // Region Style
+ public static final transient String DYNMAP_STYLE_LINE_COLOR = "#00FF00";
+ public static final transient double DYNMAP_STYLE_LINE_OPACITY = 0.8D;
+ public static final transient int DYNMAP_STYLE_LINE_WEIGHT = 3;
+ public static final transient String DYNMAP_STYLE_FILL_COLOR = "#00FF00";
+ public static final transient double DYNMAP_STYLE_FILL_OPACITY = 0.35D;
+ public static final transient String DYNMAP_STYLE_HOME_MARKER = "greenflag";
+ public static final transient boolean DYNMAP_STYLE_BOOST = false;
+
+ public static DynmapStyle dynmapDefaultStyle = new DynmapStyle()
+ .setStrokeColor(DYNMAP_STYLE_LINE_COLOR)
+ .setLineOpacity(DYNMAP_STYLE_LINE_OPACITY)
+ .setLineWeight(DYNMAP_STYLE_LINE_WEIGHT)
+ .setFillColor(DYNMAP_STYLE_FILL_COLOR)
+ .setFillOpacity(DYNMAP_STYLE_FILL_OPACITY)
+ .setHomeMarker(DYNMAP_STYLE_HOME_MARKER)
+ .setBoost(DYNMAP_STYLE_BOOST);
+
+ // Optional per Faction style overrides. Any defined replace those in dynmapDefaultStyle.
+ // Specify Faction either by name or UUID.
+ public static Map dynmapFactionStyles = ImmutableMap.of(
+ "SafeZone", new DynmapStyle().setStrokeColor("#FF00FF").setFillColor("#FF00FF").setBoost(false),
+ "WarZone", new DynmapStyle().setStrokeColor("#FF0000").setFillColor("#FF0000").setBoost(false)
+ );
+
+
//Faction banks, to pay for land claiming and other costs instead of individuals paying for them
public static boolean bankEnabled = true;
public static boolean bankMembersCanWithdraw = false; //Have to be at least moderator to withdraw or pay money to another faction
diff --git a/src/main/java/com/massivecraft/factions/P.java b/src/main/java/com/massivecraft/factions/P.java
index 8e643fa3..052fa4d3 100644
--- a/src/main/java/com/massivecraft/factions/P.java
+++ b/src/main/java/com/massivecraft/factions/P.java
@@ -7,6 +7,7 @@ import com.massivecraft.factions.cmd.FCmdRoot;
import com.massivecraft.factions.integration.Econ;
import com.massivecraft.factions.integration.Essentials;
import com.massivecraft.factions.integration.Worldguard;
+import com.massivecraft.factions.integration.dynmap.EngineDynmap;
import com.massivecraft.factions.listeners.*;
import com.massivecraft.factions.struct.ChatMode;
import com.massivecraft.factions.util.*;
@@ -107,6 +108,8 @@ public class P extends MPlugin {
Worldguard.init(this);
}
+ EngineDynmap.getInstance().init();
+
// start up task which runs the autoLeaveAfterDaysOfInactivity routine
startAutoLeaveTask(false);
diff --git a/src/main/java/com/massivecraft/factions/integration/dynmap/DynmapStyle.java b/src/main/java/com/massivecraft/factions/integration/dynmap/DynmapStyle.java
new file mode 100644
index 00000000..a898f8e9
--- /dev/null
+++ b/src/main/java/com/massivecraft/factions/integration/dynmap/DynmapStyle.java
@@ -0,0 +1,67 @@
+package com.massivecraft.factions.integration.dynmap;
+
+import com.massivecraft.factions.Conf;
+
+public class DynmapStyle
+{
+ // -------------------------------------------- //
+ // FIELDS
+ // -------------------------------------------- //
+
+ public String lineColor = null;
+ public int getLineColor() { return getColor(coalesce(this.lineColor, Conf.dynmapDefaultStyle.lineColor, Conf.DYNMAP_STYLE_LINE_COLOR)); }
+ public DynmapStyle setStrokeColor(String strokeColor) { this.lineColor = strokeColor; return this; }
+
+ public Double lineOpacity = null;
+ public double getLineOpacity() { return coalesce(this.lineOpacity, Conf.dynmapDefaultStyle.lineOpacity, Conf.DYNMAP_STYLE_LINE_OPACITY); }
+ public DynmapStyle setLineOpacity(Double strokeOpacity) { this.lineOpacity = strokeOpacity; return this; }
+
+ public Integer lineWeight = null;
+ public int getLineWeight() { return coalesce(this.lineWeight, Conf.dynmapDefaultStyle.lineWeight, Conf.DYNMAP_STYLE_LINE_WEIGHT); }
+ public DynmapStyle setLineWeight(Integer strokeWeight) { this.lineWeight = strokeWeight; return this; }
+
+ public String fillColor = null;
+ public int getFillColor() { return getColor(coalesce(this.fillColor, Conf.dynmapDefaultStyle.fillColor, Conf.DYNMAP_STYLE_FILL_COLOR)); }
+ public DynmapStyle setFillColor(String fillColor) { this.fillColor = fillColor; return this; }
+
+ public Double fillOpacity = null;
+ public double getFillOpacity() { return coalesce(this.fillOpacity, Conf.dynmapDefaultStyle.fillOpacity, Conf.DYNMAP_STYLE_FILL_OPACITY); }
+ public DynmapStyle setFillOpacity(Double fillOpacity) { this.fillOpacity = fillOpacity; return this; }
+
+ // NOTE: We just return the string here. We do not return the resolved Dynmap MarkerIcon object.
+ // The reason is we use this class in the MConf. For serialization to work Dynmap would have to be loaded and we can't require that.
+ // Using dynmap is optional.
+ public String homeMarker = null;
+ public String getHomeMarker() { return coalesce(this.homeMarker, Conf.dynmapDefaultStyle.homeMarker, Conf.DYNMAP_STYLE_HOME_MARKER); }
+ public DynmapStyle setHomeMarker(String homeMarker) { this.homeMarker = homeMarker; return this; }
+
+ public Boolean boost = null;
+ public boolean getBoost() { return coalesce(this.boost, Conf.dynmapDefaultStyle.boost, Conf.DYNMAP_STYLE_BOOST); }
+ public DynmapStyle setBoost(Boolean boost) { this.boost = boost; return this; }
+
+ // -------------------------------------------- //
+ // UTIL
+ // -------------------------------------------- //
+
+ @SafeVarargs
+ public static T coalesce(T... items)
+ {
+ for (T item : items)
+ {
+ if (item != null) return item;
+ }
+ return null;
+ }
+
+ public static int getColor(String string)
+ {
+ int ret = 0x00FF00;
+ try
+ {
+ ret = Integer.parseInt(string.substring(1), 16);
+ }
+ catch (NumberFormatException ignored) {}
+ return ret;
+ }
+
+}
diff --git a/src/main/java/com/massivecraft/factions/integration/dynmap/EngineDynmap.java b/src/main/java/com/massivecraft/factions/integration/dynmap/EngineDynmap.java
new file mode 100644
index 00000000..555322e1
--- /dev/null
+++ b/src/main/java/com/massivecraft/factions/integration/dynmap/EngineDynmap.java
@@ -0,0 +1,825 @@
+package com.massivecraft.factions.integration.dynmap;
+
+import com.massivecraft.factions.*;
+import com.massivecraft.factions.integration.Econ;
+import com.massivecraft.factions.struct.Role;
+import com.massivecraft.factions.zcore.persist.MemoryBoard;
+import org.bukkit.Bukkit;
+import org.bukkit.ChatColor;
+import org.bukkit.Location;
+import org.bukkit.plugin.Plugin;
+import org.dynmap.DynmapAPI;
+import org.dynmap.markers.*;
+import org.dynmap.utils.TileFlags;
+
+import java.util.*;
+import java.util.Map.Entry;
+
+// This source code is a heavily modified version of mikeprimms plugin Dynmap-Factions.
+public class EngineDynmap
+{
+ // -------------------------------------------- //
+ // CONSTANTS
+ // -------------------------------------------- //
+
+ public final static int BLOCKS_PER_CHUNK = 16;
+
+ public final static String DYNMAP_INTEGRATION = "\u00A7dDynmap Integration: \u00A7e";
+
+ public final static String FACTIONS = "factions";
+ public final static String FACTIONS_ = FACTIONS + "_";
+
+ public final static String FACTIONS_MARKERSET = FACTIONS_ + "markerset";
+
+ public final static String FACTIONS_HOME = FACTIONS_ + "home";
+ public final static String FACTIONS_HOME_ = FACTIONS_HOME + "_";
+
+ public final static String FACTIONS_PLAYERSET = FACTIONS_ + "playerset";
+ public final static String FACTIONS_PLAYERSET_ = FACTIONS_PLAYERSET + "_";
+
+ // -------------------------------------------- //
+ // INSTANCE & CONSTRUCT
+ // -------------------------------------------- //
+
+ private static EngineDynmap i = new EngineDynmap();
+ public static EngineDynmap getInstance() { return i; }
+ private EngineDynmap() {}
+
+ public DynmapAPI dynmapApi;
+ public MarkerAPI markerApi;
+ public MarkerSet markerset;
+
+ public void init()
+ {
+ Plugin dynmap = Bukkit.getServer().getPluginManager().getPlugin("dynmap");
+
+ if (dynmap == null || !dynmap.isEnabled())
+ {
+ return;
+ }
+
+ // Should we even use dynmap?
+ if (!Conf.dynmapUse)
+ {
+ if (this.markerset != null)
+ {
+ this.markerset.deleteMarkerSet();
+ this.markerset = null;
+ }
+ return;
+ }
+
+ // Shedule non thread safe sync at the end!
+ Bukkit.getScheduler().scheduleSyncRepeatingTask(P.p, new Runnable()
+ {
+ @Override
+ public void run()
+ {
+
+ final Map homes = createHomes();
+ final Map areas = createAreas();
+ final Map> playerSets = createPlayersets();
+
+ if (!updateCore()) return;
+
+ // createLayer() is thread safe but it makes use of fields set in updateCore() so we must have it after.
+ if (!updateLayer(createLayer())) return;
+
+ updateHomes(homes);
+ updateAreas(areas);
+ updatePlayersets(playerSets);
+ }
+ }, 100L, 100L);
+ }
+
+ // Thread Safe / Asynchronous: No
+ public boolean updateCore()
+ {
+ // Get DynmapAPI
+ this.dynmapApi = (DynmapAPI) Bukkit.getPluginManager().getPlugin("dynmap");
+ if (this.dynmapApi == null)
+ {
+ severe("Could not retrieve the DynmapAPI.");
+ return false;
+ }
+
+ // Get MarkerAPI
+ this.markerApi = this.dynmapApi.getMarkerAPI();
+ if (this.markerApi == null)
+ {
+ severe("Could not retrieve the MarkerAPI.");
+ return false;
+ }
+
+ return true;
+ }
+
+ // Thread Safe / Asynchronous: Yes
+ public TempMarkerSet createLayer()
+ {
+ TempMarkerSet ret = new TempMarkerSet();
+ ret.label = Conf.dynmapLayerName;
+ ret.minimumZoom = Conf.dynmapLayerMinimumZoom;
+ ret.priority = Conf.dynmapLayerPriority;
+ ret.hideByDefault = !Conf.dynmapLayerVisible;
+ return ret;
+ }
+
+ // Thread Safe / Asynchronous: No
+ public boolean updateLayer(TempMarkerSet temp)
+ {
+ this.markerset = this.markerApi.getMarkerSet(FACTIONS_MARKERSET);
+ if (this.markerset == null)
+ {
+ this.markerset = temp.create(this.markerApi, FACTIONS_MARKERSET);
+ if (this.markerset == null)
+ {
+ severe("Could not create the Faction Markerset/Layer");
+ return false;
+ }
+ }
+ else
+ {
+ temp.update(this.markerset);
+ }
+ return true;
+ }
+
+ // -------------------------------------------- //
+ // UPDATE: HOMES
+ // -------------------------------------------- //
+
+ // Thread Safe / Asynchronous: Yes
+ public Map createHomes()
+ {
+ Map ret = new HashMap();
+
+ // Loop current factions
+ for (Faction faction : Factions.getInstance().getAllFactions())
+ {
+ Location ps = faction.getHome();
+ if (ps == null) continue;
+
+ DynmapStyle style = getStyle(faction);
+
+ String markerId = FACTIONS_HOME_ + faction.getId();
+
+ TempMarker temp = new TempMarker();
+ temp.label = ChatColor.stripColor(faction.getTag());
+ temp.world = ps.getWorld().toString();
+ temp.x = ps.getX();
+ temp.y = ps.getY();
+ temp.z = ps.getZ();
+ temp.iconName = style.getHomeMarker();
+ temp.description = getDescription(faction);
+
+ ret.put(markerId, temp);
+ }
+
+ return ret;
+ }
+
+ // Thread Safe / Asynchronous: No
+ // This method places out the faction home markers into the factions markerset.
+ public void updateHomes(Map homes)
+ {
+ // Put all current faction markers in a map
+ Map markers = new HashMap();
+ for (Marker marker : this.markerset.getMarkers())
+ {
+ markers.put(marker.getMarkerID(), marker);
+ }
+
+ // Loop homes
+ for (Entry entry : homes.entrySet())
+ {
+ String markerId = entry.getKey();
+ TempMarker temp = entry.getValue();
+
+ // Get Creative
+ // NOTE: I remove from the map created just in the beginning of this method.
+ // NOTE: That way what is left at the end will be outdated markers to remove.
+ Marker marker = markers.remove(markerId);
+ if (marker == null)
+ {
+ marker = temp.create(this.markerApi, this.markerset, markerId);
+ if (marker == null)
+ {
+ EngineDynmap.severe("Could not get/create the home marker " + markerId);
+ }
+ }
+ else
+ {
+ temp.update(this.markerApi, marker);
+ }
+ }
+
+ // Delete Deprecated Markers
+ // Only old markers should now be left
+ for (Marker marker : markers.values())
+ {
+ marker.deleteMarker();
+ }
+ }
+
+ // -------------------------------------------- //
+ // UPDATE: AREAS
+ // -------------------------------------------- //
+
+ // Thread Safe: YES
+ public Map createAreas()
+ {
+ Map>> worldFactionChunks = createWorldFactionChunks();
+ return createAreas(worldFactionChunks);
+ }
+
+ // Thread Safe: YES
+ public Map>> createWorldFactionChunks()
+ {
+ // Create map "world name --> faction --> set of chunk coords"
+ Map>> worldFactionChunks = new HashMap>>();
+
+ // Note: The board is the world. The board id is the world name.
+ MemoryBoard board = (MemoryBoard) Board.getInstance();
+
+ for (Entry entry : board.flocationIds.entrySet())
+ {
+ String world = entry.getKey().getWorldName();
+ Faction chunkOwner = Factions.getInstance().getFactionById(entry.getValue());
+
+ Map> factionChunks = worldFactionChunks.get(world);
+ if (factionChunks == null)
+ {
+ factionChunks = new HashMap>();
+ worldFactionChunks.put(world, factionChunks);
+ }
+
+ Set factionTerritory = factionChunks.get(chunkOwner);
+ if (factionTerritory == null){
+ factionTerritory = new HashSet();
+ factionChunks.put(chunkOwner, factionTerritory);
+ }
+
+ factionTerritory.add(entry.getKey());
+ }
+
+ return worldFactionChunks;
+ }
+
+ // Thread Safe: YES
+ public Map createAreas(Map>> worldFactionChunks)
+ {
+ Map ret = new HashMap();
+
+ // For each world
+ for (Entry>> entry : worldFactionChunks.entrySet())
+ {
+ String world = entry.getKey();
+ Map> factionChunks = entry.getValue();
+
+ // For each faction and its chunks in that world
+ for (Entry> entry1 : factionChunks.entrySet())
+ {
+ Faction faction = entry1.getKey();
+ Set chunks = entry1.getValue();
+ Map worldFactionMarkers = createAreas(world, faction, chunks);
+ ret.putAll(worldFactionMarkers);
+ }
+ }
+
+ return ret;
+ }
+
+ // Thread Safe: YES
+ // Handle specific faction on specific world
+ // "handle faction on world"
+ public Map createAreas(String world, Faction faction, Set chunks)
+ {
+ Map ret = new HashMap();
+
+ // If the faction is visible ...
+ if (!isVisible(faction, world)) return ret;
+
+ // ... and has any chunks ...
+ if (chunks.isEmpty()) return ret;
+
+ // Index of polygon for given faction
+ int markerIndex = 0;
+
+ // Create the info window
+ String description = getDescription(faction);
+
+ // Fetch Style
+ DynmapStyle style = this.getStyle(faction);
+
+ // Loop through chunks: set flags on chunk map
+ TileFlags allChunkFlags = new TileFlags();
+ LinkedList allChunks = new LinkedList();
+ for (FLocation chunk : chunks)
+ {
+ allChunkFlags.setFlag((int)chunk.getX(), (int)chunk.getZ(), true); // Set flag for chunk
+ allChunks.addLast(chunk);
+ }
+
+ // Loop through until we don't find more areas
+ while (allChunks != null)
+ {
+ TileFlags ourChunkFlags = null;
+ LinkedList ourChunks = null;
+ LinkedList newChunks = null;
+
+ int minimumX = Integer.MAX_VALUE;
+ int minimumZ = Integer.MAX_VALUE;
+ for (FLocation chunk : allChunks)
+ {
+ int chunkX = (int)chunk.getX();
+ int chunkZ = (int)chunk.getZ();
+
+ // If we need to start shape, and this block is not part of one yet
+ if (ourChunkFlags == null && allChunkFlags.getFlag(chunkX, chunkZ))
+ {
+ ourChunkFlags = new TileFlags(); // Create map for shape
+ ourChunks = new LinkedList();
+ floodFillTarget(allChunkFlags, ourChunkFlags, chunkX, chunkZ); // Copy shape
+ ourChunks.add(chunk); // Add it to our chunk list
+ minimumX = chunkX;
+ minimumZ = chunkZ;
+ }
+ // If shape found, and we're in it, add to our node list
+ else if (ourChunkFlags != null && ourChunkFlags.getFlag(chunkX, chunkZ))
+ {
+ ourChunks.add(chunk);
+ if (chunkX < minimumX)
+ {
+ minimumX = chunkX;
+ minimumZ = chunkZ;
+ }
+ else if (chunkX == minimumX && chunkZ < minimumZ)
+ {
+ minimumZ = chunkZ;
+ }
+ }
+ // Else, keep it in the list for the next polygon
+ else
+ {
+ if (newChunks == null) newChunks = new LinkedList();
+ newChunks.add(chunk);
+ }
+ }
+
+ // Replace list (null if no more to process)
+ allChunks = newChunks;
+
+ if (ourChunkFlags == null) continue;
+
+ // Trace outline of blocks - start from minx, minz going to x+
+ int initialX = minimumX;
+ int initialZ = minimumZ;
+ int currentX = minimumX;
+ int currentZ = minimumZ;
+ Direction direction = Direction.XPLUS;
+ ArrayList linelist = new ArrayList();
+ linelist.add(new int[]{ initialX, initialZ }); // Add start point
+ while ((currentX != initialX) || (currentZ != initialZ) || (direction != Direction.ZMINUS))
+ {
+ switch (direction)
+ {
+ case XPLUS: // Segment in X+ direction
+ if (!ourChunkFlags.getFlag(currentX + 1, currentZ))
+ { // Right turn?
+ linelist.add(new int[]{ currentX + 1, currentZ }); // Finish line
+ direction = Direction.ZPLUS; // Change direction
+ }
+ else if (!ourChunkFlags.getFlag(currentX + 1, currentZ - 1))
+ { // Straight?
+ currentX++;
+ }
+ else
+ { // Left turn
+ linelist.add(new int[]{ currentX + 1, currentZ }); // Finish line
+ direction = Direction.ZMINUS;
+ currentX++;
+ currentZ--;
+ }
+ break;
+ case ZPLUS: // Segment in Z+ direction
+ if (!ourChunkFlags.getFlag(currentX, currentZ + 1))
+ { // Right turn?
+ linelist.add(new int[]{ currentX + 1, currentZ + 1 }); // Finish line
+ direction = Direction.XMINUS; // Change direction
+ }
+ else if (!ourChunkFlags.getFlag(currentX + 1, currentZ + 1))
+ { // Straight?
+ currentZ++;
+ }
+ else
+ { // Left turn
+ linelist.add(new int[]{ currentX + 1, currentZ + 1 }); // Finish line
+ direction = Direction.XPLUS;
+ currentX++;
+ currentZ++;
+ }
+ break;
+ case XMINUS: // Segment in X- direction
+ if (!ourChunkFlags.getFlag(currentX - 1, currentZ))
+ { // Right turn?
+ linelist.add(new int[]{ currentX, currentZ + 1 }); // Finish line
+ direction = Direction.ZMINUS; // Change direction
+ }
+ else if (!ourChunkFlags.getFlag(currentX - 1, currentZ + 1))
+ { // Straight?
+ currentX--;
+ }
+ else
+ { // Left turn
+ linelist.add(new int[] { currentX, currentZ + 1 }); // Finish line
+ direction = Direction.ZPLUS;
+ currentX--;
+ currentZ++;
+ }
+ break;
+ case ZMINUS: // Segment in Z- direction
+ if (!ourChunkFlags.getFlag(currentX, currentZ - 1))
+ { // Right turn?
+ linelist.add(new int[]{ currentX, currentZ }); // Finish line
+ direction = Direction.XPLUS; // Change direction
+ }
+ else if (!ourChunkFlags.getFlag(currentX - 1, currentZ - 1))
+ { // Straight?
+ currentZ--;
+ }
+ else
+ { // Left turn
+ linelist.add(new int[] { currentX, currentZ }); // Finish line
+ direction = Direction.XMINUS;
+ currentX--;
+ currentZ--;
+ }
+ break;
+ }
+ }
+
+ int sz = linelist.size();
+ double[] x = new double[sz];
+ double[] z = new double[sz];
+ for (int i = 0; i < sz; i++)
+ {
+ int[] line = linelist.get(i);
+ x[i] = (double) line[0] * (double) BLOCKS_PER_CHUNK;
+ z[i] = (double) line[1] * (double) BLOCKS_PER_CHUNK;
+ }
+
+ // Build information for specific area
+ String markerId = FACTIONS_ + world + "__" + faction.getId() + "__" + markerIndex;
+
+ TempAreaMarker temp = new TempAreaMarker();
+ temp.label = faction.getTag();
+ temp.world = world;
+ temp.x = x;
+ temp.z = z;
+ temp.description = description;
+
+ temp.lineColor = style.getLineColor();
+ temp.lineOpacity = style.getLineOpacity();
+ temp.lineWeight = style.getLineWeight();
+
+ temp.fillColor = style.getFillColor();
+ temp.fillOpacity = style.getFillOpacity();
+
+ temp.boost = style.getBoost();
+
+ ret.put(markerId, temp);
+
+ markerIndex++;
+ }
+
+ return ret;
+ }
+
+ // Thread Safe: NO
+ public void updateAreas(Map areas)
+ {
+ // Map Current
+ Map markers = new HashMap();
+ for (AreaMarker marker : this.markerset.getAreaMarkers())
+ {
+ markers.put(marker.getMarkerID(), marker);
+ }
+
+ // Loop New
+ for (Entry entry : areas.entrySet())
+ {
+ String markerId = entry.getKey();
+ TempAreaMarker temp = entry.getValue();
+
+ // Get Creative
+ // NOTE: I remove from the map created just in the beginning of this method.
+ // NOTE: That way what is left at the end will be outdated markers to remove.
+ AreaMarker marker = markers.remove(markerId);
+ if (marker == null)
+ {
+ marker = temp.create(this.markerset, markerId);
+ if (marker == null)
+ {
+ severe("Could not get/create the area marker " + markerId);
+ }
+ }
+ else
+ {
+ temp.update(marker);
+ }
+ }
+
+ // Only old/outdated should now be left. Delete them.
+ for (AreaMarker marker : markers.values())
+ {
+ marker.deleteMarker();
+ }
+ }
+
+ // -------------------------------------------- //
+ // UPDATE: PLAYERSET
+ // -------------------------------------------- //
+
+ // Thread Safe / Asynchronous: Yes
+ public String createPlayersetId(Faction faction)
+ {
+ if (faction == null) return null;
+ if (faction.isNone()) return null;
+ String factionId = faction.getId();
+ if (factionId == null) return null;
+ return FACTIONS_PLAYERSET_ + factionId;
+ }
+
+ // Thread Safe / Asynchronous: Yes
+ public Set createPlayerset(Faction faction)
+ {
+ if (faction == null) return null;
+ if (faction.isNone()) return null;
+
+ Set ret = new HashSet();
+
+ for (FPlayer fplayer : faction.getFPlayers())
+ {
+ // NOTE: We add both UUID and name. This might be a good idea for future proofing.
+ ret.add(fplayer.getId());
+ ret.add(fplayer.getName());
+ }
+
+ return ret;
+ }
+
+ // Thread Safe / Asynchronous: Yes
+ public Map> createPlayersets()
+ {
+ if (!Conf.dynmapVisibilityByFaction)
+ {
+ return null;
+ }
+
+ Map> ret = new HashMap>();
+
+ for (Faction faction : Factions.getInstance().getAllFactions())
+ {
+ String playersetId = createPlayersetId(faction);
+ if (playersetId == null) continue;
+ Set playerIds = createPlayerset(faction);
+ if (playerIds == null) continue;
+ ret.put(playersetId, playerIds);
+ }
+
+ return ret;
+ }
+
+ // Thread Safe / Asynchronous: No
+ public void updatePlayersets(Map> playersets)
+ {
+ if (playersets == null)
+ {
+ return;
+ }
+
+ // Remove
+ for (PlayerSet set : this.markerApi.getPlayerSets())
+ {
+ if (!set.getSetID().startsWith(FACTIONS_PLAYERSET_)) continue;
+
+ // (Null means remove all)
+ if (playersets.containsKey(set.getSetID())) continue;
+
+ set.deleteSet();
+ }
+
+ // Add / Update
+ for (Entry> entry : playersets.entrySet())
+ {
+ // Extract from Entry
+ String setId = entry.getKey();
+ Set playerIds = entry.getValue();
+
+ // Get Creatively
+ PlayerSet set = this.markerApi.getPlayerSet(setId);
+ if (set == null) set = this.markerApi.createPlayerSet(
+ setId, // id
+ true, // symmetric
+ playerIds, // players
+ false // persistent
+ );
+ if (set == null)
+ {
+ severe("Could not get/create the player set " + setId);
+ continue;
+ }
+
+ // Set Content
+ set.setPlayers(playerIds);
+ }
+ }
+
+ // -------------------------------------------- //
+ // UTIL & SHARED
+ // -------------------------------------------- //
+
+ // Thread Safe / Asynchronous: Yes
+ private String getDescription(Faction faction)
+ {
+ String ret = "" + Conf.dynmapDescription + "
";
+
+ // Name
+ String name = faction.getTag();
+ name = ChatColor.stripColor(name);
+ name = escapeHtml(name);
+ ret = ret.replace("%name%", name);
+
+ // Description
+ String description = faction.getDescription();
+ description = ChatColor.stripColor(description);
+ description = escapeHtml(description);
+ ret = ret.replace("%description%", description);
+
+ // Money
+
+ String money = "unavailable";
+ if (Conf.bankEnabled && Conf.dynmapDescriptionMoney)
+ {
+ money = String.format("%.2f", Econ.getBalance(faction.getAccountId()));
+ }
+ ret = ret.replace("%money%", money);
+
+
+ // Players
+ Set playersList = faction.getFPlayers();
+ String playersCount = String.valueOf(playersList.size());
+ String players = getHtmlPlayerString(playersList);
+
+ FPlayer playersLeaderObject = faction.getFPlayerAdmin();
+ String playersLeader = getHtmlPlayerName(playersLeaderObject);
+
+ ArrayList playersAdminsList = faction.getFPlayersWhereRole(Role.ADMIN);
+ String playersAdminsCount = String.valueOf(playersAdminsList.size());
+ String playersAdmins = getHtmlPlayerString(playersAdminsList);
+
+ ArrayList playersModeratorsList = faction.getFPlayersWhereRole(Role.MODERATOR);
+ String playersModeratorsCount = String.valueOf(playersModeratorsList.size());
+ String playersModerators = getHtmlPlayerString(playersModeratorsList);
+
+
+ ArrayList playersNormalsList = faction.getFPlayersWhereRole(Role.NORMAL);
+ String playersNormalsCount = String.valueOf(playersNormalsList.size());
+ String playersNormals = getHtmlPlayerString(playersNormalsList);
+
+
+ ret = ret.replace("%players%", players);
+ ret = ret.replace("%players.count%", playersCount);
+ ret = ret.replace("%players.leader%", playersLeader);
+ ret = ret.replace("%players.admins%", playersAdmins);
+ ret = ret.replace("%players.admins.count%", playersAdminsCount);
+ ret = ret.replace("%players.moderators%", playersModerators);
+ ret = ret.replace("%players.moderators.count%", playersModeratorsCount);
+ ret = ret.replace("%players.normals%", playersNormals);
+ ret = ret.replace("%players.normals.count%", playersNormalsCount);
+
+ return ret;
+ }
+
+ public static String getHtmlPlayerString(Collection playersOfficersList)
+ {
+ String ret = "";
+ for (FPlayer fplayer : playersOfficersList)
+ {
+ if (ret.length() > 0) ret += ", ";
+ ret += getHtmlPlayerName(fplayer);
+ }
+ return ret;
+ }
+
+ public static String getHtmlPlayerName(FPlayer fplayer)
+ {
+ if (fplayer == null) return "none";
+ return escapeHtml(fplayer.getName());
+ }
+
+ public static String escapeHtml(String string)
+ {
+ StringBuilder out = new StringBuilder(Math.max(16, string.length()));
+ for (int i = 0; i < string.length(); i++)
+ {
+ char c = string.charAt(i);
+ if (c > 127 || c == '"' || c == '<' || c == '>' || c == '&')
+ {
+ out.append("");
+ out.append((int) c);
+ out.append(';');
+ }
+ else
+ {
+ out.append(c);
+ }
+ }
+ return out.toString();
+ }
+
+ // Thread Safe / Asynchronous: Yes
+ private boolean isVisible(Faction faction, String world)
+ {
+ if (faction == null) return false;
+ final String factionId = faction.getId();
+ if (factionId == null) return false;
+ final String factionName = faction.getTag();
+ if (factionName == null) return false;
+
+ Set visible = Conf.dynmapVisibleFactions;
+ Set hidden = Conf.dynmapHiddenFactions;
+
+ if (!visible.isEmpty() && !visible.contains(factionId) && !visible.contains(factionName) && !visible.contains("world:" + world))
+ {
+ return false;
+ }
+
+ if (hidden.contains(factionId) || hidden.contains(factionName) || hidden.contains("world:" + world))
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ // Thread Safe / Asynchronous: Yes
+ public DynmapStyle getStyle(Faction faction)
+ {
+ DynmapStyle ret;
+
+ ret = Conf.dynmapFactionStyles.get(faction.getId());
+ if (ret != null) return ret;
+
+ ret = Conf.dynmapFactionStyles.get(faction.getTag());
+ if (ret != null) return ret;
+
+ return Conf.dynmapDefaultStyle;
+ }
+
+ // Thread Safe / Asynchronous: Yes
+ public static void info(String msg)
+ {
+ String message = DYNMAP_INTEGRATION + msg;
+ System.out.println(message);
+ }
+
+ // Thread Safe / Asynchronous: Yes
+ public static void severe(String msg)
+ {
+ String message = DYNMAP_INTEGRATION + ChatColor.RED.toString() + msg;
+ System.out.println(message);
+ }
+
+ enum Direction
+ {
+ XPLUS, ZPLUS, XMINUS, ZMINUS
+ }
+
+ // Find all contiguous blocks, set in target and clear in source
+ private int floodFillTarget(TileFlags source, TileFlags destination, int x, int y)
+ {
+ int cnt = 0;
+ ArrayDeque stack = new ArrayDeque();
+ stack.push(new int[] { x, y });
+
+ while (!stack.isEmpty())
+ {
+ int[] nxt = stack.pop();
+ x = nxt[0];
+ y = nxt[1];
+ if (source.getFlag(x, y))
+ { // Set in src
+ source.setFlag(x, y, false); // Clear source
+ destination.setFlag(x, y, true); // Set in destination
+ cnt++;
+ if (source.getFlag(x + 1, y)) stack.push(new int[] { x + 1, y });
+ if (source.getFlag(x - 1, y)) stack.push(new int[] { x - 1, y });
+ if (source.getFlag(x, y + 1)) stack.push(new int[] { x, y + 1 });
+ if (source.getFlag(x, y - 1)) stack.push(new int[] { x, y - 1 });
+ }
+ }
+ return cnt;
+ }
+}
diff --git a/src/main/java/com/massivecraft/factions/integration/dynmap/TempAreaMarker.java b/src/main/java/com/massivecraft/factions/integration/dynmap/TempAreaMarker.java
new file mode 100644
index 00000000..c045d634
--- /dev/null
+++ b/src/main/java/com/massivecraft/factions/integration/dynmap/TempAreaMarker.java
@@ -0,0 +1,134 @@
+package com.massivecraft.factions.integration.dynmap;
+
+import org.dynmap.markers.AreaMarker;
+import org.dynmap.markers.MarkerSet;
+
+public class TempAreaMarker
+{
+ // -------------------------------------------- //
+ // FIELDS
+ // -------------------------------------------- //
+
+ public String label;
+ public String world;
+ public double x[];
+ public double z[];
+ public String description;
+
+ public int lineColor;
+ public double lineOpacity;
+ public int lineWeight;
+
+ public int fillColor;
+ public double fillOpacity;
+
+ public boolean boost;
+
+ // -------------------------------------------- //
+ // CREATE
+ // -------------------------------------------- //
+
+ public AreaMarker create(MarkerSet markerset, String markerId)
+ {
+ AreaMarker ret = markerset.createAreaMarker(
+ markerId,
+ this.label,
+ false,
+ this.world,
+ this.x,
+ this.z,
+ false // not persistent
+ );
+
+ if (ret == null) return null;
+
+ // Description
+ ret.setDescription(this.description);
+
+ // Line Style
+ ret.setLineStyle(this.lineWeight, this.lineOpacity, this.lineColor);
+
+ // Fill Style
+ ret.setFillStyle(this.fillOpacity, this.fillColor);
+
+ // Boost Flag
+ ret.setBoostFlag(this.boost);
+
+ return ret;
+ }
+
+ // -------------------------------------------- //
+ // UPDATE
+ // -------------------------------------------- //
+
+ public void update(AreaMarker marker)
+ {
+ // Corner Locations
+ if (!equals(marker, this.x, this.z))
+ {
+ marker.setCornerLocations(this.x, this.z);
+ }
+
+ // Label
+ if (!marker.getLabel().equals(this.label))
+ {
+ marker.setLabel(this.label);
+ }
+
+ // Description
+ if (!marker.getDescription().equals( this.description))
+ {
+ marker.setDescription(this.description);
+ }
+
+ // Line Style
+ if
+ (
+ marker.getLineWeight()!=this.lineWeight
+ ||
+ marker.getLineOpacity()!=this.lineOpacity
+ ||
+ marker.getLineColor()!=this.lineColor
+ )
+ {
+ marker.setLineStyle(this.lineWeight, this.lineOpacity, this.lineColor);
+ }
+
+ // Fill Style
+ if
+ (
+ (marker.getFillOpacity()!=this.fillOpacity)
+ ||
+ (marker.getFillColor()!=this.fillColor)
+ )
+ {
+ marker.setFillStyle(this.fillOpacity, this.fillColor);
+ }
+ // Boost Flag
+ if (marker.getBoostFlag()!=this.boost)
+ {
+ marker.setBoostFlag(this.boost);
+ }
+ }
+
+ // -------------------------------------------- //
+ // UTIL
+ // -------------------------------------------- //
+
+ public static boolean equals(AreaMarker marker, double x[], double z[])
+ {
+ int length = marker.getCornerCount();
+
+ if (x.length != length) return false;
+ if (z.length != length) return false;
+
+ for (int i = 0; i < length; i++)
+ {
+ if (marker.getCornerX(i) != x[i]) return false;
+ if (marker.getCornerZ(i) != z[i]) return false;
+ }
+
+ return true;
+ }
+
+}
diff --git a/src/main/java/com/massivecraft/factions/integration/dynmap/TempMarker.java b/src/main/java/com/massivecraft/factions/integration/dynmap/TempMarker.java
new file mode 100644
index 00000000..abf7ea00
--- /dev/null
+++ b/src/main/java/com/massivecraft/factions/integration/dynmap/TempMarker.java
@@ -0,0 +1,92 @@
+package com.massivecraft.factions.integration.dynmap;
+
+import org.dynmap.markers.Marker;
+import org.dynmap.markers.MarkerAPI;
+import org.dynmap.markers.MarkerIcon;
+import org.dynmap.markers.MarkerSet;
+
+import com.massivecraft.factions.Conf;
+
+public class TempMarker
+{
+ // -------------------------------------------- //
+ // FIELDS
+ // -------------------------------------------- //
+
+ public String label;
+ public String world;
+ public double x;
+ public double y;
+ public double z;
+ public String iconName;
+ public String description;
+
+ // -------------------------------------------- //
+ // CREATE
+ // -------------------------------------------- //
+
+ public Marker create(MarkerAPI markerApi, MarkerSet markerset, String markerId)
+ {
+ Marker ret = markerset.createMarker(
+ markerId,
+ this.label,
+ this.world,
+ this.x,
+ this.y,
+ this.z,
+ getMarkerIcon(markerApi, this.iconName),
+ false // not persistent
+ );
+
+ if (ret == null) return null;
+
+ ret.setDescription(this.description);
+
+ return ret;
+ }
+
+ // -------------------------------------------- //
+ // UPDATE
+ // -------------------------------------------- //
+
+ public void update(MarkerAPI markerApi, Marker marker)
+ {
+ if (!this.world.equals(marker.getWorld()) || this.x != marker.getX() || this.y != marker.getY() || this.z != marker.getZ())
+ {
+ marker.setLocation(
+ this.world,
+ this.x,
+ this.y,
+ this.z
+ );
+ }
+
+ if (!marker.getLabel().equals(this.label))
+ {
+ marker.setLabel(this.label);
+ }
+
+ MarkerIcon icon = getMarkerIcon(markerApi, this.iconName);
+ if (marker.getMarkerIcon()==null||marker.getMarkerIcon().equals(icon))
+ {
+ marker.setMarkerIcon(icon);
+ }
+
+ if (!marker.getDescription().equals(this.description))
+ {
+ marker.setDescription(this.description);
+ }
+ }
+
+ // -------------------------------------------- //
+ // UTIL
+ // -------------------------------------------- //
+
+ public static MarkerIcon getMarkerIcon(MarkerAPI markerApi, String name)
+ {
+ MarkerIcon ret = markerApi.getMarkerIcon(name);
+ if (ret == null) ret = markerApi.getMarkerIcon(Conf.DYNMAP_STYLE_HOME_MARKER);
+ return ret;
+ }
+
+}
diff --git a/src/main/java/com/massivecraft/factions/integration/dynmap/TempMarkerSet.java b/src/main/java/com/massivecraft/factions/integration/dynmap/TempMarkerSet.java
new file mode 100644
index 00000000..298f2997
--- /dev/null
+++ b/src/main/java/com/massivecraft/factions/integration/dynmap/TempMarkerSet.java
@@ -0,0 +1,62 @@
+package com.massivecraft.factions.integration.dynmap;
+
+import org.dynmap.markers.MarkerAPI;
+import org.dynmap.markers.MarkerSet;
+
+public class TempMarkerSet
+{
+
+ public String label;
+ public int minimumZoom;
+ public int priority;
+ public boolean hideByDefault;
+
+ public MarkerSet create(MarkerAPI markerApi, String id)
+ {
+ MarkerSet ret = markerApi.createMarkerSet(id, this.label, null, false); // ("null, false" at the end means "all icons allowed, not perisistent")
+
+ if (ret == null) return null;
+
+ // Minimum Zoom
+ if (this.minimumZoom > 0)
+ {
+ ret.setMinZoom(this.minimumZoom);
+ }
+
+ // Priority
+ ret.setLayerPriority(this.priority);
+
+ // Hide by Default
+ ret.setHideByDefault(this.hideByDefault);
+
+ return ret;
+ }
+
+ public void update(MarkerSet markerset)
+ {
+ // Name
+ if (!markerset.getMarkerSetLabel().equals(this.label))
+ {
+ markerset.setMarkerSetLabel(this.label);
+ }
+
+ if (this.minimumZoom > 0)
+ {
+ if (markerset.getMinZoom()!=this.minimumZoom)
+ {
+ markerset.setMinZoom(this.minimumZoom);
+ }
+ }
+
+ if (markerset.getLayerPriority()!=this.priority)
+ {
+ markerset.setLayerPriority(this.priority);
+ }
+
+ if (markerset.getHideByDefault()!=this.hideByDefault)
+ {
+ markerset.setHideByDefault(this.hideByDefault);
+ }
+ }
+
+}
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
index 15e8c506..4d1b5da7 100644
--- a/src/main/resources/plugin.yml
+++ b/src/main/resources/plugin.yml
@@ -2,7 +2,7 @@ name: Factions
version: ${project.version}
main: com.massivecraft.factions.P
authors: [Olof Larsson, Brett Flannigan, drtshock]
-softdepend: [PermissionsEx, Permissions, Essentials, EssentialsChat, HeroChat, iChat, LocalAreaChat, LWC, nChat, ChatManager, CAPI, AuthMe, Vault, Spout, WorldEdit, WorldGuard, AuthDB, CaptureThePoints, CombatTag]
+softdepend: [PermissionsEx, Permissions, Essentials, EssentialsChat, HeroChat, iChat, LocalAreaChat, LWC, nChat, ChatManager, CAPI, AuthMe, Vault, Spout, WorldEdit, WorldGuard, AuthDB, CaptureThePoints, CombatTag, Dynmap]
commands:
factions:
description: Reference command for Factions.
@@ -240,4 +240,4 @@ permissions:
factions.warp:
description: access your faction warps
factions.modifypower:
- description: modify other player's power
\ No newline at end of file
+ description: modify other player's power