diff --git a/src/com/massivecraft/factions/Factions.java b/src/com/massivecraft/factions/Factions.java index 686ae48d..1220235c 100644 --- a/src/com/massivecraft/factions/Factions.java +++ b/src/com/massivecraft/factions/Factions.java @@ -29,6 +29,8 @@ import com.massivecraft.factions.entity.UConfColls; import com.massivecraft.factions.entity.UPlayerColls; import com.massivecraft.factions.entity.FactionColls; import com.massivecraft.factions.entity.MConfColl; +import com.massivecraft.factions.integration.dynmap.IntegrationDynmap; +import com.massivecraft.factions.integration.dynmap.IntegrationDynmapFactions; import com.massivecraft.factions.integration.herochat.IntegrationHerochat; import com.massivecraft.factions.integration.lwc.IntegrationLwc; import com.massivecraft.factions.listeners.FactionsListenerChat; @@ -140,7 +142,9 @@ public class Factions extends MassivePlugin // Integrate this.integrate( IntegrationHerochat.get(), - IntegrationLwc.get() + IntegrationLwc.get(), + IntegrationDynmap.get(), + IntegrationDynmapFactions.get() ); // Schedule recurring non-tps-dependent tasks diff --git a/src/com/massivecraft/factions/entity/MConf.java b/src/com/massivecraft/factions/entity/MConf.java index b1800b3d..ffc54262 100644 --- a/src/com/massivecraft/factions/entity/MConf.java +++ b/src/com/massivecraft/factions/entity/MConf.java @@ -3,6 +3,7 @@ package com.massivecraft.factions.entity; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.TreeSet; @@ -12,6 +13,7 @@ import org.bukkit.entity.EntityType; import org.bukkit.event.EventPriority; import com.massivecraft.factions.Factions; +import com.massivecraft.factions.integration.dynmap.DynmapStyle; import com.massivecraft.factions.listeners.FactionsListenerChat; import com.massivecraft.massivecore.store.Entity; import com.massivecraft.massivecore.util.MUtil; @@ -246,5 +248,91 @@ public class MConf extends Entity EntityType.WITHER, EntityType.ZOMBIE ); + + // -------------------------------------------- // + // DYNMAP + // -------------------------------------------- // + + // Should the dynmap intagration be used? + public boolean dynmapUse = true; + + // Should the dynmap updates be logged to console output? + public boolean dynmapUpdateLog = false; + + // Name of the Factions layer + public String dynmapLayerName = "Factions"; + + // Should the layer be visible per default + public boolean dynmapLayerVisible = true; + + // Ordering priority in layer menu (low goes before high - default is 0) + public int dynmapLayerPriority = 2; + + // (optional) set minimum zoom level before layer is visible (0 = defalt, always visible) + public int dynmapLayerMinimumZoom = 0; + + // Format for popup - substitute values for macros + //public String dynmapInfowindowFormat = "
%regionname%
Flags
%flags%
"; + public String dynmapDescription = + "
\n" + + "%name%
\n" + + "%description%
\n" + + "
\n" + + "Leader: %players.leader%
\n" + + "Officers: %players.officers.count%
\n" + + "Members: %players.members.count%
\n" + + "Recruits: %players.recruits.count%
\n" + + "TOTAL: %players.count%
\n" + + "
\n" + + "Age: %age%
\n" + + "Bank: %money%
\n" + + "
\n" + + "Flags:
\n" + + "%open.color% | %permanent.color% | %peaceful.color% | %infpower.color% | %powerloss.color%
\n" + + "%pvp.color% | %friendlyfire.color% | %monsters.color% | %explosions.color%
\n" + + "%offlineexplosions.color% | %firespread.color% | %endergrief.color%\n" + + "
"; + + // Enable the %money% macro. Only do this if you know your economy manager is thread safe. + public boolean dynmapDescriptionMoney = false; + + // Allow players in faction to see one another on Dynmap (only relevant if Dynmap has 'player-info-protected' enabled) + public 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 Set dynmapVisibleFactions = new LinkedHashSet(); + + // 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 Set dynmapHiddenFactions = new LinkedHashSet(); + + // Region Style + public final static transient String DYNMAP_STYLE_LINE_COLOR = "#00FF00"; + public final static transient double DYNMAP_STYLE_LINE_OPACITY = 0.8D; + public final static transient int DYNMAP_STYLE_LINE_WEIGHT = 3; + public final static transient String DYNMAP_STYLE_FILL_COLOR = "#00FF00"; + public final static transient double DYNMAP_STYLE_FILL_OPACITY = 0.35D; + public final static transient String DYNMAP_STYLE_HOME_MARKER = "greenflag"; + public final static transient boolean DYNMAP_STYLE_BOOST = false; + + public 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 Map dynmapFactionStyles = MUtil.map( + "SafeZone", new DynmapStyle().setStrokeColor("#FF00FF").setFillColor("#FF00FF").setBoost(false), + "WarZone", new DynmapStyle().setStrokeColor("#FF0000").setFillColor("#FF0000").setBoost(false) + ); } \ No newline at end of file diff --git a/src/com/massivecraft/factions/integration/dynmap/DynmapStyle.java b/src/com/massivecraft/factions/integration/dynmap/DynmapStyle.java new file mode 100644 index 00000000..e5562250 --- /dev/null +++ b/src/com/massivecraft/factions/integration/dynmap/DynmapStyle.java @@ -0,0 +1,70 @@ +package com.massivecraft.factions.integration.dynmap; + +import com.massivecraft.factions.entity.MConf; + +public class DynmapStyle +{ + // -------------------------------------------- // + // FIELDS + // -------------------------------------------- // + + public String lineColor = null; + public int getLineColor() { return getColor(coalesce(this.lineColor, MConf.get().dynmapDefaultStyle.lineColor, MConf.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, MConf.get().dynmapDefaultStyle.lineOpacity, MConf.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, MConf.get().dynmapDefaultStyle.lineWeight, MConf.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, MConf.get().dynmapDefaultStyle.fillColor, MConf.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, MConf.get().dynmapDefaultStyle.fillOpacity, MConf.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, MConf.get().dynmapDefaultStyle.homeMarker, MConf.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, MConf.get().dynmapDefaultStyle.boost, MConf.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 nfx) + { + + } + return ret; + } + +} diff --git a/src/com/massivecraft/factions/integration/dynmap/EngineDynmap.java b/src/com/massivecraft/factions/integration/dynmap/EngineDynmap.java new file mode 100644 index 00000000..6da14517 --- /dev/null +++ b/src/com/massivecraft/factions/integration/dynmap/EngineDynmap.java @@ -0,0 +1,959 @@ +package com.massivecraft.factions.integration.dynmap; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.plugin.Plugin; +import org.dynmap.DynmapAPI; +import org.dynmap.markers.AreaMarker; +import org.dynmap.markers.Marker; +import org.dynmap.markers.MarkerAPI; +import org.dynmap.markers.MarkerSet; +import org.dynmap.markers.PlayerSet; +import org.dynmap.utils.TileFlags; + +import com.massivecraft.factions.FFlag; +import com.massivecraft.factions.Factions; +import com.massivecraft.factions.Rel; +import com.massivecraft.factions.TerritoryAccess; +import com.massivecraft.factions.entity.Board; +import com.massivecraft.factions.entity.BoardColl; +import com.massivecraft.factions.entity.BoardColls; +import com.massivecraft.factions.entity.Faction; +import com.massivecraft.factions.entity.FactionColl; +import com.massivecraft.factions.entity.FactionColls; +import com.massivecraft.factions.entity.MConf; +import com.massivecraft.factions.entity.UConf; +import com.massivecraft.factions.entity.UPlayer; +import com.massivecraft.massivecore.EngineAbstract; +import com.massivecraft.massivecore.money.Money; +import com.massivecraft.massivecore.ps.PS; +import com.massivecraft.massivecore.util.TimeDiffUtil; +import com.massivecraft.massivecore.util.TimeUnit; +import com.massivecraft.massivecore.util.Txt; + +// This source code is a heavily modified version of mikeprimms plugin Dynmap-Factions. +public class EngineDynmap extends EngineAbstract +{ + // -------------------------------------------- // + // CONSTANTS + // -------------------------------------------- // + + public final static int BLOCKS_PER_CHUNK = 16; + + public final static String DYNMAP_INTEGRATION = Txt.parse("Dynmap Integration: "); + + 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 + "_"; + + public final static String FACTIONS_AREA = FACTIONS_ + "area"; + public final static String FACTIONS_AREA_ = FACTIONS_AREA + "_"; + + // -------------------------------------------- // + // INSTANCE & CONSTRUCT + // -------------------------------------------- // + + private static EngineDynmap i = new EngineDynmap(); + public static EngineDynmap get() { return i; } + private EngineDynmap() {} + + // -------------------------------------------- // + // OVERRIDE + // -------------------------------------------- // + + @Override + public Plugin getPlugin() + { + return Factions.get(); + } + + @Override + public Long getPeriod() + { + // Every 15 seconds + return 15 * 20L; + } + + @Override + public boolean isSync() + { + return false; + } + + // -------------------------------------------- // + // FIELDS + // -------------------------------------------- // + + public DynmapAPI dynmapApi; + public MarkerAPI markerApi; + public MarkerSet markerset; + + // -------------------------------------------- // + // RUN: UPDATE + // -------------------------------------------- // + + // Thread Safe / Asynchronous: Yes + @Override + public void run() + { + // Should we even use dynmap? + if (!MConf.get().dynmapUse) + { + if (this.markerset != null) + { + this.markerset.deleteMarkerSet(); + this.markerset = null; + } + return; + } + + long before = System.currentTimeMillis(); + + // We do what we can here. + // You /can/ run this method from the main server thread but it's not recommended at all. + // This method is supposed to be run async to avoid locking the main server thread. + final Map homes = createHomes(); + final Map areas = createAreas(); + final Map> playerSets = createPlayersets(); + + long after = System.currentTimeMillis(); + long duration = after-before; + updateLog("Async", duration); + + // Shedule non thread safe sync at the end! + Bukkit.getScheduler().scheduleSyncDelayedTask(Factions.get(), new Runnable() + { + @Override + public void run() + { + long before = System.currentTimeMillis(); + + 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); + + long after = System.currentTimeMillis(); + long duration = after-before; + updateLog("Sync", duration); + } + }); + } + + // Thread Safe / Asynchronous: Yes + public static void updateLog(String name, long millis) + { + if (!MConf.get().dynmapUpdateLog) return; + String message = Txt.parse("%s took %dms.", "Faction Dynmap " + name, millis); + Factions.get().log(message); + } + + // -------------------------------------------- // + // UPDATE: CORE + // -------------------------------------------- // + + // 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; + } + + // -------------------------------------------- // + // UPDATE: Layer + // -------------------------------------------- // + + // Thread Safe / Asynchronous: Yes + public TempMarkerSet createLayer() + { + TempMarkerSet ret = new TempMarkerSet(); + ret.label = MConf.get().dynmapLayerName; + ret.minimumZoom = MConf.get().dynmapLayerMinimumZoom; + ret.priority = MConf.get().dynmapLayerPriority; + ret.hideByDefault = !MConf.get().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.markerApi, this.markerset); + } + return true; + } + + // -------------------------------------------- // + // UPDATE: HOMES + // -------------------------------------------- // + + // Thread Safe / Asynchronous: Yes + public Map createHomes() + { + Map ret = new HashMap(); + + // Loop current factions + for (FactionColl coll : FactionColls.get().getColls()) + { + for (Faction faction : coll.getAll()) + { + PS 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.getName()); + temp.world = ps.getWorld(); + temp.x = ps.getLocationX(); + temp.y = ps.getLocationY(); + temp.z = ps.getLocationZ(); + 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, this.markerset, 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>>(); + for (BoardColl coll : BoardColls.get().getColls()) + { + // Note: The board is the world. The board id is the world name. + for (Board board : coll.getAll()) + { + String world = board.getId(); + + // Get the factionChunks creatively. + Map> factionChunks = worldFactionChunks.get(world); + if (factionChunks == null) + { + factionChunks = new HashMap>(); + worldFactionChunks.put(world, factionChunks); + } + + // Populate the factionChunks + for (Entry entry : board.getMap().entrySet()) + { + PS chunk = entry.getKey(); + TerritoryAccess territoryAccess = entry.getValue(); + String factionId = territoryAccess.getHostFactionId(); + Faction faction = FactionColls.get().getForWorld(world).get(factionId); + if (faction == null) continue; + + // Get the chunks creatively. + Set chunks = factionChunks.get(faction); + if (chunks == null) + { + chunks = new HashSet(); + factionChunks.put(faction, chunks); + } + + chunks.add(chunk); + } + } + } + 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 (PS chunk : chunks) + { + allChunkFlags.setFlag(chunk.getChunkX(), chunk.getChunkZ(), 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 (PS chunk : allChunks) + { + int chunkX = chunk.getChunkX(); + int chunkZ = chunk.getChunkZ(); + + // 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.getName(); + 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.markerApi, this.markerset, markerId); + if (marker == null) + { + severe("Could not get/create the area marker " + markerId); + } + } + else + { + temp.update(this.markerApi, this.markerset, 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 (UPlayer uplayer : faction.getUPlayers()) + { + // NOTE: We add both UUID and name. This might be a good idea for future proofing. + ret.add(uplayer.getId()); + ret.add(uplayer.getName()); + } + + return ret; + } + + // Thread Safe / Asynchronous: Yes + public Map> createPlayersets() + { + if (!MConf.get().dynmapVisibilityByFaction) return null; + + Map> ret = new HashMap>(); + + for (FactionColl coll : FactionColls.get().getColls()) + { + for (Faction faction : coll.getAll()) + { + 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) + { + // Remove + for (PlayerSet set : this.markerApi.getPlayerSets()) + { + if (!set.getSetID().startsWith(FACTIONS_PLAYERSET_)) continue; + + // (Null means remove all) + if (playersets != null && 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 = "
" + MConf.get().dynmapDescription + "
"; + + // Name + String name = faction.getName(); + 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); + + // Age + long ageMillis = faction.getCreatedAtMillis() - System.currentTimeMillis(); + LinkedHashMap ageUnitcounts = TimeDiffUtil.limit(TimeDiffUtil.unitcounts(ageMillis, TimeUnit.getAllButMillisSecondsAndMinutes()), 3); + String age = TimeDiffUtil.formatedVerboose(ageUnitcounts, ""); + age = ChatColor.stripColor(age); + ret = ret.replace("%age%", age); + + // Money + String money = "unavailable"; + if (UConf.get(faction).bankEnabled && MConf.get().dynmapDescriptionMoney) + { + money = Money.format(Money.get(faction)); + } + ret = ret.replace("%money%", money); + + // Flags and Open + Map flags = new HashMap(); + flags.put("open", faction.isOpen()); + for (FFlag fflag : FFlag.values()) + { + flags.put(fflag.getNicename(), faction.getFlag(fflag)); + } + for (Entry entry : flags.entrySet()) + { + String flag = entry.getKey(); + boolean value = entry.getValue(); + + String bool = String.valueOf(value); + String color = boolcolor(flag, value); + String boolcolor = boolcolor(String.valueOf(value), value); + + ret = ret.replace("%" + flag + ".bool%", bool); + ret = ret.replace("%" + flag + ".color%", color); + ret = ret.replace("%" + flag + ".boolcolor%", boolcolor); + } + + // Players + List playersList = faction.getUPlayers(); + String playersCount = String.valueOf(playersList.size()); + String players = getPlayerString(playersList); + + UPlayer playersLeaderObject = faction.getLeader(); + String playersLeader = getPlayerName(playersLeaderObject); + + List playersOfficersList = faction.getUPlayersWhereRole(Rel.OFFICER); + String playersOfficersCount = String.valueOf(playersOfficersList.size()); + String playersOfficers = getPlayerString(playersOfficersList); + + List playersMembersList = faction.getUPlayersWhereRole(Rel.MEMBER); + String playersMembersCount = String.valueOf(playersMembersList.size()); + String playersMembers = getPlayerString(playersMembersList); + + List playersRecruitsList = faction.getUPlayersWhereRole(Rel.RECRUIT); + String playersRecruitsCount = String.valueOf(playersRecruitsList.size()); + String playersRecruits = getPlayerString(playersRecruitsList); + + + ret = ret.replace("%players%", players); + ret = ret.replace("%players.count%", playersCount); + ret = ret.replace("%players.leader%", playersLeader); + ret = ret.replace("%players.officers%", playersOfficers); + ret = ret.replace("%players.officers.count%", playersOfficersCount); + ret = ret.replace("%players.members%", playersMembers); + ret = ret.replace("%players.members.count%", playersMembersCount); + ret = ret.replace("%players.recruits%", playersRecruits); + ret = ret.replace("%players.recruits.count%", playersRecruitsCount); + + return ret; + } + + public static String getPlayerString(List uplayers) + { + String ret = ""; + for (UPlayer uplayer : uplayers) + { + if (ret.length() > 0) ret += ", "; + ret += getPlayerName(uplayer); + } + return ret; + } + + public static String getPlayerName(UPlayer uplayer) + { + if (uplayer == null) return "none"; + return escapeHtml(uplayer.getName()); + } + + public static String boolcolor(String string, boolean bool) + { + return "" + string + ""; + } + + 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.getName(); + if (factionName == null) return false; + + Set visible = MConf.get().dynmapVisibleFactions; + Set hidden = MConf.get().dynmapHiddenFactions; + + if (visible.size() > 0) + { + if (!visible.contains(factionId) && !visible.contains(factionName) && !visible.contains("world:" + world)) + { + return false; + } + } + + if (hidden.size() > 0) + { + 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 = MConf.get().dynmapFactionStyles.get(faction.getId()); + if (ret != null) return ret; + + ret = MConf.get().dynmapFactionStyles.get(faction.getName()); + if (ret != null) return ret; + + return MConf.get().dynmapDefaultStyle; + } + + // Thread Safe / Asynchronous: Yes + public static void info(String msg) + { + String message = DYNMAP_INTEGRATION + msg; + Factions.get().log(message); + } + + // Thread Safe / Asynchronous: Yes + public static void severe(String msg) + { + String message = DYNMAP_INTEGRATION + ChatColor.RED.toString() + msg; + Factions.get().log(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() == false) + { + 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/com/massivecraft/factions/integration/dynmap/IntegrationDynmap.java b/src/com/massivecraft/factions/integration/dynmap/IntegrationDynmap.java new file mode 100644 index 00000000..9fa4d64c --- /dev/null +++ b/src/com/massivecraft/factions/integration/dynmap/IntegrationDynmap.java @@ -0,0 +1,31 @@ +package com.massivecraft.factions.integration.dynmap; + +import com.massivecraft.massivecore.integration.IntegrationAbstract; + +public class IntegrationDynmap extends IntegrationAbstract +{ + // -------------------------------------------- // + // INSTANCE & CONSTRUCT + // -------------------------------------------- // + + private static IntegrationDynmap i = new IntegrationDynmap(); + public static IntegrationDynmap get() { return i; } + private IntegrationDynmap() { super("dynmap"); } + + // -------------------------------------------- // + // OVERRIDE + // -------------------------------------------- // + + @Override + public void activate() + { + EngineDynmap.get().activate(); + } + + @Override + public void deactivate() + { + EngineDynmap.get().deactivate(); + } + +} diff --git a/src/com/massivecraft/factions/integration/dynmap/IntegrationDynmapFactions.java b/src/com/massivecraft/factions/integration/dynmap/IntegrationDynmapFactions.java new file mode 100644 index 00000000..20080310 --- /dev/null +++ b/src/com/massivecraft/factions/integration/dynmap/IntegrationDynmapFactions.java @@ -0,0 +1,44 @@ +package com.massivecraft.factions.integration.dynmap; + +import org.bukkit.Bukkit; +import org.bukkit.plugin.Plugin; + +import com.massivecraft.factions.Factions; +import com.massivecraft.massivecore.integration.IntegrationAbstract; +import com.massivecraft.massivecore.util.Txt; + +public class IntegrationDynmapFactions extends IntegrationAbstract +{ + // -------------------------------------------- // + // INSTANCE & CONSTRUCT + // -------------------------------------------- // + + private static IntegrationDynmapFactions i = new IntegrationDynmapFactions(); + public static IntegrationDynmapFactions get() { return i; } + private IntegrationDynmapFactions() { super("Dynmap-Factions"); } + + // -------------------------------------------- // + // OVERRIDE + // -------------------------------------------- // + + @Override + public void activate() + { + // Time for an error message! + Bukkit.getScheduler().scheduleSyncDelayedTask(Factions.get(), new Runnable() + { + @Override + public void run() + { + Factions.get().log(Txt.parse("I see you have the plugin Dynmap-Factions installed!")); + Factions.get().log(Txt.parse("That plugin is no longer required for Dynmap features.")); + Factions.get().log(Txt.parse("Factions now ship with it's own Dynmap integration.")); + Factions.get().log(Txt.parse("Now disabling Dynmap-Factions for you:")); + + Plugin plugin = Bukkit.getPluginManager().getPlugin("Dynmap-Factions"); + Bukkit.getPluginManager().disablePlugin(plugin); + } + }); + } + +} diff --git a/src/com/massivecraft/factions/integration/dynmap/TempAreaMarker.java b/src/com/massivecraft/factions/integration/dynmap/TempAreaMarker.java new file mode 100644 index 00000000..e5ebf28b --- /dev/null +++ b/src/com/massivecraft/factions/integration/dynmap/TempAreaMarker.java @@ -0,0 +1,138 @@ +package com.massivecraft.factions.integration.dynmap; + +import org.dynmap.markers.AreaMarker; +import org.dynmap.markers.MarkerAPI; +import org.dynmap.markers.MarkerSet; + +import com.massivecraft.massivecore.util.MUtil; + +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(MarkerAPI markerApi, 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(MarkerAPI markerApi, MarkerSet markerset, AreaMarker marker) + { + // Corner Locations + if (!equals(marker, this.x, this.z)) + { + marker.setCornerLocations(this.x, this.z); + } + + // Label + if (!MUtil.equals(marker.getLabel(), this.label)) + { + marker.setLabel(this.label); + } + + // Description + if (!MUtil.equals(marker.getDescription(), this.description)) + { + marker.setDescription(this.description); + } + + // Line Style + if + ( + !MUtil.equals(marker.getLineWeight(), this.lineWeight) + || + !MUtil.equals(marker.getLineOpacity(), this.lineOpacity) + || + !MUtil.equals(marker.getLineColor(), this.lineColor) + ) + { + marker.setLineStyle(this.lineWeight, this.lineOpacity, this.lineColor); + } + + // Fill Style + if + ( + !MUtil.equals(marker.getFillOpacity(), this.fillOpacity) + || + !MUtil.equals(marker.getFillColor(), this.fillColor) + ) + { + marker.setFillStyle(this.fillOpacity, this.fillColor); + } + + // Boost Flag + if (!MUtil.equals(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/com/massivecraft/factions/integration/dynmap/TempMarker.java b/src/com/massivecraft/factions/integration/dynmap/TempMarker.java new file mode 100644 index 00000000..ce0a9980 --- /dev/null +++ b/src/com/massivecraft/factions/integration/dynmap/TempMarker.java @@ -0,0 +1,102 @@ +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.entity.MConf; +import com.massivecraft.massivecore.util.MUtil; + +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, MarkerSet markerset, Marker marker) + { + if + ( + marker.getWorld() != this.world + || + marker.getX() != this.x + || + marker.getY() != this.y + || + marker.getZ() != this.z + ) + { + marker.setLocation( + this.world, + this.x, + this.y, + this.z + ); + } + + if (!MUtil.equals(marker.getLabel(), this.label)) + { + marker.setLabel(this.label); + } + + MarkerIcon icon = getMarkerIcon(markerApi, this.iconName); + if (!MUtil.equals(marker.getMarkerIcon(), icon)) + { + marker.setMarkerIcon(icon); + } + + if (!MUtil.equals(marker.getDescription(), 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(MConf.DYNMAP_STYLE_HOME_MARKER); + return ret; + } + +} diff --git a/src/com/massivecraft/factions/integration/dynmap/TempMarkerSet.java b/src/com/massivecraft/factions/integration/dynmap/TempMarkerSet.java new file mode 100644 index 00000000..50390e61 --- /dev/null +++ b/src/com/massivecraft/factions/integration/dynmap/TempMarkerSet.java @@ -0,0 +1,78 @@ +package com.massivecraft.factions.integration.dynmap; + +import org.dynmap.markers.MarkerAPI; +import org.dynmap.markers.MarkerSet; + +import com.massivecraft.massivecore.util.MUtil; + +public class TempMarkerSet +{ + // -------------------------------------------- // + // FIELDS + // -------------------------------------------- // + + public String label; + public int minimumZoom; + public int priority; + public boolean hideByDefault; + + // -------------------------------------------- // + // CREATE + // -------------------------------------------- // + + 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; + } + + // -------------------------------------------- // + // UPDATE + // -------------------------------------------- // + + public void update(MarkerAPI markerApi, MarkerSet markerset) + { + // Name + if (!MUtil.equals(markerset.getMarkerSetLabel(), this.label)) + { + markerset.setMarkerSetLabel(this.label); + } + + // Minimum Zoom + if (this.minimumZoom > 0) + { + if (!MUtil.equals(markerset.getMinZoom(), this.minimumZoom)) + { + markerset.setMinZoom(this.minimumZoom); + } + } + + // Priority + if (!MUtil.equals(markerset.getLayerPriority(), this.priority)) + { + markerset.setLayerPriority(this.priority); + } + + // Hide by Default + if (!MUtil.equals(markerset.getHideByDefault(), this.hideByDefault)) + { + markerset.setHideByDefault(this.hideByDefault); + } + } + +}