diff --git a/src/main/java/net/redstoneore/legacyfactions/Factions.java b/src/main/java/net/redstoneore/legacyfactions/Factions.java index dde43f8f..a8081d1d 100644 --- a/src/main/java/net/redstoneore/legacyfactions/Factions.java +++ b/src/main/java/net/redstoneore/legacyfactions/Factions.java @@ -39,6 +39,7 @@ import net.redstoneore.legacyfactions.flag.Flags; import net.redstoneore.legacyfactions.integration.Integrations; import net.redstoneore.legacyfactions.integration.bstats.BStatsIntegration; +import net.redstoneore.legacyfactions.integration.dynmap.DynmapIntegration; import net.redstoneore.legacyfactions.integration.essentials.EssentialsIntegration; import net.redstoneore.legacyfactions.integration.metrics.MetricsIntegration; import net.redstoneore.legacyfactions.integration.novucsftop.NovucsFactionsTopIntegration; @@ -234,7 +235,8 @@ public void enable() throws Exception { MetricsIntegration.get(), BStatsIntegration.get(), VentureChatIntegration.get(), - NovucsFactionsTopIntegration.get() + NovucsFactionsTopIntegration.get(), + DynmapIntegration.get() ); // Sync expansions diff --git a/src/main/java/net/redstoneore/legacyfactions/Lang.java b/src/main/java/net/redstoneore/legacyfactions/Lang.java index d63f4b75..b3ac83be 100644 --- a/src/main/java/net/redstoneore/legacyfactions/Lang.java +++ b/src/main/java/net/redstoneore/legacyfactions/Lang.java @@ -904,6 +904,11 @@ public enum Lang { WARMUPS_ALREADY("&cYou are already warming up."), WARMUPS_CANCELLED("&cYou have cancelled your warmup."), + // -------------------------------------------------- // + // Integration: Dynmap + // -------------------------------------------------- // + + DYNMAP_TITLE("Dynmap Integration: "), // -------------------------------------------------- // // Expansion: FactionsFly diff --git a/src/main/java/net/redstoneore/legacyfactions/entity/Conf.java b/src/main/java/net/redstoneore/legacyfactions/entity/Conf.java index 9535f88a..a73b450d 100644 --- a/src/main/java/net/redstoneore/legacyfactions/entity/Conf.java +++ b/src/main/java/net/redstoneore/legacyfactions/entity/Conf.java @@ -10,6 +10,7 @@ import net.redstoneore.legacyfactions.entity.persist.Persist; import net.redstoneore.legacyfactions.expansion.chat.FactionsChatConfig; import net.redstoneore.legacyfactions.expansion.fly.FactionsFlyConfig; +import net.redstoneore.legacyfactions.integration.dynmap.DynmapConfig; import net.redstoneore.legacyfactions.util.MiscUtil; import net.redstoneore.legacyfactions.util.cross.CrossEntityType; import net.redstoneore.legacyfactions.util.cross.CrossMaterial; @@ -1055,6 +1056,12 @@ public class Conf { public static Set worldsIgnorePvP = new LinkedHashSet<>(); public static Set worldsNoWildernessProtection = new LinkedHashSet<>(); + // -------------------------------------------------- // + // INTEGRATION: DYNMAP + // -------------------------------------------------- // + + public static DynmapConfig dynmap = new DynmapConfig(); + // -------------------------------------------------- // // BUFFERS // -------------------------------------------------- // diff --git a/src/main/java/net/redstoneore/legacyfactions/integration/dynmap/DynmapConfig.java b/src/main/java/net/redstoneore/legacyfactions/integration/dynmap/DynmapConfig.java new file mode 100644 index 00000000..c06df884 --- /dev/null +++ b/src/main/java/net/redstoneore/legacyfactions/integration/dynmap/DynmapConfig.java @@ -0,0 +1,69 @@ +package net.redstoneore.legacyfactions.integration.dynmap; + +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import net.redstoneore.legacyfactions.util.MiscUtil; + +public class DynmapConfig { + + // -------------------------------------------------- // + // STATIC FIELDS + // -------------------------------------------------- // + + 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; + + // -------------------------------------------------- // + // FIELDS + // -------------------------------------------------- // + + public boolean enabled = true; + + public String layerName = "Factions"; + + public boolean layerVisible = true; + + public int layerPriority = 2; + + public int layerMinimumZoom = 0; + + public String description = + "
\n" + + "%factions_faction_name%
\n" + + "%factions_faction_description_blankwild%
\n" + + "
\n" + + "Leader: %factions_faction_admin%
\n" + + "Members: %factions_faction_count_members%
\n" + + "
"; + + public boolean descriptionMoney = false; + + public boolean visibilityByFaction = true; + + public Set visibleFactions = new LinkedHashSet<>(); + + public Set hiddenFactions = new LinkedHashSet<>(); + + public DynmapStyle defaultStyle = 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 factionStyles = MiscUtil.newMap( + "SafeZone", new DynmapStyle().setStrokeColor("#FF00FF").setFillColor("#FF00FF").setBoost(false), + "WarZone", new DynmapStyle().setStrokeColor("#FF0000").setFillColor("#FF0000").setBoost(false) + ); +} diff --git a/src/main/java/net/redstoneore/legacyfactions/integration/dynmap/DynmapEngine.java b/src/main/java/net/redstoneore/legacyfactions/integration/dynmap/DynmapEngine.java new file mode 100644 index 00000000..10df8d2e --- /dev/null +++ b/src/main/java/net/redstoneore/legacyfactions/integration/dynmap/DynmapEngine.java @@ -0,0 +1,570 @@ +package net.redstoneore.legacyfactions.integration.dynmap; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.scheduler.BukkitRunnable; +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 net.redstoneore.legacyfactions.Factions; +import net.redstoneore.legacyfactions.entity.Conf; +import net.redstoneore.legacyfactions.entity.Faction; +import net.redstoneore.legacyfactions.entity.FactionColl; +import net.redstoneore.legacyfactions.integration.dynmap.marker.TempAreaMarker; +import net.redstoneore.legacyfactions.integration.dynmap.marker.TempMarker; +import net.redstoneore.legacyfactions.integration.dynmap.marker.TempMarkerSet; +import net.redstoneore.legacyfactions.integration.dynmap.util.DynmapUtil; +import net.redstoneore.legacyfactions.locality.Locality; +import net.redstoneore.legacyfactions.placeholder.FactionsPlaceholderFaction; +import net.redstoneore.legacyfactions.placeholder.FactionsPlaceholders; + +public class DynmapEngine extends BukkitRunnable { + + // -------------------------------------------------- // + // INSTANCE + // -------------------------------------------------- // + + private static DynmapEngine instance = new DynmapEngine(); + public static DynmapEngine get() { return instance; } + + // -------------------------------------------------- // + // FIELDS + // -------------------------------------------------- // + + public DynmapAPI dynmapApi; + public MarkerAPI markerApi; + public MarkerSet markerset; + + // -------------------------------------------------- // + // METHODS + // -------------------------------------------------- // + + @Override + public void run() { + // Make sure we're not on the main thread + if (Bukkit.isPrimaryThread()) { + this.cancel(); + this.runTaskTimerAsynchronously(Factions.get(), 10, 20 * 15); + return; + } + // Async + + // If we're not enabled, issue a cleanup + if (!Conf.dynmap.enabled) { + this.cleanup(); + return; + } + + final Map homes = this.createHomes(); + final Map areas = this.createAreas(); + final Map> playerSets = createPlayersets(); + + // Go sync + Bukkit.getScheduler().scheduleSyncDelayedTask(Factions.get(), () -> { + // Sync + this.dynmapApi = (DynmapAPI) Bukkit.getPluginManager().getPlugin("dynmap"); + if (this.dynmapApi == null) { + Factions.get().warn("Could not retrieve the DynmapAPI."); + return; + } + + this.markerApi = this.dynmapApi.getMarkerAPI(); + if (this.markerApi == null) { + Factions.get().warn("Could not retrieve the MarkerAPI."); + return; + } + + if (!this.updateLayer(this.createLayer())) return; + + this.updateHomes(homes); + this.updateAreas(areas); + this.updatePlayersets(playerSets); + }); + + } + + private boolean isVisible(Faction faction) { + if (Conf.dynmap.hiddenFactions.contains(faction.getId())) return false; + + if (Conf.dynmap.visibleFactions.size() > 0 && !Conf.dynmap.visibleFactions.contains(faction.getId())) { + return false; + } + + return true; + } + + private Map createHomes() { + Map homes = new HashMap<>(); + + if (!Conf.homesEnabled) return homes; + + FactionColl.all(faction -> { + Location home = faction.getHome(); + if (home == null) return; + + if (Conf.dynmap.hiddenFactions.contains(faction.getId())) return; + + if (Conf.dynmap.visibleFactions.size() > 0 && !Conf.dynmap.visibleFactions.contains(faction.getId())) { + return; + } + + DynmapStyle style = DynmapUtil.getStyle(faction); + + String markerId = DynmapIntegration.FACTIONS_HOME + "_" + faction.getId(); + + TempMarker marker = new TempMarker(); + marker.label = ChatColor.stripColor(faction.getTag()); + marker.world = home.getWorld().getName(); + marker.x = home.getX(); + marker.y = home.getY(); + marker.z = home.getZ(); + marker.iconName = style.getHomeMarker(); + marker.description = this.getDescription(faction); + + homes.put(markerId, marker); + }); + + return homes; + } + + private void updateHomes(Map homes) { + // Put all current faction markers in a map + Map markers = new HashMap<>(); + this.markerset.getMarkers().forEach(marker -> markers.put(marker.getMarkerID(), marker)); + + // Loop homes + homes.entrySet().forEach(entry -> { + 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) { + Optional tempMarker = temp.create(this.markerApi, this.markerset, markerId); + if (!tempMarker.isPresent()) { + Factions.get().warn("Could not get/create the home marker " + markerId); + } + marker = tempMarker.get(); + } else { + temp.update(this.markerApi, this.markerset, marker); + } + }); + + markers.values().forEach(Marker::deleteMarker); + } + + + private TempMarkerSet createLayer() { + // Async + TempMarkerSet ret = new TempMarkerSet(); + ret.label = Conf.dynmap.layerName; + ret.minimumZoom = Conf.dynmap.layerMinimumZoom; + ret.priority = Conf.dynmap.layerPriority; + ret.hideByDefault = !Conf.dynmap.layerVisible; + return ret; + } + + private boolean updateLayer(TempMarkerSet temp) { + // Sync + this.markerset = this.markerApi.getMarkerSet(DynmapIntegration.FACTIONS_MARKERSET); + if (this.markerset == null) { + Optional markerSet = temp.create(this.markerApi, DynmapIntegration.FACTIONS_MARKERSET); + if (!markerSet.isPresent()) { + Factions.get().warn("Could not create the Faction Markerset/Layer"); + return false; + } + this.markerset = markerSet.get(); + } else { + temp.update(this.markerApi, this.markerset); + } + return true; + } + + private Map createAreas() { + Map>> worldFactionChunks = createWorldFactionChunks(); + return this.createAreas(worldFactionChunks); + } + + private Map>> createWorldFactionChunks() { + Map>> worldFactionChunks = new HashMap<>(); + + Bukkit.getWorlds().forEach(world -> { + String worldName = world.getName(); + + if (!worldFactionChunks.containsKey(worldName)) { + worldFactionChunks.put(worldName, new ConcurrentHashMap<>()); + } + + Map> worldClaims = worldFactionChunks.get(worldName); + + FactionColl.get().getAll(world).forEach(faction -> { + + if (!worldClaims.containsKey(faction)) { + worldClaims.put(faction, new HashSet<>()); + } + + Set factionClaims = worldClaims.get(faction); + + faction.getAllClaims().stream() + .filter(flocation -> flocation.getWorld().getName() == worldName) + .forEach(flocation -> factionClaims.add(Locality.of(flocation.getChunk()))); + }); + }); + + return worldFactionChunks; + } + + private Map createAreas(Map>> worldFactionChunks) { + Map areas = new HashMap(); + + // For each world + worldFactionChunks.entrySet().forEach(entry -> { + String world = entry.getKey(); + Map> factionChunks = entry.getValue(); + factionChunks.entrySet().forEach(chunkSet -> { + Faction faction = chunkSet.getKey(); + Set chunks = chunkSet.getValue(); + Map worldFactionMarkers = this.createAreas(world, faction, chunks); + areas.putAll(worldFactionMarkers); + + }); + }); + + return areas; + } + + private Map createAreas(String world, Faction faction, Set chunks) { + Map ret = new HashMap<>(); + + if (!this.isVisible(faction) || chunks.isEmpty()) { + return ret; + } + + // Index of polygon for given faction + int markerIndex = 0; + + // Create the info window + String description = this.getDescription(faction); + + // Fetch Style + DynmapStyle style = DynmapUtil.getStyle(faction); + + // Loop through chunks: set flags on chunk map + TileFlags allChunkFlags = new TileFlags(); + LinkedList allChunks = new LinkedList<>(); + + for (Locality 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 (Locality chunk : allChunks) { + int chunkX = chunk.getChunkX(); + int chunkZ = chunk.getChunkZ(); + + if (ourChunkFlags == null && allChunkFlags.getFlag(chunkX, chunkZ)) { + ourChunkFlags = new TileFlags(); // Create map for shape + ourChunks = new LinkedList<>(); + this.floodFillTarget(allChunkFlags, ourChunkFlags, chunkX, chunkZ); // Copy shape + ourChunks.add(chunk); // Add it to our chunk list + minimumX = chunkX; + minimumZ = chunkZ; + } 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 { + 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; + List 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) DynmapIntegration.BLOCKS_PER_CHUNK; + z[i] = (double) line[1] * (double) DynmapIntegration.BLOCKS_PER_CHUNK; + } + + // Build information for specific area + String markerId = "faction_" + 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<>(); + this.markerset.getAreaMarkers().forEach(marker -> markers.put(marker.getMarkerID(), marker)); + + // Loop New + areas.entrySet().forEach(entry -> { + String markerId = entry.getKey(); + TempAreaMarker temp = entry.getValue(); + + AreaMarker marker = markers.remove(markerId); + if (marker == null) { + Optional tempMarker = temp.create(this.markerApi, this.markerset, markerId); + if (!tempMarker.isPresent()) { + Factions.get().warn("Could not get/create the area marker " + markerId); + } + marker = tempMarker.get(); + } else { + temp.update(this.markerApi, this.markerset, marker); + } + + }); + + markers.values().forEach(AreaMarker::deleteMarker); + } + + + /** + * Async: true + */ + public void cleanup() { + // Always run cleanup async + if (Bukkit.isPrimaryThread()) { + Bukkit.getScheduler().runTaskAsynchronously(Factions.get(), () -> { this.cleanup(); }); + return; + } + + if (this.markerset == null) return; + this.markerset.deleteMarkerSet(); + this.markerset = null; + } + + private String getDescription(Faction faction) { + String description = "
" + Conf.dynmap.description + "
"; + + for (FactionsPlaceholderFaction placeholder : FactionsPlaceholders.get().getPlaceholders(FactionsPlaceholderFaction.class)) { + description = description.replaceAll("%factions_"+placeholder.placeholder()+"%", placeholder.get(faction)); + } + + return description; + } + + enum Direction { + XPLUS, ZPLUS, XMINUS, ZMINUS + }; + + 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; + } + + public String createPlayersetId(Faction faction) { + if (faction == null) return null; + if (faction.isWilderness()) return null; + String factionId = faction.getId(); + if (factionId == null) return null; + return DynmapIntegration.FACTIONS_PLAYERSET + "_" + factionId; + } + + // Thread Safe / Asynchronous: Yes + public Set createPlayerset(Faction faction) { + if (faction == null) return null; + if (faction.isWilderness()) return null; + + Set playerSet = new HashSet<>(); + + faction.getMembers().forEach(member -> { + playerSet.add(member.getId()); + playerSet.add(member.getName()); + }); + + return playerSet; + } + + public Map> createPlayersets() { + if (!Conf.dynmap.visibilityByFaction) return null; + + Map> playersets = new HashMap<>(); + + FactionColl.all(faction -> { + String playersetId = this.createPlayersetId(faction); + if (playersetId == null) return; + Set playerIds = this.createPlayerset(faction); + if (playerIds == null) return; + playersets.put(playersetId, playerIds); + }); + return playersets; + } + + private void updatePlayersets(Map> playersets) { + // Remove + this.markerApi.getPlayerSets().stream() + .filter(set -> set.getSetID().startsWith(DynmapIntegration.FACTIONS_PLAYERSET)) + .filter(set -> playersets != null && playersets.containsKey(set.getSetID())) + .forEach(PlayerSet::deleteSet); + + // Add / Update + playersets.entrySet().stream() + .forEach(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, + true, + playerIds, + false + ); + } + if (set == null) { + Factions.get().warn("Could not get/create the player set " + setId); + return; + } + + // Set Content + set.setPlayers(playerIds); + }); + } + +} diff --git a/src/main/java/net/redstoneore/legacyfactions/integration/dynmap/DynmapIntegration.java b/src/main/java/net/redstoneore/legacyfactions/integration/dynmap/DynmapIntegration.java new file mode 100644 index 00000000..8d68b091 --- /dev/null +++ b/src/main/java/net/redstoneore/legacyfactions/integration/dynmap/DynmapIntegration.java @@ -0,0 +1,51 @@ +package net.redstoneore.legacyfactions.integration.dynmap; + +import org.bukkit.Bukkit; + +import net.redstoneore.legacyfactions.Factions; +import net.redstoneore.legacyfactions.integration.Integration; + +public class DynmapIntegration extends Integration { + + // -------------------------------------------------- // + // INSTANCE + // -------------------------------------------------- // + + private static DynmapIntegration instance = new DynmapIntegration(); + public static DynmapIntegration get() { return instance; } + + // -------------------------------------------------- // + // CONSTANTS + // -------------------------------------------------- // + + public final static int BLOCKS_PER_CHUNK = 16; + + public final static String FACTIONS_MARKERSET = "factions_markerset"; + + public final static String FACTIONS_HOME = "factions_home"; + + public final static String FACTIONS_PLAYERSET = "factions_playerset"; + + public final static String FACTIONS_AREA = "factions_area"; + + // -------------------------------------------------- // + // METHODS + // -------------------------------------------------- // + + @Override + public String getName() { + return "Dynmap"; + } + + @Override + public boolean isEnabled() { + return Bukkit.getPluginManager().isPluginEnabled("dynmap"); + } + + @Override + public void init() { + DynmapEngine.get().runTaskTimerAsynchronously(Factions.get(), 10, 20 * 15); + this.notifyEnabled(); + } + +} diff --git a/src/main/java/net/redstoneore/legacyfactions/integration/dynmap/DynmapStyle.java b/src/main/java/net/redstoneore/legacyfactions/integration/dynmap/DynmapStyle.java new file mode 100644 index 00000000..c02cafe4 --- /dev/null +++ b/src/main/java/net/redstoneore/legacyfactions/integration/dynmap/DynmapStyle.java @@ -0,0 +1,37 @@ +package net.redstoneore.legacyfactions.integration.dynmap; + +import net.redstoneore.legacyfactions.entity.Conf; +import net.redstoneore.legacyfactions.integration.dynmap.util.DynmapUtil; +import net.redstoneore.legacyfactions.util.MiscUtil; + +public class DynmapStyle { + + public String lineColor = null; + public int getLineColor() { return DynmapUtil.getColour(MiscUtil.firstNotNull(this.lineColor, Conf.dynmap.defaultStyle.lineColor, DynmapConfig.DYNMAP_STYLE_LINE_COLOR)); } + public DynmapStyle setStrokeColor(String strokeColor) { this.lineColor = strokeColor; return this; } + + public Double lineOpacity = null; + public double getLineOpacity() { return MiscUtil.firstNotNull(this.lineOpacity, Conf.dynmap.defaultStyle.lineOpacity, DynmapConfig.DYNMAP_STYLE_LINE_OPACITY); } + public DynmapStyle setLineOpacity(Double strokeOpacity) { this.lineOpacity = strokeOpacity; return this; } + + public Integer lineWeight = null; + public int getLineWeight() { return MiscUtil.firstNotNull(this.lineWeight, Conf.dynmap.defaultStyle.lineWeight, DynmapConfig.DYNMAP_STYLE_LINE_WEIGHT); } + public DynmapStyle setLineWeight(Integer strokeWeight) { this.lineWeight = strokeWeight; return this; } + + public String fillColor = null; + public int getFillColor() { return DynmapUtil.getColour(MiscUtil.firstNotNull(this.fillColor, Conf.dynmap.defaultStyle.fillColor, DynmapConfig.DYNMAP_STYLE_FILL_COLOR)); } + public DynmapStyle setFillColor(String fillColor) { this.fillColor = fillColor; return this; } + + public Double fillOpacity = null; + public double getFillOpacity() { return MiscUtil.firstNotNull(this.fillOpacity, Conf.dynmap.defaultStyle.fillOpacity, DynmapConfig.DYNMAP_STYLE_FILL_OPACITY); } + public DynmapStyle setFillOpacity(Double fillOpacity) { this.fillOpacity = fillOpacity; return this; } + + public String homeMarker = null; + public String getHomeMarker() { return MiscUtil.firstNotNull(this.homeMarker, Conf.dynmap.defaultStyle.homeMarker, DynmapConfig.DYNMAP_STYLE_HOME_MARKER); } + public DynmapStyle setHomeMarker(String homeMarker) { this.homeMarker = homeMarker; return this; } + + public Boolean boost = null; + public boolean getBoost() { return MiscUtil.firstNotNull(this.boost, Conf.dynmap.defaultStyle.boost, DynmapConfig.DYNMAP_STYLE_BOOST); } + public DynmapStyle setBoost(Boolean boost) { this.boost = boost; return this; } + +} diff --git a/src/main/java/net/redstoneore/legacyfactions/integration/dynmap/marker/TempAreaMarker.java b/src/main/java/net/redstoneore/legacyfactions/integration/dynmap/marker/TempAreaMarker.java new file mode 100644 index 00000000..c942712d --- /dev/null +++ b/src/main/java/net/redstoneore/legacyfactions/integration/dynmap/marker/TempAreaMarker.java @@ -0,0 +1,117 @@ +package net.redstoneore.legacyfactions.integration.dynmap.marker; + +import java.util.Optional; + +import org.dynmap.markers.AreaMarker; +import org.dynmap.markers.MarkerAPI; +import org.dynmap.markers.MarkerSet; + +import net.redstoneore.legacyfactions.integration.dynmap.util.DynmapUtil; + +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; + + // -------------------------------------------------- // + // METHODS + // -------------------------------------------------- // + + /** + * Create a marker + * @param markerApi The MarkerAPI + * @param markerset The MarketSet + * @param markerId The marker id + * @return an {@link Optional} containing the {@link AreaMarker} if it was a success + */ + public Optional create(MarkerAPI markerApi, MarkerSet markerset, String markerId) { + AreaMarker marker = markerset.createAreaMarker( + markerId, + this.label, + false, + this.world, + this.x, + this.z, + false + ); + + if (marker == null) return Optional.empty(); + + // Description + marker.setDescription(this.description); + + // Line Style + marker.setLineStyle(this.lineWeight, this.lineOpacity, this.lineColor); + + // Fill Style + marker.setFillStyle(this.fillOpacity, this.fillColor); + + // Boost Flag + marker.setBoostFlag(this.boost); + + return Optional.of(marker); + } + + /** + * Update a Marker + * @param markerApi The MarkerAPI + * @param markerset The MarkerSet + * @param marker The AreaMarker + */ + public void update(MarkerAPI markerApi, MarkerSet markerset, AreaMarker marker) { + // Corner Locations + if (!DynmapUtil.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 + Integer lineWeight = marker.getLineWeight(); + Double lineOpacity = marker.getLineOpacity(); + Integer lineColor = marker.getLineColor(); + + if (!lineWeight.equals(this.lineWeight) || !lineOpacity.equals(this.lineOpacity) || !lineColor.equals(this.lineColor)) { + marker.setLineStyle(this.lineWeight, this.lineOpacity, this.lineColor); + } + + // Fill Style + Double fillOpacity = marker.getFillOpacity(); + Integer fillColor = marker.getFillColor(); + + if (!fillOpacity.equals(this.fillOpacity) || !fillColor.equals(this.fillColor)) { + marker.setFillStyle(this.fillOpacity, this.fillColor); + } + + // Boost Flag + if (marker.getBoostFlag() != this.boost) { + marker.setBoostFlag(this.boost); + } + } + +} diff --git a/src/main/java/net/redstoneore/legacyfactions/integration/dynmap/marker/TempMarker.java b/src/main/java/net/redstoneore/legacyfactions/integration/dynmap/marker/TempMarker.java new file mode 100644 index 00000000..9f9c7099 --- /dev/null +++ b/src/main/java/net/redstoneore/legacyfactions/integration/dynmap/marker/TempMarker.java @@ -0,0 +1,83 @@ +package net.redstoneore.legacyfactions.integration.dynmap.marker; + +import java.util.Optional; + +import org.dynmap.markers.Marker; +import org.dynmap.markers.MarkerAPI; +import org.dynmap.markers.MarkerIcon; +import org.dynmap.markers.MarkerSet; + +import net.redstoneore.legacyfactions.integration.dynmap.util.DynmapUtil; + +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; + + // -------------------------------------------------- // + // METHODS + // -------------------------------------------------- // + + /** + * Create a marker. + * @param markerApi The MarkerAPI + * @param markerSet The MarkerSet + * @param markerId The Marker id + * @return an {@link Optional} contain the {@link Marker} + */ + public Optional create(MarkerAPI markerApi, MarkerSet markerSet, String markerId) { + Marker marker = markerSet.createMarker( + markerId, + this.label, + this.world, + this.x, + this.y, + this.z, + DynmapUtil.getMarkerIcon(markerApi, this.iconName), + false + ); + + if (marker == null) return Optional.empty(); + + marker.setDescription(this.description); + + return Optional.of(marker); + } + + public void update(MarkerAPI markerApi, MarkerSet markerset, Marker marker) { + if (!this.equals(marker)) { + marker.setLocation(this.world, this.x, this.y, this.z ); + } + + if (!marker.getLabel().equalsIgnoreCase(this.label)) { + marker.setLabel(this.label); + } + + MarkerIcon icon = DynmapUtil.getMarkerIcon(markerApi, this.iconName); + if (!marker.getMarkerIcon().equals(icon)) { + marker.setMarkerIcon(icon); + } + + if (!marker.getDescription().equals(this.description)) { + marker.setDescription(this.description); + } + } + + // -------------------------------------------- // + // UTIL + // -------------------------------------------- // + + public boolean equals(Marker marker) { + return marker.getWorld() == this.world && marker.getX() == this.x && marker.getY() == this.y && marker.getZ() == this.z; + } + +} diff --git a/src/main/java/net/redstoneore/legacyfactions/integration/dynmap/marker/TempMarkerSet.java b/src/main/java/net/redstoneore/legacyfactions/integration/dynmap/marker/TempMarkerSet.java new file mode 100644 index 00000000..d20fc20d --- /dev/null +++ b/src/main/java/net/redstoneore/legacyfactions/integration/dynmap/marker/TempMarkerSet.java @@ -0,0 +1,70 @@ +package net.redstoneore.legacyfactions.integration.dynmap.marker; + +import java.util.Optional; + +import org.dynmap.markers.MarkerAPI; +import org.dynmap.markers.MarkerSet; + +public class TempMarkerSet { + + // -------------------------------------------- // + // FIELDS + // -------------------------------------------- // + + public String label; + public int minimumZoom; + public int priority; + public boolean hideByDefault; + + // -------------------------------------------- // + // METHODS + // -------------------------------------------- // + + /** + * Create a MarkerSet + * @param markerApi MarkerAPI + * @param id MarkerSet id + * @return {@link Optional} containing the MarkerSet if it was successful + */ + public Optional create(MarkerAPI markerApi, String id) { + MarkerSet marker = markerApi.createMarkerSet(id, this.label, null, false); + + if (marker == null) return Optional.empty(); + + // Minimum Zoom + if (this.minimumZoom > 0) { + marker.setMinZoom(this.minimumZoom); + } + + // Priority + marker.setLayerPriority(this.priority); + + // Hide by Default + marker.setHideByDefault(this.hideByDefault); + + return Optional.of(marker); + } + + public void update(MarkerAPI markerApi, MarkerSet markerset) { + // Label + if (!markerset.getMarkerSetLabel().equals(this.label)) { + markerset.setMarkerSetLabel(this.label); + } + + // Minimum Zoom + if (this.minimumZoom > 0 && markerset.getMinZoom() != this.minimumZoom) { + markerset.setMinZoom(this.minimumZoom); + } + + // Priority + if (markerset.getLayerPriority() != this.priority) { + markerset.setLayerPriority(this.priority); + } + + // Hide by Default + if (markerset.getHideByDefault() != this.hideByDefault) { + markerset.setHideByDefault(this.hideByDefault); + } + } + +} diff --git a/src/main/java/net/redstoneore/legacyfactions/integration/dynmap/util/DynmapUtil.java b/src/main/java/net/redstoneore/legacyfactions/integration/dynmap/util/DynmapUtil.java new file mode 100644 index 00000000..17c25f93 --- /dev/null +++ b/src/main/java/net/redstoneore/legacyfactions/integration/dynmap/util/DynmapUtil.java @@ -0,0 +1,82 @@ +package net.redstoneore.legacyfactions.integration.dynmap.util; + +import org.dynmap.markers.AreaMarker; +import org.dynmap.markers.MarkerAPI; +import org.dynmap.markers.MarkerIcon; + +import net.redstoneore.legacyfactions.entity.Conf; +import net.redstoneore.legacyfactions.entity.Faction; +import net.redstoneore.legacyfactions.integration.dynmap.DynmapConfig; +import net.redstoneore.legacyfactions.integration.dynmap.DynmapStyle; + +public class DynmapUtil { + + /** + * Get the Dynmap marker icon + * @param markerApi The MarkerAPI + * @param name Name of this marker icon + * @return {@link MarkerIcon} + */ + public static MarkerIcon getMarkerIcon(MarkerAPI markerApi, String name) { + MarkerIcon markerIcon = markerApi.getMarkerIcon(name); + if (markerIcon == null) { + markerIcon = markerApi.getMarkerIcon(DynmapConfig.DYNMAP_STYLE_HOME_MARKER); + } + return markerIcon; + } + + /** + * Get the Dynmap style colour + * @param string String format + * @return int colour + */ + public static int getColour(String string) { + int colour = 0x00FF00; + try { + colour = Integer.parseInt(string.substring(1), 16); + } catch (NumberFormatException nfx) { + + } + return colour; + } + + /** + * Does an area marker equal and x and z + * @param marker The marker. + * @param x X + * @param z Z + * @return true if they equal + */ + 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; + } + + /** + * get the DynmapStyle for a faction + * @param faction The faction to get the style for. + * @return {@link DynmapStyle} + */ + public static DynmapStyle getStyle(Faction faction) { + DynmapStyle style; + + style = Conf.dynmap.factionStyles.get(faction.getId()); + if (style != null) return style; + + style = Conf.dynmap.factionStyles.get(faction.getTag()); + if (style != null) return style; + + return Conf.dynmap.defaultStyle; + } + + +}