diff --git a/pom.xml b/pom.xml index 59592400..10413704 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.massivecraft Factions - 1.6.9.5-U0.1.12 + 1.6.9.5-U0.1.13-SNAPSHOT jar Factions @@ -110,6 +110,18 @@ fanciful 0.2.1-SNAPSHOT + + org.dynmap + dynmap + 2.0 + provided + + + com.google.guava + guava + 10.0.1 + provided + 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