diff --git a/BlueMapBukkit/build.gradle b/BlueMapBukkit/build.gradle new file mode 100644 index 00000000..c8d6a719 --- /dev/null +++ b/BlueMapBukkit/build.gradle @@ -0,0 +1,15 @@ +repositories { + maven { + url = 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/' + + content { + includeGroup 'org.bukkit' + } + } +} + +dependencies { + shadow "org.bukkit:bukkit:1.14.4-R0.1-SNAPSHOT" + compile group: 'org.bstats', name: 'bstats-bukkit-lite', version: '1.5' + compile project(':BlueMapCommon') +} diff --git a/BlueMapBukkit/src/main/java/de/bluecolored/bluemap/bukkit/BukkitCommandSource.java b/BlueMapBukkit/src/main/java/de/bluecolored/bluemap/bukkit/BukkitCommandSource.java new file mode 100644 index 00000000..44be5fec --- /dev/null +++ b/BlueMapBukkit/src/main/java/de/bluecolored/bluemap/bukkit/BukkitCommandSource.java @@ -0,0 +1,33 @@ +package de.bluecolored.bluemap.bukkit; + +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import de.bluecolored.bluemap.common.plugin.serverinterface.CommandSource; +import de.bluecolored.bluemap.common.plugin.text.Text; + +public class BukkitCommandSource implements CommandSource { + + private CommandSender delegate; + + public BukkitCommandSource(CommandSender delegate) { + this.delegate = delegate; + } + + @Override + public void sendMessage(Text text) { + Bukkit.getScheduler().runTask(BukkitPlugin.getInstance(), () -> { + if (delegate instanceof Player) { + Player player = (Player) delegate; + + //kinda hacky but works + Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), "tellraw " + player.getName() + " " + text.toJSONString()); + return; + } + + delegate.sendMessage(text.toFormattingCodedString('§')); + }); + } + +} diff --git a/BlueMapBukkit/src/main/java/de/bluecolored/bluemap/bukkit/BukkitCommands.java b/BlueMapBukkit/src/main/java/de/bluecolored/bluemap/bukkit/BukkitCommands.java new file mode 100644 index 00000000..b95538d5 --- /dev/null +++ b/BlueMapBukkit/src/main/java/de/bluecolored/bluemap/bukkit/BukkitCommands.java @@ -0,0 +1,209 @@ +package de.bluecolored.bluemap.bukkit; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.UUID; + +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import com.flowpowered.math.vector.Vector3i; + +import de.bluecolored.bluemap.common.plugin.Commands; +import de.bluecolored.bluemap.common.plugin.serverinterface.CommandSource; +import de.bluecolored.bluemap.common.plugin.text.Text; +import de.bluecolored.bluemap.common.plugin.text.TextColor; + +public class BukkitCommands implements CommandExecutor { + + private Commands bluemapCommands; + + private Collection commands; + + public BukkitCommands(Commands commands) { + this.bluemapCommands = commands; + this.commands = new ArrayList<>(); + initCommands(); + } + + private void initCommands() { + + commands.add(new Command("bluemap.status") { + @Override + public boolean execute(CommandSender sender, CommandSource source, String[] args) { + if (args.length != 0) return false; + + bluemapCommands.executeRootCommand(source); + return true; + } + }); + + commands.add(new Command("bluemap.reload", "reload") { + @Override + public boolean execute(CommandSender sender, CommandSource source, String[] args) { + if (args.length != 0) return false; + + bluemapCommands.executeReloadCommand(source); + return true; + } + }); + + commands.add(new Command("bluemap.pause", "pause") { + @Override + public boolean execute(CommandSender sender, CommandSource source, String[] args) { + if (args.length != 0) return false; + + bluemapCommands.executePauseCommand(source); + return true; + } + }); + + commands.add(new Command("bluemap.resume", "resume") { + @Override + public boolean execute(CommandSender sender, CommandSource source, String[] args) { + if (args.length != 0) return false; + + bluemapCommands.executeResumeCommand(source); + return true; + } + }); + + commands.add(new Command("bluemap.rendertask.create.world", "render") { + @Override + public boolean execute(CommandSender sender, CommandSource source, String[] args) { + if (args.length > 1) return false; + + World world; + if (args.length == 1) { + world = Bukkit.getWorld(args[0]); + if (world == null) { + source.sendMessage(Text.of(TextColor.RED, "There is no world named '" + args[0] + "'!")); + return true; + } + } else { + if (sender instanceof Player) { + Player player = (Player) sender; + world = player.getWorld(); + } else { + source.sendMessage(Text.of(TextColor.RED, "Since you are not a player, you have to specify a world!")); + return true; + } + } + + bluemapCommands.executeRenderWorldCommand(source, world.getUID()); + return true; + } + }); + + commands.add(new Command("bluemap.rendertask.prioritize", "render", "prioritize") { + @Override + public boolean execute(CommandSender sender, CommandSource source, String[] args) { + if (args.length != 1) return false; + + try { + UUID uuid = UUID.fromString(args[0]); + bluemapCommands.executePrioritizeRenderTaskCommand(source, uuid); + return true; + } catch (IllegalArgumentException ex) { + source.sendMessage(Text.of(TextColor.RED, "'" + args[0] + "' is not a valid UUID!")); + return true; + } + } + }); + + commands.add(new Command("bluemap.rendertask.remove", "render", "remove") { + @Override + public boolean execute(CommandSender sender, CommandSource source, String[] args) { + if (args.length != 1) return false; + + try { + UUID uuid = UUID.fromString(args[0]); + bluemapCommands.executeRemoveRenderTaskCommand(source, uuid); + return true; + } catch (IllegalArgumentException ex) { + source.sendMessage(Text.of(TextColor.RED, "'" + args[0] + "' is not a valid UUID!")); + return true; + } + } + }); + + commands.add(new Command("bluemap.debug", "debug") { + @Override + public boolean execute(CommandSender sender, CommandSource source, String[] args) { + if (!(sender instanceof Player)) { + source.sendMessage(Text.of(TextColor.RED, "You have to be a player to use this command!")); + return true; + } + + Player player = (Player) sender; + UUID world = player.getWorld().getUID(); + Vector3i pos = new Vector3i( + player.getLocation().getBlockX(), + player.getLocation().getBlockY(), + player.getLocation().getBlockZ() + ); + + bluemapCommands.executeDebugCommand(source, world, pos); + return true; + } + }); + + } + + @Override + public boolean onCommand(CommandSender sender, org.bukkit.command.Command bukkitCommand, String label, String[] args) { + int max = -1; + Command maxCommand = null; + for (Command command : commands) { + int matchSize = command.matches(args); + if (matchSize > max) { + maxCommand = command; + max = matchSize; + } + } + + if (maxCommand == null) return false; + + BukkitCommandSource source = new BukkitCommandSource(sender); + + if (!maxCommand.checkPermission(sender)) { + source.sendMessage(Text.of(TextColor.RED, "You don't have permission to use this command!")); + return true; + } + + return maxCommand.execute(sender, source, Arrays.copyOfRange(args, max, args.length)); + } + + private abstract class Command { + + private String[] command; + private String permission; + + public Command(String permission, String... command) { + this.command = command; + } + + public abstract boolean execute(CommandSender sender, CommandSource source, String[] args); + + public int matches(String[] args) { + if (args.length < command.length) return -1; + + for (int i = 0; i < command.length; i++) { + if (!args[i].equalsIgnoreCase(command[i])) return -1; + } + + return command.length; + } + + public boolean checkPermission(CommandSender sender) { + if (sender.isOp()) return true; + return sender.hasPermission(permission); + } + + } + +} diff --git a/BlueMapBukkit/src/main/java/de/bluecolored/bluemap/bukkit/BukkitPlugin.java b/BlueMapBukkit/src/main/java/de/bluecolored/bluemap/bukkit/BukkitPlugin.java new file mode 100644 index 00000000..b3d49919 --- /dev/null +++ b/BlueMapBukkit/src/main/java/de/bluecolored/bluemap/bukkit/BukkitPlugin.java @@ -0,0 +1,118 @@ +package de.bluecolored.bluemap.bukkit; + +import java.io.File; +import java.io.IOException; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +import org.bstats.bukkit.MetricsLite; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.plugin.java.JavaPlugin; + +import de.bluecolored.bluemap.common.plugin.Plugin; +import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener; +import de.bluecolored.bluemap.common.plugin.serverinterface.ServerInterface; +import de.bluecolored.bluemap.core.logger.Logger; + +public class BukkitPlugin extends JavaPlugin implements ServerInterface { + + private static BukkitPlugin instance; + + private Plugin bluemap; + private EventForwarder eventForwarder; + private BukkitCommands commands; + + public BukkitPlugin() { + Logger.global = new JavaLogger(getLogger()); + + this.eventForwarder = new EventForwarder(); + this.bluemap = new Plugin("bukkit", this); + this.commands = new BukkitCommands(bluemap.getCommands()); + + BukkitPlugin.instance = this; + } + + @Override + public void onEnable() { + new MetricsLite(this); + + getServer().getPluginManager().registerEvents(eventForwarder, this); + getCommand("bluemap").setExecutor(commands); + + getServer().getScheduler().runTaskAsynchronously(this, () -> { + try { + Logger.global.logInfo("Loading..."); + this.bluemap.load(); + if (bluemap.isLoaded()) Logger.global.logInfo("Loaded!"); + } catch (Throwable t) { + Logger.global.logError("Failed to load!", t); + } + }); + } + + @Override + public void onDisable() { + Logger.global.logInfo("Stopping..."); + bluemap.unload(); + Logger.global.logInfo("Saved and stopped!"); + } + + @Override + public void registerListener(ServerEventListener listener) { + eventForwarder.addListener(listener); + } + + @Override + public void unregisterAllListeners() { + eventForwarder.removeAllListeners(); + } + + @Override + public UUID getUUIDForWorld(File worldFolder) throws IOException { + final File normalizedWorldFolder = worldFolder.getCanonicalFile(); + + Future futureUUID; + if (!Bukkit.isPrimaryThread()) { + futureUUID = Bukkit.getScheduler().callSyncMethod(BukkitPlugin.getInstance(), () -> getUUIDForWorldSync(normalizedWorldFolder)); + } else { + futureUUID = CompletableFuture.completedFuture(getUUIDForWorldSync(normalizedWorldFolder)); + } + + try { + return futureUUID.get(); + } catch (InterruptedException e) { + throw new IOException(e); + } catch (ExecutionException e) { + if (e.getCause() instanceof IOException) { + throw (IOException) e.getCause(); + } else { + throw new IOException(e); + } + } + } + + private UUID getUUIDForWorldSync (File worldFolder) throws IOException { + for (World world : getServer().getWorlds()) { + if (worldFolder.equals(world.getWorldFolder().getCanonicalFile())) return world.getUID(); + } + + throw new IOException("There is no world with this folder loaded: " + worldFolder.getCanonicalPath()); + } + + @Override + public File getConfigFolder() { + return getDataFolder(); + } + + public Plugin getBlueMap() { + return bluemap; + } + + public static BukkitPlugin getInstance() { + return instance; + } + +} diff --git a/BlueMapBukkit/src/main/java/de/bluecolored/bluemap/bukkit/EventForwarder.java b/BlueMapBukkit/src/main/java/de/bluecolored/bluemap/bukkit/EventForwarder.java new file mode 100644 index 00000000..a307437c --- /dev/null +++ b/BlueMapBukkit/src/main/java/de/bluecolored/bluemap/bukkit/EventForwarder.java @@ -0,0 +1,133 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.bluecolored.bluemap.bukkit; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.UUID; + +import org.bukkit.Chunk; +import org.bukkit.Location; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.block.BlockBurnEvent; +import org.bukkit.event.block.BlockExplodeEvent; +import org.bukkit.event.block.BlockFadeEvent; +import org.bukkit.event.block.BlockFertilizeEvent; +import org.bukkit.event.block.BlockFormEvent; +import org.bukkit.event.block.BlockGrowEvent; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.block.BlockSpreadEvent; +import org.bukkit.event.world.ChunkPopulateEvent; +import org.bukkit.event.world.WorldSaveEvent; + +import com.flowpowered.math.vector.Vector2i; +import com.flowpowered.math.vector.Vector3i; + +import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener; + +public class EventForwarder implements Listener { + + private Collection listeners; + + public EventForwarder() { + listeners = new ArrayList<>(); + } + + public synchronized void addListener(ServerEventListener listener) { + listeners.add(listener); + } + + public synchronized void removeAllListeners() { + listeners.clear(); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public synchronized void onWorldSaveToDisk(WorldSaveEvent evt) { + listeners.forEach(l -> l.onWorldSaveToDisk(evt.getWorld().getUID())); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onBlockChange(BlockPlaceEvent evt) { + onBlockChange(evt.getBlock().getLocation()); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onBlockChange(BlockBreakEvent evt) { + onBlockChange(evt.getBlock().getLocation()); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onBlockChange(BlockGrowEvent evt) { + onBlockChange(evt.getBlock().getLocation()); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onBlockChange(BlockBurnEvent evt) { + onBlockChange(evt.getBlock().getLocation()); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onBlockChange(BlockExplodeEvent evt) { + onBlockChange(evt.getBlock().getLocation()); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onBlockChange(BlockFadeEvent evt) { + onBlockChange(evt.getBlock().getLocation()); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onBlockChange(BlockSpreadEvent evt) { + onBlockChange(evt.getBlock().getLocation()); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onBlockChange(BlockFormEvent evt) { + onBlockChange(evt.getBlock().getLocation()); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onBlockChange(BlockFertilizeEvent evt) { + onBlockChange(evt.getBlock().getLocation()); + } + + private synchronized void onBlockChange(Location loc) { + UUID world = loc.getWorld().getUID(); + Vector3i pos = new Vector3i(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()); + listeners.forEach(l -> l.onBlockChange(world, pos)); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public synchronized void onChunkFinishedGeneration(ChunkPopulateEvent evt) { + Chunk chunk = evt.getChunk(); + UUID world = chunk.getWorld().getUID(); + Vector2i chunkPos = new Vector2i(chunk.getX(), chunk.getZ()); + listeners.forEach(l -> l.onChunkFinishedGeneration(world, chunkPos)); + } + +} diff --git a/BlueMapBukkit/src/main/java/de/bluecolored/bluemap/bukkit/JavaLogger.java b/BlueMapBukkit/src/main/java/de/bluecolored/bluemap/bukkit/JavaLogger.java new file mode 100644 index 00000000..433bf667 --- /dev/null +++ b/BlueMapBukkit/src/main/java/de/bluecolored/bluemap/bukkit/JavaLogger.java @@ -0,0 +1,70 @@ +/* + * This file is part of BlueMapSponge, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.bluecolored.bluemap.bukkit; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import de.bluecolored.bluemap.core.logger.AbstractLogger; + +public class JavaLogger extends AbstractLogger { + + private Logger out; + + public JavaLogger(Logger out) { + this.out = out; + } + + @Override + public void logError(String message, Throwable throwable) { + out.log(Level.SEVERE, message, throwable); + } + + @Override + public void logWarning(String message) { + out.log(Level.WARNING, message); + } + + @Override + public void logInfo(String message) { + out.log(Level.INFO, message); + } + + @Override + public void logDebug(String message) { + if (out.isLoggable(Level.FINE)) out.log(Level.FINE, message); + } + + @Override + public void noFloodDebug(String message) { + if (out.isLoggable(Level.FINE)) super.noFloodDebug(message); + } + + @Override + public void noFloodDebug(String key, String message) { + if (out.isLoggable(Level.FINE)) super.noFloodDebug(key, message); + } + +} diff --git a/BlueMapBukkit/src/main/resources/bluemap-bukkit-defaults.conf b/BlueMapBukkit/src/main/resources/bluemap-bukkit-defaults.conf new file mode 100644 index 00000000..d8404ad8 --- /dev/null +++ b/BlueMapBukkit/src/main/resources/bluemap-bukkit-defaults.conf @@ -0,0 +1,10 @@ +accept-download: false +metrics: true +renderThreadCount: -2 +data: "bluemap" +webroot: "bluemap/web" +webserver { + enabled: true + port: 8100 + maxConnectionCount: 100 +} diff --git a/BlueMapBukkit/src/main/resources/bluemap-bukkit.conf b/BlueMapBukkit/src/main/resources/bluemap-bukkit.conf new file mode 100644 index 00000000..34d64029 --- /dev/null +++ b/BlueMapBukkit/src/main/resources/bluemap-bukkit.conf @@ -0,0 +1,171 @@ +## ## +## BlueMap ## +## ## +## by Blue (Lukas Rieger) ## +## http://bluecolored.de/ ## +## ## + +# By changing the setting (accept-download) below to TRUE you are indicating that you have accepted mojang's EULA (https://account.mojang.com/documents/minecraft_eula), +# you confirm that you own a license to Minecraft (Java Edition) +# and you agree that BlueMap will download and use this file for you: %minecraft-client-url% +# (Alternatively you can download the file yourself and store it here: /minecraft-client-%minecraft-client-version%.jar) +# This file contains resources that belong to mojang and you must not redistribute it or do anything else that is not compliant with mojang's EULA. +# BlueMap uses resources in this file to generate the 3D-Models used for the map and texture them. (BlueMap will not work without those resources.) +# %datetime-iso% +accept-download: false + +# This changes the amount of threads that BlueMap will use to render the maps. +# A higher value can improve render-speed but could impact performance on the host machine. +# This should be always below or equal to the number of available processor-cores. +# Zero or a negative value means the amount of of available processor-cores subtracted by the value. +# (So a value of -2 with 6 cores results in 4 render-processes) +# Default is -2 +renderThreadCount: -2 + +# If this is true, BlueMap might send really basic metrics reports containg only the implementation-type and the version that is being used to https://metrics.bluecolored.de/bluemap/ +# This allows me to track the basic usage of BlueMap and helps me stay motivated to further develop this tool! Please leave it on :) +# An example report looks like this: {"implementation":"CLI","version":"%version%"} +metrics: true + +# The folder where bluemap saves data-files it needs during runtime or to save e.g. the render-progress to resume it later. +data: "bluemap" + +# The webroot of the website that displays the map. +webroot: "bluemap/web" + +# Unncomment this to override the path where bluemap stores the data-files. +# Default is "/data" +#webdata: "path/to/data/folder" + +webserver { + # With this setting you can disable the integrated web-server. + # This is usefull if you want to only render the map-data for later use, or if you setup your own webserver. + # Default is enabled + enabled: true + + # The IP-Adress that the webserver binds to. + # If this setting is commented out, bluemap tries to find the default ip-adress of your system. + # If you only want to access it locally use "localhost". + #ip: "localhost" + #ip: "127.0.0.1" + + # The port that the webserver listenes to. + # Default is 8100 + port: 8100 + + # Max number of simultaneous connections that the webserver allows + # Default is 100 + maxConnectionCount: 100 +} + +# This is an array with multiple configured maps. +# You can define multiple maps, for different worlds with different render-settings here +maps: [ + + { + # The id of this map + # Should only contain word-charactes: [a-zA-Z0-9_] + id: "world" + + # The name of this map + # This defines the display name of this map, you can change this at any time + # Default is the id of this map + name: "World" + + # The path to the save-folder of the world to render + world: "world" + + # If this is false, BlueMap tries to omit all blocks that are not visible from above-ground. + # More specific: Block-Faces that have a sunlight/skylight value of 0 are removed. + # This improves the performance of the map on slower devices by a lot, but might cause some blocks to disappear that should normally be visible. + # Default is false + renderCaves: false + + # AmbientOcclusion adds soft shadows into corners, which gives the map a much better look. + # This has only a small impact on render-time and has no impact on the web-performance of the map. + # The value defines the strength of the shading, a value of 0 disables ambientOcclusion. + # Default is 0.25 + ambientOcclusion: 0.25 + + # Lighting uses the light-data in minecraft to shade each block-face. + # If this is enabled, caves and inside buildings without torches will be darker. + # The value defines the strength of the shading and a value of 0 disables lighting (every block will be fully lit). + # Default is 0.8 + lighting: 0.8 + + # With the below values you can limit the map-render. + # This can be used to ignore the nethers ceiling or render only a certain part of a world. + # Default is no min or max value (= infinite bounds) + #minX: -4000 + #maxX: 4000 + #minZ: -4000 + #maxZ: 4000 + #minY: 50 + #maxY: 126 + + # Using this, BlueMap pretends that every Block out of the defined render-bounds is AIR, + # this means you can see the blocks where the world is cut (instead of having a see-through/xray view). + # This has only an effect if you set some render-bounds above. + # Default is enabled + renderEdges: true + + # HIRES is the high-resolution render of the map. Where you see every block. + hires { + # Defines the size of one map-tile in blocks. + # If you change this value, the lowres values might need adjustment as well! + # Default is 32 + tileSize: 32 + + # The View-Distance for hires tiles on the web-map (the value is the radius in tiles) + # Default is 4.5 + viewDistance: 4.5 + } + + # LOWRES is the low-resolution render of the map. Thats the model that you see if you zoom far out to get an overview. + lowres { + # Defines resolution of the lowres model. E.g. If the hires.tileSize is 32, a value of 4 means that every 8*8 blocks will be summarized by one point on the lowres map. + # Calculation: 32 / 4 = 8 + # You can only use values that result in an integer if you use the above calculation! + # Default is 4 + pointsPerHiresTile: 4 + + # Defines the size of one lowres-map-tile in points. + # Default is 50 + pointsPerLowresTile: 50 + + # The View-Distance for lowres tiles on the web-map (the value is the radius in tiles) + # Default is 7 + viewDistance: 7 + } + } + + # Here another example for the End-Map + # Things we don't want to change from default we can just omit + { + id: "end" + name: "End" + world: "world/DIM1" + + # In the end is no sky-light, so we need to enable this or we won't see anything. + renderCaves: true + + # Same here, we don't want a dark map. But not completely disabled, so we see the effect of e.g torches. + lighting: 0.4 + } + + # Here another example for the Nether-Map + { + id: "nether" + name: "Nether" + world: "world/DIM-1" + + renderCaves: true + lighting: 0.6 + + # We slice the whole world at y:90 so every block above 90 will be air. + # This way we don't render the nethers ceiling. + maxY: 90 + renderEdges: true + } + +] diff --git a/BlueMapBukkit/src/main/resources/plugin.yml b/BlueMapBukkit/src/main/resources/plugin.yml new file mode 100644 index 00000000..6c8fbe10 --- /dev/null +++ b/BlueMapBukkit/src/main/resources/plugin.yml @@ -0,0 +1,46 @@ +name: BlueMap +description: "A 3d-map of your Minecraft worlds view-able in your browser using three.js (WebGL)" +main: de.bluecolored.bluemap.bukkit.BukkitPlugin +version: 0.2.1 +author: "Blue (TBlueF / Lukas Rieger)" +authors: [Blue (TBlueF / Lukas Rieger)] +website: "https://github.com/BlueMap-Minecraft" +commands: + bluemap: + description: Root command for all bluemap commands + permission: bluemap + usage: | + / + / reload + / pause + / resume + / render [world] + / debug +permissions: + bluemap.*: + children: + bluemap.status: true + bluemap.reload: true + bluemap.pause: true + bluemap.resume: true + bluemap.rendertask.create.world: true + bluemap.rendertask.prioritize: true + bluemap.rendertask.remove: true + bluemap.debug: true + default: op + bluemap.status: + default: op + bluemap.reload: + default: op + bluemap.pause: + default: op + bluemap.resume: + default: op + bluemap.rendertask.create.world: + default: op + bluemap.rendertask.prioritize: + default: op + bluemap.rendertask.remove: + default: op + bluemap.debug: + default: op \ No newline at end of file diff --git a/BlueMapCLI/src/main/java/de/bluecolored/bluemap/cli/BlueMapCLI.java b/BlueMapCLI/src/main/java/de/bluecolored/bluemap/cli/BlueMapCLI.java index b3c58e76..2b55dfb2 100644 --- a/BlueMapCLI/src/main/java/de/bluecolored/bluemap/cli/BlueMapCLI.java +++ b/BlueMapCLI/src/main/java/de/bluecolored/bluemap/cli/BlueMapCLI.java @@ -31,10 +31,8 @@ import java.util.Arrays; import java.util.Collection; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.UUID; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.TimeUnit; @@ -49,7 +47,6 @@ import org.apache.commons.io.FileUtils; import com.flowpowered.math.vector.Vector2i; -import com.flowpowered.math.vector.Vector3i; import com.google.common.base.Preconditions; import de.bluecolored.bluemap.core.config.ConfigManager; @@ -162,14 +159,7 @@ public void renderMaps() throws IOException { } HiresModelManager hiresModelManager = map.getTileRenderer().getHiresModelManager(); - Set tiles = new HashSet<>(); - for (Vector2i chunk : chunks) { - Vector3i minBlockPos = new Vector3i(chunk.getX() * 16, 0, chunk.getY() * 16); - tiles.add(hiresModelManager.posToTile(minBlockPos)); - tiles.add(hiresModelManager.posToTile(minBlockPos.add(0, 0, 15))); - tiles.add(hiresModelManager.posToTile(minBlockPos.add(15, 0, 0))); - tiles.add(hiresModelManager.posToTile(minBlockPos.add(15, 0, 15))); - } + Collection tiles = hiresModelManager.getTilesForChunks(chunks); Logger.global.logInfo("Found " + tiles.size() + " tiles to render! (" + chunks.size() + " chunks)"); if (!forceRender && chunks.size() == 0) { Logger.global.logInfo("(This is normal if nothing has changed in the world since the last render. Use -f on the command-line to force a render of all chunks)"); diff --git a/BlueMapCommon/build.gradle b/BlueMapCommon/build.gradle new file mode 100644 index 00000000..d8406125 --- /dev/null +++ b/BlueMapCommon/build.gradle @@ -0,0 +1,3 @@ +dependencies { + compile project(':BlueMapCore') +} diff --git a/BlueMapSponge/src/main/java/de/bluecolored/bluemap/sponge/MapType.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/MapType.java similarity index 98% rename from BlueMapSponge/src/main/java/de/bluecolored/bluemap/sponge/MapType.java rename to BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/MapType.java index 5789e42d..5d3a11d3 100644 --- a/BlueMapSponge/src/main/java/de/bluecolored/bluemap/sponge/MapType.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/MapType.java @@ -22,7 +22,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package de.bluecolored.bluemap.sponge; +package de.bluecolored.bluemap.common; import java.io.IOException; diff --git a/BlueMapSponge/src/main/java/de/bluecolored/bluemap/sponge/RenderManager.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/RenderManager.java similarity index 96% rename from BlueMapSponge/src/main/java/de/bluecolored/bluemap/sponge/RenderManager.java rename to BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/RenderManager.java index 43e7be3b..b0236457 100644 --- a/BlueMapSponge/src/main/java/de/bluecolored/bluemap/sponge/RenderManager.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/RenderManager.java @@ -1,4 +1,4 @@ -package de.bluecolored.bluemap.sponge; +package de.bluecolored.bluemap.common; import java.io.DataInputStream; import java.io.DataOutputStream; @@ -194,14 +194,14 @@ public void writeState(DataOutputStream out) throws IOException { } } - public void readState(DataInputStream in) throws IOException { + public void readState(DataInputStream in, Collection mapTypes) throws IOException { //read renderTickets int mapCount = in.readInt(); for (int i = 0; i < mapCount; i++) { String mapId = in.readUTF(); MapType mapType = null; - for (MapType map : SpongePlugin.getInstance().getMapTypes()) { + for (MapType map : mapTypes) { if (map.getId().equals(mapId)) { mapType = map; break; @@ -227,7 +227,7 @@ public void readState(DataInputStream in) throws IOException { int taskCount = in.readInt(); for (int i = 0; i < taskCount; i++) { try { - RenderTask task = RenderTask.read(in); + RenderTask task = RenderTask.read(in, mapTypes); addRenderTask(task); } catch (IOException ex) { Logger.global.logWarning("A render-task can not be loaded. It will be discared. (Error message: " + ex.toString() + ")"); diff --git a/BlueMapSponge/src/main/java/de/bluecolored/bluemap/sponge/RenderTask.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/RenderTask.java similarity index 96% rename from BlueMapSponge/src/main/java/de/bluecolored/bluemap/sponge/RenderTask.java rename to BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/RenderTask.java index 28710a29..5f6f9cd6 100644 --- a/BlueMapSponge/src/main/java/de/bluecolored/bluemap/sponge/RenderTask.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/RenderTask.java @@ -1,4 +1,4 @@ -package de.bluecolored.bluemap.sponge; +package de.bluecolored.bluemap.common; import java.io.DataInputStream; import java.io.DataOutputStream; @@ -159,12 +159,12 @@ public void write(DataOutputStream out) throws IOException { } } - public static RenderTask read(DataInputStream in) throws IOException { + public static RenderTask read(DataInputStream in, Collection mapTypes) throws IOException { String name = in.readUTF(); String mapId = in.readUTF(); MapType mapType = null; - for (MapType map : SpongePlugin.getInstance().getMapTypes()) { + for (MapType map : mapTypes) { if (map.getId().equals(mapId)) { mapType = map; break; diff --git a/BlueMapSponge/src/main/java/de/bluecolored/bluemap/sponge/RenderTicket.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/RenderTicket.java similarity index 96% rename from BlueMapSponge/src/main/java/de/bluecolored/bluemap/sponge/RenderTicket.java rename to BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/RenderTicket.java index 2f4d8cd9..545a7bdc 100644 --- a/BlueMapSponge/src/main/java/de/bluecolored/bluemap/sponge/RenderTicket.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/RenderTicket.java @@ -1,4 +1,4 @@ -package de.bluecolored.bluemap.sponge; +package de.bluecolored.bluemap.common; import java.io.IOException; import java.util.Objects; diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/Commands.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/Commands.java new file mode 100644 index 00000000..82d1b576 --- /dev/null +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/Commands.java @@ -0,0 +1,298 @@ +package de.bluecolored.bluemap.common.plugin; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.UUID; + +import org.apache.commons.lang3.time.DurationFormatUtils; + +import com.flowpowered.math.GenericMath; +import com.flowpowered.math.vector.Vector2i; +import com.flowpowered.math.vector.Vector3i; +import com.google.common.collect.Lists; + +import de.bluecolored.bluemap.common.MapType; +import de.bluecolored.bluemap.common.RenderManager; +import de.bluecolored.bluemap.common.RenderTask; +import de.bluecolored.bluemap.common.plugin.serverinterface.CommandSource; +import de.bluecolored.bluemap.common.plugin.text.Text; +import de.bluecolored.bluemap.common.plugin.text.TextColor; +import de.bluecolored.bluemap.core.logger.Logger; +import de.bluecolored.bluemap.core.mca.Chunk; +import de.bluecolored.bluemap.core.mca.ChunkAnvil112; +import de.bluecolored.bluemap.core.mca.MCAWorld; +import de.bluecolored.bluemap.core.render.hires.HiresModelManager; +import de.bluecolored.bluemap.core.world.Block; +import de.bluecolored.bluemap.core.world.World; + +/** + * Commands: + * + *
    + *
  • /bluemap
  • + *
  • /bluemap reload
  • + *
  • /bluemap pause
  • + *
  • /bluemap resume
  • + *
  • /bluemap render [world]
  • + *
  • /bluemap render prioritize [task-uuid]
  • + *
  • /bluemap render remove [task-uuid]
  • + *
  • /bluemap debug
  • + *
+ */ +public class Commands { + + private Plugin bluemap; + + public Commands(Plugin bluemap) { + this.bluemap = bluemap; + } + + /** + * Command: /bluemap + */ + public void executeRootCommand(CommandSource source) { + if (!checkLoaded(source)) return; + + source.sendMessages(createStatusMessage()); + } + + /** + * Command: /bluemap debug + */ + public boolean executeDebugCommand(CommandSource source, UUID worldUuid, Vector3i playerPosition) { + if (!checkLoaded(source)) return false; + + World world = bluemap.getWorld(worldUuid); + + if (world == null) { + source.sendMessage(Text.of(TextColor.RED, "This world is not loaded with BlueMap! Maybe it is not configured?")); + return false; + } + + Block block = world.getBlock(playerPosition); + Block blockBelow = world.getBlock(playerPosition.add(0, -1, 0)); + + String blockIdMeta = ""; + String blockBelowIdMeta = ""; + + if (world instanceof MCAWorld) { + try { + Chunk chunk = ((MCAWorld) world).getChunk(MCAWorld.blockToChunk(playerPosition)); + if (chunk instanceof ChunkAnvil112) { + blockIdMeta = " (" + ((ChunkAnvil112) chunk).getBlockIdMeta(playerPosition) + ")"; + blockBelowIdMeta = " (" + ((ChunkAnvil112) chunk).getBlockIdMeta(playerPosition.add(0, -1, 0)) + ")"; + } + } catch (IOException ex) { + Logger.global.logError("Failed to read chunk for debug!", ex); + } + } + + source.sendMessages(Lists.newArrayList( + Text.of(TextColor.GOLD, "Block at you: ", TextColor.WHITE, block, TextColor.GRAY, blockIdMeta), + Text.of(TextColor.GOLD, "Block below you: ", TextColor.WHITE, blockBelow, TextColor.GRAY, blockBelowIdMeta) + )); + + return true; + } + + /** + * Command: /bluemap reload + */ + public void executeReloadCommand(CommandSource source) { + source.sendMessage(Text.of(TextColor.GOLD, "Reloading BlueMap...")); + + new Thread(() -> { + try { + bluemap.reload(); + + if (bluemap.isLoaded()) { + source.sendMessage(Text.of(TextColor.GREEN, "BlueMap reloaded!")); + } else { + source.sendMessage(Text.of(TextColor.RED, "Could not load BlueMap! See the console for details!")); + } + + } catch (Exception ex) { + Logger.global.logError("Failed to reload BlueMap!", ex); + + source.sendMessage(Text.of(TextColor.RED, "There was an error reloading BlueMap! See the console for details!")); + } + }).start(); + + } + + /** + * Command: /bluemap pause + */ + public boolean executePauseCommand(CommandSource source) { + if (!checkLoaded(source)) return false; + + if (bluemap.getRenderManager().isRunning()) { + bluemap.getRenderManager().stop(); + source.sendMessage(Text.of(TextColor.GREEN, "BlueMap rendering paused!")); + return true; + } else { + source.sendMessage(Text.of(TextColor.RED, "BlueMap rendering are already paused!")); + return false; + } + } + + /** + * Command: /bluemap resume + */ + public boolean executeResumeCommand(CommandSource source) { + if (!checkLoaded(source)) return false; + + if (!bluemap.getRenderManager().isRunning()) { + bluemap.getRenderManager().start(); + source.sendMessage(Text.of(TextColor.GREEN, "BlueMap renders resumed!")); + return true; + } else { + source.sendMessage(Text.of(TextColor.RED, "BlueMap renders are already running!")); + return false; + } + } + + /** + * Command: /bluemap render [world] + */ + public boolean executeRenderWorldCommand(CommandSource source, UUID worldUuid) { + if (!checkLoaded(source)) return false; + + World world = bluemap.getWorld(worldUuid); + + if (world == null) { + source.sendMessage(Text.of(TextColor.RED, "This world is not loaded with BlueMap! Maybe it is not configured?")); + return false; + } + + world.invalidateChunkCache(); + + new Thread(() -> { + createWorldRenderTask(source, world); + }).start(); + + return true; + } + + /** + * Command: /bluemap render prioritize [task-uuid] + */ + public void executePrioritizeRenderTaskCommand(CommandSource source, UUID taskUUID) { + if (!checkLoaded(source)) return; + + for (RenderTask task : bluemap.getRenderManager().getRenderTasks()) { + if (task.getUuid().equals(taskUUID)) { + bluemap.getRenderManager().prioritizeRenderTask(task); + break; + } + } + + source.sendMessages(createStatusMessage()); + } + + /** + * Command: /bluemap render remove [task-uuid] + */ + public void executeRemoveRenderTaskCommand(CommandSource source, UUID taskUUID) { + if (!checkLoaded(source)) return; + + for (RenderTask task : bluemap.getRenderManager().getRenderTasks()) { + if (task.getUuid().equals(taskUUID)) { + bluemap.getRenderManager().removeRenderTask(task); + break; + } + } + + source.sendMessages(createStatusMessage()); + } + + private List createStatusMessage(){ + List lines = new ArrayList<>(); + + RenderManager renderer = bluemap.getRenderManager(); + + lines.add(Text.of()); + lines.add(Text.of(TextColor.BLUE, "Tile-Updates:")); + + if (renderer.isRunning()) { + lines.add(Text.of(TextColor.WHITE, " Render-Threads are ", Text.of(TextColor.GREEN, "running").setHoverText(Text.of("click to pause rendering")).setClickCommand("/bluemap pause"), TextColor.GRAY, "!")); + } else { + lines.add(Text.of(TextColor.WHITE, " Render-Threads are ", Text.of(TextColor.RED, "paused").setHoverText(Text.of("click to resume rendering")).setClickCommand("/bluemap resume"), TextColor.GRAY, "!")); + } + + lines.add(Text.of(TextColor.WHITE, " Scheduled tile-updates: ", Text.of(TextColor.GOLD, renderer.getQueueSize()).setHoverText(Text.of("tiles waiting for a free render-thread")), TextColor.GRAY, " + " , Text.of(TextColor.GRAY, bluemap.getUpdateHandler().getUpdateBufferCount()).setHoverText(Text.of("tiles waiting for world-save")))); + + RenderTask[] tasks = renderer.getRenderTasks(); + if (tasks.length > 0) { + RenderTask task = tasks[0]; + + long time = task.getActiveTime(); + String durationString = DurationFormatUtils.formatDurationWords(time, true, true); + double pct = (double)task.getRenderedTileCount() / (double)(task.getRenderedTileCount() + task.getRemainingTileCount()); + + long ert = (long)((time / pct) * (1d - pct)); + String ertDurationString = DurationFormatUtils.formatDurationWords(ert, true, true); + + double tps = task.getRenderedTileCount() / (time / 1000.0); + + lines.add(Text.of(TextColor.BLUE, "Current task:")); + lines.add(Text.of(" ", createCancelTaskText(task), TextColor.WHITE, " Task ", TextColor.GOLD, task.getName(), TextColor.WHITE, " for map ", Text.of(TextColor.GOLD, task.getMapType().getName()).setHoverText(Text.of(TextColor.WHITE, "World: ", TextColor.GOLD, task.getMapType().getWorld().getName())))); + lines.add(Text.of(TextColor.WHITE, " rendered ", TextColor.GOLD, task.getRenderedTileCount(), TextColor.WHITE, " tiles ", TextColor.GRAY, "(" + (Math.round(pct * 1000)/10.0) + "% | " + GenericMath.round(tps, 1) + "t/s)", TextColor.WHITE, " in ", TextColor.GOLD, durationString)); + lines.add(Text.of(TextColor.WHITE, " with ", TextColor.GOLD, task.getRemainingTileCount(), TextColor.WHITE, " tiles to go. ETA: ", TextColor.GOLD, ertDurationString)); + } + + if (tasks.length > 1) { + lines.add(Text.of(TextColor.BLUE, "Waiting tasks:")); + for (int i = 1; i < tasks.length; i++) { + RenderTask task = tasks[i]; + lines.add(Text.of(" ", createCancelTaskText(task), createPrioritizeTaskText(task), TextColor.WHITE, " Task ", TextColor.GOLD, task.getName(), TextColor.WHITE, " for map ", Text.of(TextColor.GOLD, task.getMapType().getName()).setHoverText(Text.of(TextColor.WHITE, "World: ", TextColor.GOLD, task.getMapType().getWorld().getName())), TextColor.GRAY, " (" + task.getRemainingTileCount() + " tiles)")); + } + } + + return lines; + } + + private Text createCancelTaskText(RenderTask task) { + return Text.of(TextColor.RED, "[X]").setHoverText(Text.of(TextColor.GRAY, "click to remove this render-task")).setClickCommand("/bluemap render remove " + task.getUuid()); + } + + private Text createPrioritizeTaskText(RenderTask task) { + return Text.of(TextColor.GREEN, "[^]").setHoverText(Text.of(TextColor.GRAY, "click to prioritize this render-task")).setClickCommand("/bluemap render prioritize " + task.getUuid()); + } + + private void createWorldRenderTask(CommandSource source, World world) { + source.sendMessage(Text.of(TextColor.GOLD, "Collecting chunks to render...")); + Collection chunks = world.getChunkList(); + source.sendMessage(Text.of(TextColor.GREEN, chunks.size() + " chunks found!")); + + for (MapType map : bluemap.getMapTypes()) { + if (!map.getWorld().getUUID().equals(world.getUUID())) continue; + + source.sendMessage(Text.of(TextColor.GOLD, "Collecting tiles for map '" + map.getId() + "'")); + + HiresModelManager hmm = map.getTileRenderer().getHiresModelManager(); + Collection tiles = hmm.getTilesForChunks(chunks); + + RenderTask task = new RenderTask("world-render", map); + task.addTiles(tiles); + task.optimizeQueue(); + bluemap.getRenderManager().addRenderTask(task); + + source.sendMessage(Text.of(TextColor.GREEN, tiles.size() + " tiles found! Task created.")); + } + + source.sendMessage(Text.of(TextColor.GREEN, "All render tasks created! Use /bluemap to view the progress!")); + } + + private boolean checkLoaded(CommandSource source) { + if (!bluemap.isLoaded()) { + source.sendMessage(Text.of(TextColor.RED, "BlueMap is not loaded!", TextColor.GRAY, "(Try /bluemap reload)")); + return false; + } + + return true; + } + +} diff --git a/BlueMapSponge/src/main/java/de/bluecolored/bluemap/sponge/MapUpdateHandler.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/MapUpdateHandler.java similarity index 53% rename from BlueMapSponge/src/main/java/de/bluecolored/bluemap/sponge/MapUpdateHandler.java rename to BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/MapUpdateHandler.java index 529b6d0e..8e485732 100644 --- a/BlueMapSponge/src/main/java/de/bluecolored/bluemap/sponge/MapUpdateHandler.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/MapUpdateHandler.java @@ -1,45 +1,34 @@ -package de.bluecolored.bluemap.sponge; +package de.bluecolored.bluemap.common.plugin; import java.util.Iterator; -import java.util.Optional; import java.util.UUID; -import org.spongepowered.api.Sponge; -import org.spongepowered.api.block.BlockSnapshot; -import org.spongepowered.api.data.Transaction; -import org.spongepowered.api.event.Listener; -import org.spongepowered.api.event.Order; -import org.spongepowered.api.event.block.ChangeBlockEvent; -import org.spongepowered.api.event.filter.type.Exclude; -import org.spongepowered.api.event.world.SaveWorldEvent; -import org.spongepowered.api.event.world.chunk.PopulateChunkEvent; -import org.spongepowered.api.world.Location; - import com.flowpowered.math.vector.Vector2i; import com.flowpowered.math.vector.Vector3i; import com.google.common.collect.Multimap; import com.google.common.collect.MultimapBuilder; -public class MapUpdateHandler { +import de.bluecolored.bluemap.common.MapType; +import de.bluecolored.bluemap.common.RenderManager; +import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener; + +public class MapUpdateHandler implements ServerEventListener { public Multimap updateBuffer; public MapUpdateHandler() { updateBuffer = MultimapBuilder.hashKeys().hashSetValues().build(); - - Sponge.getEventManager().registerListeners(SpongePlugin.getInstance(), this); } - @Listener(order = Order.POST) - public void onWorldSave(SaveWorldEvent.Post evt) { - UUID worldUuid = evt.getTargetWorld().getUniqueId(); - RenderManager renderManager = SpongePlugin.getInstance().getRenderManager(); + @Override + public void onWorldSaveToDisk(UUID world) { + RenderManager renderManager = Plugin.getInstance().getRenderManager(); synchronized (updateBuffer) { Iterator iterator = updateBuffer.keys().iterator(); while (iterator.hasNext()) { MapType map = iterator.next(); - if (map.getWorld().getUUID().equals(worldUuid)) { + if (map.getWorld().getUUID().equals(world)) { renderManager.createTickets(map, updateBuffer.get(map)); iterator.remove(); } @@ -48,27 +37,17 @@ public void onWorldSave(SaveWorldEvent.Post evt) { } } - @Listener(order = Order.POST) - @Exclude({ChangeBlockEvent.Post.class, ChangeBlockEvent.Pre.class, ChangeBlockEvent.Modify.class}) - public void onBlockChange(ChangeBlockEvent evt) { + @Override + public void onBlockChange(UUID world, Vector3i blockPos) { synchronized (updateBuffer) { - for (Transaction tr : evt.getTransactions()) { - if (!tr.isValid()) continue; - - Optional> ow = tr.getFinal().getLocation(); - if (ow.isPresent()) { - updateBlock(ow.get().getExtent().getUniqueId(), ow.get().getPosition().toInt()); - } - } + updateBlock(world, blockPos); } } - @Listener(order = Order.POST) - public void onChunkPopulate(PopulateChunkEvent.Post evt) { - UUID world = evt.getTargetChunk().getWorld().getUniqueId(); - - int x = evt.getTargetChunk().getPosition().getX(); - int z = evt.getTargetChunk().getPosition().getZ(); + @Override + public void onChunkFinishedGeneration(UUID world, Vector2i chunkPos) { + int x = chunkPos.getX(); + int z = chunkPos.getY(); // also update the chunks around, because they might be modified or not rendered yet due to finalizations for (int dx = -1; dx <= 1; dx++) { @@ -96,7 +75,7 @@ private void updateChunk(UUID world, Vector2i chunkPos) { private void updateBlock(UUID world, Vector3i pos){ synchronized (updateBuffer) { - for (MapType mapType : SpongePlugin.getInstance().getMapTypes()) { + for (MapType mapType : Plugin.getInstance().getMapTypes()) { if (mapType.getWorld().getUUID().equals(world)) { mapType.getWorld().invalidateChunkCache(mapType.getWorld().blockPosToChunkPos(pos)); @@ -112,7 +91,7 @@ public int getUpdateBufferCount() { } public void flushTileBuffer() { - RenderManager renderManager = SpongePlugin.getInstance().getRenderManager(); + RenderManager renderManager = Plugin.getInstance().getRenderManager(); synchronized (updateBuffer) { for (MapType map : updateBuffer.keySet()) { diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/Plugin.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/Plugin.java new file mode 100644 index 00000000..4a2e4064 --- /dev/null +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/Plugin.java @@ -0,0 +1,367 @@ +package de.bluecolored.bluemap.common.plugin; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.TimeUnit; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; + +import org.apache.commons.io.FileUtils; + +import com.flowpowered.math.vector.Vector2i; + +import de.bluecolored.bluemap.common.MapType; +import de.bluecolored.bluemap.common.RenderManager; +import de.bluecolored.bluemap.common.plugin.serverinterface.ServerInterface; +import de.bluecolored.bluemap.core.BlueMap; +import de.bluecolored.bluemap.core.config.ConfigManager; +import de.bluecolored.bluemap.core.config.MainConfig; +import de.bluecolored.bluemap.core.config.MainConfig.MapConfig; +import de.bluecolored.bluemap.core.logger.Logger; +import de.bluecolored.bluemap.core.mca.MCAWorld; +import de.bluecolored.bluemap.core.metrics.Metrics; +import de.bluecolored.bluemap.core.render.RenderSettings; +import de.bluecolored.bluemap.core.render.TileRenderer; +import de.bluecolored.bluemap.core.render.hires.HiresModelManager; +import de.bluecolored.bluemap.core.render.lowres.LowresModelManager; +import de.bluecolored.bluemap.core.resourcepack.ParseResourceException; +import de.bluecolored.bluemap.core.resourcepack.ResourcePack; +import de.bluecolored.bluemap.core.web.BlueMapWebServer; +import de.bluecolored.bluemap.core.web.WebFilesManager; +import de.bluecolored.bluemap.core.web.WebSettings; +import de.bluecolored.bluemap.core.world.SlicedWorld; +import de.bluecolored.bluemap.core.world.World; + +public class Plugin { + + public static final String PLUGIN_ID = "bluemap"; + public static final String PLUGIN_NAME = "BlueMap"; + public static final String PLUGIN_VERSION = BlueMap.VERSION; + + private static Plugin instance; + + private String implementationType; + + private ServerInterface serverInterface; + private Commands commands; + + private MainConfig config; + private ResourcePack resourcePack; + + private Map worlds; + private Map maps; + + private MapUpdateHandler updateHandler; + + private RenderManager renderManager; + private BlueMapWebServer webServer; + + private Thread metricsThread; + + private boolean loaded = false; + + public Plugin(String implementationType, ServerInterface serverInterface) { + this.implementationType = implementationType.toLowerCase(); + + this.serverInterface = serverInterface; + this.commands = new Commands(this); + + this.maps = new HashMap<>(); + this.worlds = new HashMap<>(); + + instance = this; + } + + public synchronized void load() throws IOException, ParseResourceException { + if (loaded) return; + unload(); //ensure nothing is left running (from a failed load or something) + + //load configs + URL defaultSpongeConfig = Plugin.class.getResource("/bluemap-" + implementationType + ".conf"); + URL spongeConfigDefaults = Plugin.class.getResource("/bluemap-" + implementationType + "-defaults.conf"); + ConfigManager configManager = new ConfigManager(serverInterface.getConfigFolder(), defaultSpongeConfig, spongeConfigDefaults); + configManager.loadMainConfig(); + config = configManager.getMainConfig(); + + //load resources + File defaultResourceFile = config.getDataPath().resolve("minecraft-client-" + ResourcePack.MINECRAFT_CLIENT_VERSION + ".jar").toFile(); + File resourceExtensionsFile = config.getDataPath().resolve("resourceExtensions.zip").toFile(); + File textureExportFile = config.getWebDataPath().resolve("textures.json").toFile(); + + if (!defaultResourceFile.exists()) { + if (config.isDownloadAccepted()) { + + //download file + try { + Logger.global.logInfo("Downloading " + ResourcePack.MINECRAFT_CLIENT_URL + " to " + defaultResourceFile + " ..."); + ResourcePack.downloadDefaultResource(defaultResourceFile); + } catch (IOException e) { + Logger.global.logError("Failed to download resources!", e); + return; + } + + } else { + Logger.global.logWarning("BlueMap is missing important resources!"); + Logger.global.logWarning("You need to accept the download of the required files in order of BlueMap to work!"); + try { Logger.global.logWarning("Please check: " + configManager.getMainConfigFile().getCanonicalPath()); } catch (IOException ignored) {} + Logger.global.logInfo("If you have changed the config you can simply reload the plugin using: /bluemap reload"); + + return; + } + } + + resourceExtensionsFile.delete(); + FileUtils.copyURLToFile(Plugin.class.getResource("/resourceExtensions.zip"), resourceExtensionsFile, 10000, 10000); + + //find more resource packs + File resourcePackFolder = new File(serverInterface.getConfigFolder(), "resourcepacks"); + resourcePackFolder.mkdirs(); + File[] resourcePacks = resourcePackFolder.listFiles(); + Arrays.sort(resourcePacks); //load resource packs in alphabetical order so you can reorder them by renaming + + List resources = new ArrayList<>(resourcePacks.length + 1); + resources.add(defaultResourceFile); + for (File file : resourcePacks) resources.add(file); + resources.add(resourceExtensionsFile); + + resourcePack = new ResourcePack(); + if (textureExportFile.exists()) resourcePack.loadTextureFile(textureExportFile); + resourcePack.load(resources); + resourcePack.saveTextureFile(textureExportFile); + + configManager.loadResourceConfigs(resourcePack); + + //load maps + for (MapConfig mapConfig : config.getMapConfigs()) { + String id = mapConfig.getId(); + String name = mapConfig.getName(); + + File worldFolder = new File(mapConfig.getWorldPath()); + if (!worldFolder.exists() || !worldFolder.isDirectory()) { + Logger.global.logError("Failed to load map '" + id + "': '" + worldFolder.getCanonicalPath() + "' does not exist or is no directory!", new IOException()); + continue; + } + + UUID worldUUID; + try { + worldUUID = serverInterface.getUUIDForWorld(worldFolder); + } catch (IOException e) { + Logger.global.logError("Failed to load map '" + id + "': Failed to get UUID for the world!", e); + continue; + } + + World world = worlds.get(worldUUID); + if (world == null) { + try { + world = MCAWorld.load(worldFolder.toPath(), worldUUID, configManager.getBlockIdConfig(), configManager.getBlockPropertiesConfig(), configManager.getBiomeConfig()); + worlds.put(worldUUID, world); + } catch (IOException e) { + Logger.global.logError("Failed to load map '" + id + "': Failed to read level.dat", e); + continue; + } + } + + //slice world to render edges if configured + if (mapConfig.isRenderEdges() && !(mapConfig.getMin().equals(RenderSettings.DEFAULT_MIN) && mapConfig.getMax().equals(RenderSettings.DEFAULT_MAX))) { + world = new SlicedWorld(world, mapConfig.getMin(), mapConfig.getMax()); + } + + HiresModelManager hiresModelManager = new HiresModelManager( + config.getWebDataPath().resolve(id).resolve("hires"), + resourcePack, + mapConfig, + new Vector2i(mapConfig.getHiresTileSize(), mapConfig.getHiresTileSize()), + ForkJoinPool.commonPool() + ); + + LowresModelManager lowresModelManager = new LowresModelManager( + config.getWebDataPath().resolve(id).resolve("lowres"), + new Vector2i(mapConfig.getLowresPointsPerLowresTile(), mapConfig.getLowresPointsPerLowresTile()), + new Vector2i(mapConfig.getLowresPointsPerHiresTile(), mapConfig.getLowresPointsPerHiresTile()) + ); + + TileRenderer tileRenderer = new TileRenderer(hiresModelManager, lowresModelManager); + + MapType mapType = new MapType(id, name, world, tileRenderer); + maps.put(id, mapType); + } + + //initialize render manager + renderManager = new RenderManager(config.getRenderThreadCount()); + renderManager.start(); + + //load render-manager state + try { + File saveFile = config.getDataPath().resolve("rmstate").toFile(); + saveFile.getParentFile().mkdirs(); + if (saveFile.exists()) { + try (DataInputStream in = new DataInputStream(new GZIPInputStream(new FileInputStream(saveFile)))) { + renderManager.readState(in, getMapTypes()); + } + } + saveFile.delete(); + } catch (IOException ex) { + Logger.global.logError("Failed to load render-manager state!", ex); + } + + //start map updater + this.updateHandler = new MapUpdateHandler(); + serverInterface.registerListener(updateHandler); + + //create/update webfiles + WebFilesManager webFilesManager = new WebFilesManager(config.getWebRoot()); + if (webFilesManager.needsUpdate()) { + webFilesManager.updateFiles(); + } + + WebSettings webSettings = new WebSettings(config.getWebDataPath().resolve("settings.json").toFile()); + webSettings.setAllEnabled(false); + for (MapType map : maps.values()) { + webSettings.setEnabled(true, map.getId()); + webSettings.setName(map.getName(), map.getId()); + webSettings.setFrom(map.getTileRenderer(), map.getId()); + } + int ordinal = 0; + for (MapConfig map : config.getMapConfigs()) { + if (!maps.containsKey(map.getId())) continue; //don't add not loaded maps + webSettings.setOrdinal(ordinal++, map.getId()); + webSettings.setHiresViewDistance(map.getHiresViewDistance(), map.getId()); + webSettings.setLowresViewDistance(map.getLowresViewDistance(), map.getId()); + } + webSettings.save(); + + //start webserver + if (config.isWebserverEnabled()) { + webServer = new BlueMapWebServer(config); + webServer.updateWebfiles(); + webServer.start(); + } + + //metrics + metricsThread = new Thread(() -> { + try { + Thread.sleep(TimeUnit.MINUTES.toMillis(1)); + + while (true) { + if (serverInterface.isMetricsEnabled(config.isMetricsEnabled())) Metrics.sendReport("Sponge"); + Thread.sleep(TimeUnit.MINUTES.toMillis(30)); + } + } catch (InterruptedException ex){ + return; + } + }); + metricsThread.start(); + + loaded = true; + } + + public synchronized void unload() { + + //unregister listeners + serverInterface.unregisterAllListeners(); + + //stop scheduled threads + if (metricsThread != null) metricsThread.interrupt(); + metricsThread = null; + + //stop services + if (renderManager != null) renderManager.stop(); + if (webServer != null) webServer.close(); + + //save render-manager state + if (updateHandler != null) updateHandler.flushTileBuffer(); //first write all buffered tiles to the render manager to save them too + if (renderManager != null) { + try { + File saveFile = config.getDataPath().resolve("rmstate").toFile(); + saveFile.getParentFile().mkdirs(); + if (saveFile.exists()) saveFile.delete(); + saveFile.createNewFile(); + + try (DataOutputStream out = new DataOutputStream(new GZIPOutputStream(new FileOutputStream(saveFile)))) { + renderManager.writeState(out); + } + } catch (IOException ex) { + Logger.global.logError("Failed to save render-manager state!", ex); + } + } + + //save renders + for (MapType map : maps.values()) { + map.getTileRenderer().save(); + } + + //clear resources and configs + renderManager = null; + webServer = null; + updateHandler = null; + resourcePack = null; + config = null; + maps.clear(); + worlds.clear(); + + loaded = false; + } + + public synchronized void reload() throws IOException, ParseResourceException { + unload(); + load(); + } + + public ServerInterface getServerInterface() { + return serverInterface; + } + + public Commands getCommands() { + return commands; + } + + public MainConfig getMainConfig() { + return config; + } + + public ResourcePack getResourcePack() { + return resourcePack; + } + + public World getWorld(UUID uuid){ + return worlds.get(uuid); + } + + public Collection getMapTypes(){ + return maps.values(); + } + + public RenderManager getRenderManager() { + return renderManager; + } + + public MapUpdateHandler getUpdateHandler() { + return updateHandler; + } + + public BlueMapWebServer getWebServer() { + return webServer; + } + + public boolean isLoaded() { + return loaded; + } + + public static Plugin getInstance() { + return instance; + } + +} diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/serverinterface/CommandSource.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/serverinterface/CommandSource.java new file mode 100644 index 00000000..f59900ca --- /dev/null +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/serverinterface/CommandSource.java @@ -0,0 +1,15 @@ +package de.bluecolored.bluemap.common.plugin.serverinterface; + +import de.bluecolored.bluemap.common.plugin.text.Text; + +public interface CommandSource { + + void sendMessage(Text text); + + default void sendMessages(Iterable textLines) { + for (Text text : textLines) { + sendMessage(text); + } + } + +} diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/serverinterface/ServerEventListener.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/serverinterface/ServerEventListener.java new file mode 100644 index 00000000..ee8ead13 --- /dev/null +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/serverinterface/ServerEventListener.java @@ -0,0 +1,16 @@ +package de.bluecolored.bluemap.common.plugin.serverinterface; + +import java.util.UUID; + +import com.flowpowered.math.vector.Vector2i; +import com.flowpowered.math.vector.Vector3i; + +public interface ServerEventListener { + + void onWorldSaveToDisk(UUID world); + + void onBlockChange(UUID world, Vector3i blockPos); + + void onChunkFinishedGeneration(UUID world, Vector2i chunkPos); + +} diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/serverinterface/ServerInterface.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/serverinterface/ServerInterface.java new file mode 100644 index 00000000..126be964 --- /dev/null +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/serverinterface/ServerInterface.java @@ -0,0 +1,42 @@ +package de.bluecolored.bluemap.common.plugin.serverinterface; + +import java.io.File; +import java.io.IOException; +import java.util.UUID; + +public interface ServerInterface { + + /** + * Registers a ServerEventListener, every method of this interface should be called on the specified events + */ + void registerListener(ServerEventListener listener); + + /** + * Removes all registered listeners + */ + void unregisterAllListeners(); + + /** + * Returns an {@link UUID} for the given world. + * The UUID does not need to persist over multiple runtime, but has to be always the same for this runtime. + * + * @param worldFolder The folder of the world + * @return The worlds {@link UUID} + * @throws IOException If the uuid is read from some file and there was an exception reading this file + */ + UUID getUUIDForWorld(File worldFolder) throws IOException; + + /** + * Returns the Folder containing the configurations for the plugin + */ + File getConfigFolder(); + + /** + * Gives the possibility to override the metrics-setting in the config + */ + default boolean isMetricsEnabled(boolean configValue) { + return configValue; + } + + +} diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/text/Text.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/text/Text.java new file mode 100644 index 00000000..e7d54ac2 --- /dev/null +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/text/Text.java @@ -0,0 +1,207 @@ +package de.bluecolored.bluemap.common.plugin.text; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class Text { + + private String content = ""; + private TextColor color; + private Set formats = new HashSet<>(); + private Text hoverText; + private String clickCommand; + private List children = new ArrayList<>(); + + public Text setHoverText(Text hoverText) { + this.hoverText = hoverText; + + return this; + } + + public Text setClickCommand(String clickCommand) { + this.clickCommand = clickCommand; + + return this; + } + + public Text addChild(Text child) { + children.add(child); + + return this; + } + + public String toJSONString() { + StringBuilder sb = new StringBuilder(); + sb.append("{"); + + sb.append(quote("text")).append(":").append(quote(content)).append(','); + + if (color != null) { + sb.append(quote("color")).append(":").append(quote(color.getId())).append(','); + } + + for (TextFormat format : formats) { + sb.append(quote(format.getId())).append(":").append(true).append(','); + } + + if (hoverText != null) { + sb.append(quote("hoverEvent")).append(":{"); + sb.append(quote("action")).append(":").append(quote("show_text")).append(','); + sb.append(quote("value")).append(":").append(quote(hoverText.toFormattingCodedString('§'))); + sb.append("},"); + } + + if (clickCommand != null) { + sb.append(quote("clickEvent")).append(":{"); + sb.append(quote("action")).append(":").append(quote("run_command")).append(','); + sb.append(quote("value")).append(":").append(quote(clickCommand)); + sb.append("},"); + } + + if (!children.isEmpty()) { + sb.append(quote("extra")).append(":["); + for (Text child : children) { + sb.append(child.toJSONString()).append(','); + } + sb.deleteCharAt(sb.length() - 1); //delete last , + sb.append("],"); + } + + if (sb.charAt(sb.length() - 1) == ',') sb.deleteCharAt(sb.length() - 1); //delete last , + + sb.append("}"); + return sb.toString(); + } + + public String toFormattingCodedString(char escapeChar) { + StringBuilder sb = new StringBuilder(); + + if (!content.isEmpty()) { + if (color != null) { + sb.append(escapeChar).append(color.getFormattingCode()); + } + + for (TextFormat format : formats) { + sb.append(escapeChar).append(format.getFormattingCode()); + } + + sb.append(content); + } + + for (Text child : children) { + if (sb.length() > 0) sb.append(escapeChar).append('r'); + sb.append(child.withParentFormat(this).toFormattingCodedString(escapeChar)); + } + + return sb.toString(); + } + + public String toPlainString() { + StringBuilder sb = new StringBuilder(); + + if (content != null) sb.append(content); + for (Text child : children) { + sb.append(child.toPlainString()); + } + + return sb.toString(); + } + + private Text withParentFormat(Text parent) { + Text text = new Text(); + + text.content = this.content; + text.clickCommand = this.clickCommand; + text.children = this.children; + + text.color = this.color != null ? this.color : parent.color; + + text.formats.addAll(this.formats); + text.formats.addAll(parent.formats); + + return text; + } + + private String quote(String value) { + return '"' + escape(value) + '"'; + } + + private String escape(String value) { + value = value.replace("\\", "\\\\"); + value = value.replace("\"", "\\\""); + value = value.replace("§", "\\u00a7"); + value = value.replace("\n", "\\n"); + return value; + } + + @Override + public String toString() { + return getClass().getSimpleName() + ":" + toJSONString(); + } + + public static Text of(String message) { + Text text = new Text(); + text.content = message; + return text; + } + + public static Text of(TextColor color, String message) { + Text text = new Text(); + text.content = message; + text.color = color; + return text; + } + + public static Text of(Object... objects) { + Text text = new Text(); + + Text currentChild = new Text(); + for (Object object : objects) { + + if (object instanceof Text) { + if (!currentChild.content.isEmpty()) { + text.addChild(currentChild); + currentChild = new Text().withParentFormat(currentChild); + } + + text.addChild((Text) object); + continue; + } + + if (object instanceof TextColor) { + if (!currentChild.content.isEmpty()) { + text.addChild(currentChild); + currentChild = new Text(); + } + + currentChild.color = (TextColor) object; + continue; + } + + if (object instanceof TextFormat) { + if (!currentChild.content.isEmpty()) { + text.addChild(currentChild); + currentChild = new Text().withParentFormat(currentChild); + } + + currentChild.formats.add((TextFormat) object); + continue; + } + + currentChild.content += object.toString(); + + } + + if (!currentChild.content.isEmpty()) { + text.addChild(currentChild); + } + + if (text.children.isEmpty()) return text; + if (text.children.size() == 1) return text.children.get(0); + + return text; + } + +} diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/text/TextColor.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/text/TextColor.java new file mode 100644 index 00000000..cf4c7ce5 --- /dev/null +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/text/TextColor.java @@ -0,0 +1,38 @@ +package de.bluecolored.bluemap.common.plugin.text; + +public enum TextColor { + + BLACK ("black", '0'), + DARK_BLUE ("dark_blue", '1'), + DARK_GREEN ("dark_green", '2'), + DARK_AQUA ("dark_aqua", '3'), + DARK_RED ("dark_red", '4'), + DARK_PURPLE ("dark_purple", '5'), + GOLD ("gold", '6'), + GRAY ("gray", '7'), + DARK_GRAY ("dark_gray", '8'), + BLUE ("blue", '9'), + GREEN ("green", 'a'), + AQUA ("aqua", 'b'), + RED ("red", 'c'), + LIGHT_PURPLE ("light_purple", 'd'), + YELLOW ("yellow", 'e'), + WHITE ("white", 'f'); + + private final String id; + private final char formattingCode; + + private TextColor(String id, char formattingCode) { + this.id = id; + this.formattingCode = formattingCode; + } + + public String getId() { + return id; + } + + public char getFormattingCode() { + return formattingCode; + } + +} diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/text/TextFormat.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/text/TextFormat.java new file mode 100644 index 00000000..d7da1d4a --- /dev/null +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/text/TextFormat.java @@ -0,0 +1,27 @@ +package de.bluecolored.bluemap.common.plugin.text; + +public enum TextFormat { + + OBFUSCATED ("obfuscated", 'k'), + BOLD ("bold", 'l'), + STRIKETHROUGH ("strikethrough", 'm'), + UNDERLINED ("underlined", 'n'), + ITALIC ("italic", 'o'); + + private final String id; + private final char formattingCode; + + private TextFormat(String id, char formattingCode) { + this.id = id; + this.formattingCode = formattingCode; + } + + public String getId() { + return id; + } + + public char getFormattingCode() { + return formattingCode; + } + +} diff --git a/src/main/resources/mcmod.info b/BlueMapCommon/src/main/resources/mcmod.info similarity index 87% rename from src/main/resources/mcmod.info rename to BlueMapCommon/src/main/resources/mcmod.info index 9fb1ffc6..ae6186eb 100644 --- a/src/main/resources/mcmod.info +++ b/BlueMapCommon/src/main/resources/mcmod.info @@ -4,7 +4,7 @@ "name": "BlueMap", "version": "0.2.1", "description": "A 3d-map of your Minecraft worlds view-able in your browser using three.js (WebGL)", - "url": "https://ore.spongepowered.org/Blue/BlueMap", + "url": "https://github.com/BlueMap-Minecraft", "authorList": [ "Blue (TBlueF, Lukas Rieger)" ], diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAWorld.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAWorld.java index 0dd3a79c..e020c3c3 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAWorld.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAWorld.java @@ -385,7 +385,10 @@ public static MCAWorld load(Path worldFolder, UUID uuid, BlockIdMapper blockIdMa ); try { - ListTag> blockIdReg = level.getCompoundTag("FML").getCompoundTag("Registries").getCompoundTag("minecraft:blocks").getListTag("ids"); + CompoundTag fmlTag = level.getCompoundTag("FML"); + if (fmlTag == null) fmlTag = level.getCompoundTag("fml"); + + ListTag> blockIdReg = fmlTag.getCompoundTag("Registries").getCompoundTag("minecraft:blocks").getListTag("ids"); for (Tag tag : blockIdReg) { if (tag instanceof CompoundTag) { CompoundTag entry = (CompoundTag) tag; diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/HiresModelManager.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/HiresModelManager.java index 2f487667..137308dc 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/HiresModelManager.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/HiresModelManager.java @@ -31,6 +31,9 @@ import java.io.PrintWriter; import java.nio.charset.StandardCharsets; import java.nio.file.Path; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.zip.GZIPOutputStream; @@ -107,6 +110,29 @@ private void save(HiresModel model, String modelJson){ } } + /** + * Returns all tiles that the provided chunks are intersecting + */ + public Collection getTilesForChunks(Iterable chunks){ + Set tiles = new HashSet<>(); + for (Vector2i chunk : chunks) { + Vector3i minBlockPos = new Vector3i(chunk.getX() * 16, 0, chunk.getY() * 16); + + //loop to cover the case that a tile is smaller then a chunk, should normally only add one tile (at 0, 0) + for (int x = 0; x < 15; x += getTileSize().getX()) { + for (int z = 0; z < 15; z += getTileSize().getY()) { + tiles.add(posToTile(minBlockPos.add(x, 0, z))); + } + } + + tiles.add(posToTile(minBlockPos.add(0, 0, 15))); + tiles.add(posToTile(minBlockPos.add(15, 0, 0))); + tiles.add(posToTile(minBlockPos.add(15, 0, 15))); + } + + return tiles; + } + /** * Returns the region of blocks that a tile includes */ diff --git a/BlueMapSponge/build.gradle b/BlueMapSponge/build.gradle index a1dbf64d..88c19eb9 100644 --- a/BlueMapSponge/build.gradle +++ b/BlueMapSponge/build.gradle @@ -1,5 +1,5 @@ dependencies { shadow "org.spongepowered:spongeapi:7.1.0-SNAPSHOT" compile group: 'org.bstats', name: 'bstats-sponge-lite', version: '1.5' - compile project(':BlueMapCore') + compile project(':BlueMapCommon') } diff --git a/BlueMapSponge/src/main/java/de/bluecolored/bluemap/sponge/Commands.java b/BlueMapSponge/src/main/java/de/bluecolored/bluemap/sponge/Commands.java deleted file mode 100644 index d7d7221e..00000000 --- a/BlueMapSponge/src/main/java/de/bluecolored/bluemap/sponge/Commands.java +++ /dev/null @@ -1,345 +0,0 @@ -package de.bluecolored.bluemap.sponge; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.UUID; - -import org.apache.commons.lang3.time.DurationFormatUtils; -import org.spongepowered.api.Sponge; -import org.spongepowered.api.command.CommandResult; -import org.spongepowered.api.command.CommandSource; -import org.spongepowered.api.command.args.GenericArguments; -import org.spongepowered.api.command.spec.CommandSpec; -import org.spongepowered.api.text.Text; -import org.spongepowered.api.text.action.TextActions; -import org.spongepowered.api.text.format.TextColors; -import org.spongepowered.api.world.Locatable; -import org.spongepowered.api.world.Location; -import org.spongepowered.api.world.storage.WorldProperties; - -import com.flowpowered.math.GenericMath; -import com.flowpowered.math.vector.Vector2i; -import com.flowpowered.math.vector.Vector3i; -import com.google.common.collect.Lists; - -import de.bluecolored.bluemap.core.logger.Logger; -import de.bluecolored.bluemap.core.mca.Chunk; -import de.bluecolored.bluemap.core.mca.ChunkAnvil112; -import de.bluecolored.bluemap.core.mca.MCAWorld; -import de.bluecolored.bluemap.core.render.hires.HiresModelManager; -import de.bluecolored.bluemap.core.world.Block; -import de.bluecolored.bluemap.core.world.World; - -public class Commands { - - private SpongePlugin plugin; - - public Commands(SpongePlugin plugin) { - this.plugin = plugin; - } - - public CommandSpec createRootCommand() { - - CommandSpec debugCommand = CommandSpec.builder() - .permission("bluemap.debug") - .description(Text.of("Prints some debug info")) - .extendedDescription(Text.of("Prints some information about how bluemap sees the blocks at and below your position")) - .executor((source, args) -> { - if (source instanceof Locatable) { - Location loc = ((Locatable) source).getLocation(); - UUID worldUuid = loc.getExtent().getUniqueId(); - World world = plugin.getWorld(worldUuid); - Block block = world.getBlock(loc.getBlockPosition()); - Block blockBelow = world.getBlock(loc.getBlockPosition().add(0, -1, 0)); - - String blockIdMeta = ""; - String blockBelowIdMeta = ""; - - if (world instanceof MCAWorld) { - try { - Chunk chunk = ((MCAWorld) world).getChunk(MCAWorld.blockToChunk(loc.getBlockPosition())); - if (chunk instanceof ChunkAnvil112) { - blockIdMeta = " (" + ((ChunkAnvil112) chunk).getBlockIdMeta(loc.getBlockPosition()) + ")"; - blockBelowIdMeta = " (" + ((ChunkAnvil112) chunk).getBlockIdMeta(loc.getBlockPosition().add(0, -1, 0)) + ")"; - } - } catch (IOException ex) { - Logger.global.logError("Failed to read chunk for debug!", ex); - } - } - - source.sendMessages(Lists.newArrayList( - Text.of(TextColors.GOLD, "Block at you: ", TextColors.RESET, block, TextColors.GRAY, blockIdMeta), - Text.of(TextColors.GOLD, "Block below you: ", TextColors.RESET, blockBelow, TextColors.GRAY, blockBelowIdMeta) - )); - } - - return CommandResult.success(); - }) - .build(); - - return CommandSpec.builder() - .description(Text.of("Displays BlueMaps render status")) - .permission("bluemap.status") - .childArgumentParseExceptionFallback(false) - .child(createReloadCommand(), "reload") - .child(createPauseRenderCommand(), "pause") - .child(createResumeRenderCommand(), "resume") - .child(createRenderCommand(), "render") - .child(debugCommand, "debug") - .executor((source, args) -> { - source.sendMessages(createStatusMessage()); - return CommandResult.success(); - }) - .build(); - } - - public CommandSpec createStandaloneReloadCommand() { - return CommandSpec.builder() - .description(Text.of("BlueMaps root command")) - .childArgumentParseExceptionFallback(false) - .child(createReloadCommand(), "reload") - .build(); - } - - public CommandSpec createReloadCommand() { - return CommandSpec.builder() - .description(Text.of("Reloads all resources and configuration-files")) - .permission("bluemap.reload") - .executor((source, args) -> { - source.sendMessage(Text.of(TextColors.GOLD, "Reloading BlueMap...")); - - plugin.getAsyncExecutor().submit(() -> { - try { - plugin.reload(); - - if (plugin.isLoaded()) { - source.sendMessage(Text.of(TextColors.GREEN, "BlueMap reloaded!")); - } else { - source.sendMessage(Text.of(TextColors.RED, "Could not load BlueMap! See the console for details!")); - } - - } catch (Exception ex) { - Logger.global.logError("Failed to reload BlueMap!", ex); - - source.sendMessage(Text.of(TextColors.RED, "There was an error reloading BlueMap! See the console for details!")); - } - }); - - return CommandResult.success(); - }) - .build(); - } - - public CommandSpec createPauseRenderCommand() { - return CommandSpec.builder() - .description(Text.of("Pauses all rendering")) - .permission("bluemap.pause") - .executor((source, args) -> { - if (plugin.getRenderManager().isRunning()) { - plugin.getRenderManager().stop(); - source.sendMessage(Text.of(TextColors.GREEN, "BlueMap rendering paused!")); - return CommandResult.success(); - } else { - source.sendMessage(Text.of(TextColors.RED, "BlueMap rendering are already paused!")); - return CommandResult.empty(); - } - }) - .build(); - } - - public CommandSpec createResumeRenderCommand() { - return CommandSpec.builder() - .description(Text.of("Resumes all paused rendering")) - .permission("bluemap.resume") - .executor((source, args) -> { - if (!plugin.getRenderManager().isRunning()) { - plugin.getRenderManager().start(); - source.sendMessage(Text.of(TextColors.GREEN, "BlueMap renders resumed!")); - return CommandResult.success(); - } else { - source.sendMessage(Text.of(TextColors.RED, "BlueMap renders are already running!")); - return CommandResult.empty(); - } - }) - .build(); - } - - public CommandSpec createRenderCommand() { - return CommandSpec.builder() - .description(Text.of("Renders the whole world")) - .permission("bluemap.rendertask.create.world") - .childArgumentParseExceptionFallback(false) - .child(createPrioritizeTaskCommand(), "prioritize") - .child(createRemoveTaskCommand(), "remove") - .arguments(GenericArguments.optional(GenericArguments.world(Text.of("world")))) - .executor((source, args) -> { - WorldProperties spongeWorld = args.getOne("world").orElse(null); - - if (spongeWorld == null && source instanceof Locatable) { - Location loc = ((Locatable) source).getLocation(); - spongeWorld = loc.getExtent().getProperties(); - } - - if (spongeWorld == null){ - source.sendMessage(Text.of(TextColors.RED, "You have to define a world to render!")); - return CommandResult.empty(); - } - - World world = plugin.getWorld(spongeWorld.getUniqueId()); - if (world == null) { - source.sendMessage(Text.of(TextColors.RED, "This world is not loaded with BlueMap! Maybe it is not configured?")); - } - - world.invalidateChunkCache(); - - Sponge.getScheduler().createTaskBuilder() - .async() - .execute(() -> createWorldRenderTask(source, world)) - .submit(plugin); - - return CommandResult.success(); - }) - .build(); - } - - public CommandSpec createPrioritizeTaskCommand() { - return CommandSpec.builder() - .description(Text.of("Prioritizes the render-task with the given uuid")) - .permission("bluemap.rendertask.prioritize") - .arguments(GenericArguments.uuid(Text.of("task-uuid"))) - .executor((source, args) -> { - Optional uuid = args.getOne("task-uuid"); - if (!uuid.isPresent()) { - source.sendMessage(Text.of("You need to specify a task-uuid")); - return CommandResult.empty(); - } - - for (RenderTask task : plugin.getRenderManager().getRenderTasks()) { - if (task.getUuid().equals(uuid.get())) { - plugin.getRenderManager().prioritizeRenderTask(task); - break; - } - } - - source.sendMessages(createStatusMessage()); - return CommandResult.success(); - }) - .build(); - } - - public CommandSpec createRemoveTaskCommand() { - return CommandSpec.builder() - .description(Text.of("Removes the render-task with the given uuid")) - .permission("bluemap.rendertask.remove") - .arguments(GenericArguments.uuid(Text.of("task-uuid"))) - .executor((source, args) -> { - Optional uuid = args.getOne("task-uuid"); - if (!uuid.isPresent()) { - source.sendMessage(Text.of("You need to specify a task-uuid")); - return CommandResult.empty(); - } - - for (RenderTask task : plugin.getRenderManager().getRenderTasks()) { - if (task.getUuid().equals(uuid.get())) { - plugin.getRenderManager().removeRenderTask(task); - break; - } - } - - source.sendMessages(createStatusMessage()); - return CommandResult.success(); - }) - .build(); - } - - private List createStatusMessage(){ - List lines = new ArrayList<>(); - - RenderManager renderer = plugin.getRenderManager(); - - lines.add(Text.EMPTY); - lines.add(Text.of(TextColors.BLUE, "Tile-Updates:")); - - if (renderer.isRunning()) { - lines.add(Text.of(TextColors.WHITE, " Render-Threads are ", Text.of(TextActions.runCommand("/bluemap pause"), TextActions.showText(Text.of("click to pause rendering")), TextColors.GREEN, "running"), TextColors.GRAY, "!")); - } else { - lines.add(Text.of(TextColors.WHITE, " Render-Threads are ", Text.of(TextActions.runCommand("/bluemap resume"), TextActions.showText(Text.of("click to resume rendering")), TextColors.RED, "paused"), TextColors.GRAY, "!")); - } - - lines.add(Text.of(TextColors.WHITE, " Scheduled tile-updates: ", Text.of(TextActions.showText(Text.of("tiles waiting for a free render-thread")), TextColors.GOLD, renderer.getQueueSize()), Text.of(TextActions.showText(Text.of("tiles waiting for world-save")), TextColors.GRAY, " + " + plugin.getUpdateHandler().getUpdateBufferCount()))); - - RenderTask[] tasks = renderer.getRenderTasks(); - if (tasks.length > 0) { - RenderTask task = tasks[0]; - - long time = task.getActiveTime(); - String durationString = DurationFormatUtils.formatDurationWords(time, true, true); - double pct = (double)task.getRenderedTileCount() / (double)(task.getRenderedTileCount() + task.getRemainingTileCount()); - - long ert = (long)((time / pct) * (1d - pct)); - String ertDurationString = DurationFormatUtils.formatDurationWords(ert, true, true); - - double tps = task.getRenderedTileCount() / (time / 1000.0); - - lines.add(Text.of(TextColors.BLUE, "Current task:")); - lines.add(Text.of(" ", createCancelTaskText(task), TextColors.WHITE, " Task ", TextColors.GOLD, task.getName(), TextColors.WHITE, " for map ", TextActions.showText(Text.of(TextColors.WHITE, "World: ", TextColors.GOLD, task.getMapType().getWorld().getName())), TextColors.GOLD, task.getMapType().getName())); - lines.add(Text.of(TextColors.WHITE, " rendered ", TextColors.GOLD, task.getRenderedTileCount(), TextColors.WHITE, " tiles ", TextColors.GRAY, "(" + (Math.round(pct * 1000)/10.0) + "% | " + GenericMath.round(tps, 1) + "t/s)", TextColors.WHITE, " in ", TextColors.GOLD, durationString)); - lines.add(Text.of(TextColors.WHITE, " with ", TextColors.GOLD, task.getRemainingTileCount(), TextColors.WHITE, " tiles to go. ETA: ", TextColors.GOLD, ertDurationString)); - } - - if (tasks.length > 1) { - lines.add(Text.of(TextColors.BLUE, "Waiting tasks:")); - for (int i = 1; i < tasks.length; i++) { - RenderTask task = tasks[i]; - lines.add(Text.of(" ", createCancelTaskText(task), createPrioritizeTaskText(task), TextColors.WHITE, " Task ", TextColors.GOLD, task.getName(), TextColors.WHITE, " for map ", Text.of(TextActions.showText(Text.of(TextColors.WHITE, "World: ", TextColors.GOLD, task.getMapType().getWorld().getName())), TextColors.GOLD, task.getMapType().getName()), TextColors.GRAY, " (" + task.getRemainingTileCount() + " tiles)")); - } - } - - return lines; - } - - private Text createCancelTaskText(RenderTask task) { - return Text.of(TextActions.runCommand("/bluemap render remove " + task.getUuid()), TextActions.showText(Text.of("click to remove this render-task")), TextColors.RED, "[X]"); - } - - private Text createPrioritizeTaskText(RenderTask task) { - return Text.of(TextActions.runCommand("/bluemap render prioritize " + task.getUuid()), TextActions.showText(Text.of("click to prioritize this render-task")), TextColors.GREEN, "[^]"); - } - - private void createWorldRenderTask(CommandSource source, World world) { - source.sendMessage(Text.of(TextColors.GOLD, "Collecting chunks to render...")); - Collection chunks = world.getChunkList(); - source.sendMessage(Text.of(TextColors.GREEN, chunks.size() + " chunks found!")); - - for (MapType map : SpongePlugin.getInstance().getMapTypes()) { - if (!map.getWorld().getUUID().equals(world.getUUID())) continue; - - source.sendMessage(Text.of(TextColors.GOLD, "Collecting tiles for map '" + map.getId() + "'")); - - HiresModelManager hmm = map.getTileRenderer().getHiresModelManager(); - Set tiles = new HashSet<>(); - for (Vector2i chunk : chunks) { - Vector3i minBlockPos = new Vector3i(chunk.getX() * 16, 0, chunk.getY() * 16); - tiles.add(hmm.posToTile(minBlockPos)); - tiles.add(hmm.posToTile(minBlockPos.add(0, 0, 15))); - tiles.add(hmm.posToTile(minBlockPos.add(15, 0, 0))); - tiles.add(hmm.posToTile(minBlockPos.add(15, 0, 15))); - } - - RenderTask task = new RenderTask("world-render", map); - task.addTiles(tiles); - task.optimizeQueue(); - plugin.getRenderManager().addRenderTask(task); - - source.sendMessage(Text.of(TextColors.GREEN, tiles.size() + " tiles found! Task created.")); - } - - source.sendMessage(Text.of(TextColors.GREEN, "All render tasks created! Use /bluemap to view the progress!")); - } - -} diff --git a/BlueMapSponge/src/main/java/de/bluecolored/bluemap/sponge/EventForwarder.java b/BlueMapSponge/src/main/java/de/bluecolored/bluemap/sponge/EventForwarder.java new file mode 100644 index 00000000..06bd3022 --- /dev/null +++ b/BlueMapSponge/src/main/java/de/bluecolored/bluemap/sponge/EventForwarder.java @@ -0,0 +1,76 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.bluecolored.bluemap.sponge; + +import java.util.Optional; + +import org.spongepowered.api.block.BlockSnapshot; +import org.spongepowered.api.data.Transaction; +import org.spongepowered.api.event.Listener; +import org.spongepowered.api.event.Order; +import org.spongepowered.api.event.block.ChangeBlockEvent; +import org.spongepowered.api.event.filter.type.Exclude; +import org.spongepowered.api.event.world.SaveWorldEvent; +import org.spongepowered.api.event.world.chunk.PopulateChunkEvent; +import org.spongepowered.api.world.Location; + +import com.flowpowered.math.vector.Vector2i; +import com.flowpowered.math.vector.Vector3i; + +import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener; + +public class EventForwarder { + + private ServerEventListener listener; + + public EventForwarder(ServerEventListener listener) { + this.listener = listener; + } + + @Listener(order = Order.POST) + public void onWorldSaveToDisk(SaveWorldEvent evt) { + listener.onWorldSaveToDisk(evt.getTargetWorld().getUniqueId()); + } + + @Listener(order = Order.POST) + @Exclude({ChangeBlockEvent.Post.class, ChangeBlockEvent.Pre.class, ChangeBlockEvent.Modify.class}) + public void onBlockChange(ChangeBlockEvent evt) { + for (Transaction tr : evt.getTransactions()) { + if(!tr.isValid()) continue; + + Optional> ow = tr.getFinal().getLocation(); + if (ow.isPresent()) { + listener.onBlockChange(ow.get().getExtent().getUniqueId(), ow.get().getPosition().toInt()); + } + } + } + + @Listener(order = Order.POST) + public void onChunkFinishedGeneration(PopulateChunkEvent.Post evt) { + Vector3i chunkPos = evt.getTargetChunk().getPosition(); + listener.onChunkFinishedGeneration(evt.getTargetChunk().getWorld().getUniqueId(), new Vector2i(chunkPos.getX(), chunkPos.getZ())); + } + +} diff --git a/BlueMapSponge/src/main/java/de/bluecolored/bluemap/sponge/SpongeCommandSource.java b/BlueMapSponge/src/main/java/de/bluecolored/bluemap/sponge/SpongeCommandSource.java new file mode 100644 index 00000000..8f9b80f6 --- /dev/null +++ b/BlueMapSponge/src/main/java/de/bluecolored/bluemap/sponge/SpongeCommandSource.java @@ -0,0 +1,46 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.bluecolored.bluemap.sponge; + +import org.spongepowered.api.text.serializer.TextSerializers; + +import de.bluecolored.bluemap.common.plugin.serverinterface.CommandSource; +import de.bluecolored.bluemap.common.plugin.text.Text; + +public class SpongeCommandSource implements CommandSource { + + private org.spongepowered.api.command.CommandSource delegate; + + public SpongeCommandSource(org.spongepowered.api.command.CommandSource delegate) { + this.delegate = delegate; + } + + @Override + public void sendMessage(Text text) { + org.spongepowered.api.text.Text spongeText = TextSerializers.JSON.deserializeUnchecked(text.toJSONString()); + delegate.sendMessage(spongeText); + } + +} diff --git a/BlueMapSponge/src/main/java/de/bluecolored/bluemap/sponge/SpongeCommands.java b/BlueMapSponge/src/main/java/de/bluecolored/bluemap/sponge/SpongeCommands.java new file mode 100644 index 00000000..b58b17d3 --- /dev/null +++ b/BlueMapSponge/src/main/java/de/bluecolored/bluemap/sponge/SpongeCommands.java @@ -0,0 +1,170 @@ +package de.bluecolored.bluemap.sponge; + +import java.util.Optional; +import java.util.UUID; + +import org.spongepowered.api.command.CommandResult; +import org.spongepowered.api.command.args.GenericArguments; +import org.spongepowered.api.command.spec.CommandSpec; +import org.spongepowered.api.text.Text; +import org.spongepowered.api.text.format.TextColors; +import org.spongepowered.api.world.Locatable; +import org.spongepowered.api.world.Location; +import org.spongepowered.api.world.storage.WorldProperties; + +import de.bluecolored.bluemap.common.plugin.Commands; + +public class SpongeCommands { + + private Commands commands; + + public SpongeCommands(Commands commands) { + this.commands = commands; + } + + public CommandSpec createRootCommand() { + return CommandSpec.builder() + .description(Text.of("Displays BlueMaps render status")) + .permission("bluemap.status") + .childArgumentParseExceptionFallback(false) + .child(createReloadCommand(), "reload") + .child(createPauseRenderCommand(), "pause") + .child(createResumeRenderCommand(), "resume") + .child(createRenderCommand(), "render") + .child(createDebugCommand(), "debug") + .executor((source, args) -> { + commands.executeRootCommand(new SpongeCommandSource(source)); + return CommandResult.success(); + }) + .build(); + } + + public CommandSpec createReloadCommand() { + return CommandSpec.builder() + .description(Text.of("Reloads all resources and configuration-files")) + .permission("bluemap.reload") + .executor((source, args) -> { + commands.executeReloadCommand(new SpongeCommandSource(source)); + return CommandResult.success(); + }) + .build(); + } + + public CommandSpec createPauseRenderCommand() { + return CommandSpec.builder() + .description(Text.of("Pauses all rendering")) + .permission("bluemap.pause") + .executor((source, args) -> { + if (commands.executePauseCommand(new SpongeCommandSource(source))) { + return CommandResult.success(); + } else { + return CommandResult.empty(); + } + }) + .build(); + } + + public CommandSpec createResumeRenderCommand() { + return CommandSpec.builder() + .description(Text.of("Resumes all paused rendering")) + .permission("bluemap.resume") + .executor((source, args) -> { + if (commands.executeResumeCommand(new SpongeCommandSource(source))) { + return CommandResult.success(); + } else { + return CommandResult.empty(); + } + }) + .build(); + } + + public CommandSpec createRenderCommand() { + return CommandSpec.builder() + .description(Text.of("Renders the whole world")) + .permission("bluemap.rendertask.create.world") + .childArgumentParseExceptionFallback(false) + .child(createPrioritizeTaskCommand(), "prioritize") + .child(createRemoveTaskCommand(), "remove") + .arguments(GenericArguments.optional(GenericArguments.world(Text.of("world")))) + .executor((source, args) -> { + WorldProperties spongeWorld = args.getOne("world").orElse(null); + + if (spongeWorld == null && source instanceof Locatable) { + Location loc = ((Locatable) source).getLocation(); + spongeWorld = loc.getExtent().getProperties(); + } + + if (spongeWorld == null){ + source.sendMessage(Text.of(TextColors.RED, "You have to define a world to render!")); + return CommandResult.empty(); + } + + if (commands.executeRenderWorldCommand(new SpongeCommandSource(source), spongeWorld.getUniqueId())) { + return CommandResult.success(); + } else { + return CommandResult.empty(); + } + }) + .build(); + } + + public CommandSpec createPrioritizeTaskCommand() { + return CommandSpec.builder() + .description(Text.of("Prioritizes the render-task with the given uuid")) + .permission("bluemap.rendertask.prioritize") + .arguments(GenericArguments.uuid(Text.of("task-uuid"))) + .executor((source, args) -> { + Optional uuid = args.getOne("task-uuid"); + if (!uuid.isPresent()) { + source.sendMessage(Text.of(TextColors.RED, "You need to specify a task-uuid")); + return CommandResult.empty(); + } + + commands.executePrioritizeRenderTaskCommand(new SpongeCommandSource(source), uuid.get()); + return CommandResult.success(); + }) + .build(); + } + + public CommandSpec createRemoveTaskCommand() { + return CommandSpec.builder() + .description(Text.of("Removes the render-task with the given uuid")) + .permission("bluemap.rendertask.remove") + .arguments(GenericArguments.uuid(Text.of("task-uuid"))) + .executor((source, args) -> { + Optional uuid = args.getOne("task-uuid"); + if (!uuid.isPresent()) { + source.sendMessage(Text.of(TextColors.RED, "You need to specify a task-uuid")); + return CommandResult.empty(); + } + + commands.executeRemoveRenderTaskCommand(new SpongeCommandSource(source), uuid.get()); + return CommandResult.success(); + }) + .build(); + } + + public CommandSpec createDebugCommand() { + return CommandSpec.builder() + .permission("bluemap.debug") + .description(Text.of("Prints some debug info")) + .extendedDescription(Text.of("Prints some information about how bluemap sees the blocks at and below your position")) + .executor((source, args) -> { + if (source instanceof Locatable) { + Location loc = ((Locatable) source).getLocation(); + UUID worldUuid = loc.getExtent().getUniqueId(); + + if (commands.executeDebugCommand(new SpongeCommandSource(source), worldUuid, loc.getBlockPosition())) { + return CommandResult.success(); + } else { + return CommandResult.empty(); + } + } + + source.sendMessage(Text.of(TextColors.RED, "You can only execute this command as a player!")); + return CommandResult.empty(); + }) + .build(); + } + +} diff --git a/BlueMapSponge/src/main/java/de/bluecolored/bluemap/sponge/SpongePlugin.java b/BlueMapSponge/src/main/java/de/bluecolored/bluemap/sponge/SpongePlugin.java index 96458db5..8c070220 100644 --- a/BlueMapSponge/src/main/java/de/bluecolored/bluemap/sponge/SpongePlugin.java +++ b/BlueMapSponge/src/main/java/de/bluecolored/bluemap/sponge/SpongePlugin.java @@ -24,29 +24,13 @@ */ package de.bluecolored.bluemap.sponge; -import java.io.DataInputStream; -import java.io.DataOutputStream; import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; import java.io.IOException; -import java.net.URL; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; import java.util.UUID; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.zip.GZIPInputStream; -import java.util.zip.GZIPOutputStream; import javax.inject.Inject; -import org.apache.commons.io.FileUtils; import org.bstats.sponge.MetricsLite2; import org.spongepowered.api.Sponge; import org.spongepowered.api.config.ConfigDir; @@ -54,48 +38,25 @@ import org.spongepowered.api.event.game.GameReloadEvent; import org.spongepowered.api.event.game.state.GameStartingServerEvent; import org.spongepowered.api.event.game.state.GameStoppingEvent; -import org.spongepowered.api.plugin.Plugin; import org.spongepowered.api.scheduler.SpongeExecutorService; import org.spongepowered.api.world.storage.WorldProperties; -import com.flowpowered.math.vector.Vector2i; - -import de.bluecolored.bluemap.core.BlueMap; -import de.bluecolored.bluemap.core.config.ConfigManager; -import de.bluecolored.bluemap.core.config.MainConfig; -import de.bluecolored.bluemap.core.config.MainConfig.MapConfig; +import de.bluecolored.bluemap.common.plugin.Plugin; +import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener; +import de.bluecolored.bluemap.common.plugin.serverinterface.ServerInterface; import de.bluecolored.bluemap.core.logger.Logger; -import de.bluecolored.bluemap.core.mca.MCAWorld; -import de.bluecolored.bluemap.core.metrics.Metrics; -import de.bluecolored.bluemap.core.render.RenderSettings; -import de.bluecolored.bluemap.core.render.TileRenderer; -import de.bluecolored.bluemap.core.render.hires.HiresModelManager; -import de.bluecolored.bluemap.core.render.lowres.LowresModelManager; -import de.bluecolored.bluemap.core.resourcepack.ParseResourceException; -import de.bluecolored.bluemap.core.resourcepack.ResourcePack; -import de.bluecolored.bluemap.core.web.BlueMapWebServer; -import de.bluecolored.bluemap.core.web.WebFilesManager; -import de.bluecolored.bluemap.core.web.WebSettings; -import de.bluecolored.bluemap.core.world.SlicedWorld; -import de.bluecolored.bluemap.core.world.World; import net.querz.nbt.CompoundTag; import net.querz.nbt.NBTUtil; -@Plugin( - id = SpongePlugin.PLUGIN_ID, - name = SpongePlugin.PLUGIN_NAME, +@org.spongepowered.api.plugin.Plugin( + id = Plugin.PLUGIN_ID, + name = Plugin.PLUGIN_NAME, authors = { "Blue (Lukas Rieger)" }, description = "This plugin provides a fully 3D map of your world for your browser!", - version = SpongePlugin.PLUGIN_VERSION + version = Plugin.PLUGIN_VERSION ) -public class SpongePlugin { +public class SpongePlugin implements ServerInterface { - public static final String PLUGIN_ID = "bluemap"; - public static final String PLUGIN_NAME = "BlueMap"; - public static final String PLUGIN_VERSION = BlueMap.VERSION; - - private static SpongePlugin instance; - @Inject @ConfigDir(sharedRoot = false) private Path configurationDir; @@ -104,261 +65,19 @@ public class SpongePlugin { @Inject private MetricsLite2 metrics; - private MainConfig config; - private ResourcePack resourcePack; - - private Map worlds; - private Map maps; - - private RenderManager renderManager; - private MapUpdateHandler updateHandler; - private BlueMapWebServer webServer; - - private SpongeExecutorService syncExecutor; - private SpongeExecutorService asyncExecutor; + private Plugin bluemap; - private boolean loaded = false; + private SpongeExecutorService asyncExecutor; @Inject public SpongePlugin(org.slf4j.Logger logger) { Logger.global = new Slf4jLogger(logger); - this.maps = new HashMap<>(); - this.worlds = new HashMap<>(); - - instance = this; - } - - public synchronized void load() throws ExecutionException, IOException, InterruptedException, ParseResourceException { - if (loaded) return; - unload(); //ensure nothing is left running (from a failed load or something) - - //register reload command in case bluemap crashes during loading - Sponge.getCommandManager().register(this, new Commands(this).createStandaloneReloadCommand(), "bluemap"); - - //load configs - URL defaultSpongeConfig = SpongePlugin.class.getResource("/bluemap-sponge.conf"); - URL spongeConfigDefaults = SpongePlugin.class.getResource("/bluemap-sponge-defaults.conf"); - ConfigManager configManager = new ConfigManager(getConfigPath().toFile(), defaultSpongeConfig, spongeConfigDefaults); - configManager.loadMainConfig(); - config = configManager.getMainConfig(); - - //load resources - File defaultResourceFile = config.getDataPath().resolve("minecraft-client-" + ResourcePack.MINECRAFT_CLIENT_VERSION + ".jar").toFile(); - File resourceExtensionsFile = config.getDataPath().resolve("resourceExtensions.zip").toFile(); - File textureExportFile = config.getWebDataPath().resolve("textures.json").toFile(); - - if (!defaultResourceFile.exists()) { - handleMissingResources(defaultResourceFile, configManager.getMainConfigFile()); - unload(); - - Sponge.getCommandManager().register(this, new Commands(this).createStandaloneReloadCommand(), "bluemap"); - return; - } - - resourceExtensionsFile.delete(); - FileUtils.copyURLToFile(SpongePlugin.class.getResource("/resourceExtensions.zip"), resourceExtensionsFile, 10000, 10000); - - //find more resource packs - File resourcePackFolder = getConfigPath().resolve("resourcepacks").toFile(); - resourcePackFolder.mkdirs(); - File[] resourcePacks = resourcePackFolder.listFiles(); - Arrays.sort(resourcePacks); //load resource packs in alphabetical order so you can reorder them by renaming - - List resources = new ArrayList<>(resourcePacks.length + 1); - resources.add(defaultResourceFile); - for (File file : resourcePacks) resources.add(file); - resources.add(resourceExtensionsFile); - - resourcePack = new ResourcePack(); - if (textureExportFile.exists()) resourcePack.loadTextureFile(textureExportFile); - resourcePack.load(resources); - resourcePack.saveTextureFile(textureExportFile); - - configManager.loadResourceConfigs(resourcePack); - - //load maps - for (MapConfig mapConfig : config.getMapConfigs()) { - String id = mapConfig.getId(); - String name = mapConfig.getName(); - - File worldFolder = new File(mapConfig.getWorldPath()); - if (!worldFolder.exists() || !worldFolder.isDirectory()) { - Logger.global.logError("Failed to load map '" + id + "': '" + worldFolder.getCanonicalPath() + "' does not exist or is no directory!", new IOException()); - continue; - } - - UUID worldUUID; - try { - CompoundTag levelSponge = (CompoundTag) NBTUtil.readTag(new File(worldFolder, "level_sponge.dat")); - CompoundTag spongeData = levelSponge.getCompoundTag("SpongeData"); - long most = spongeData.getLong("UUIDMost"); - long least = spongeData.getLong("UUIDLeast"); - worldUUID = new UUID(most, least); - } catch (Exception e) { - Logger.global.logError("Failed to load map '" + id + "': Failed to read level_sponge.dat", e); - continue; - } - - World world = worlds.get(worldUUID); - if (world == null) { - try { - world = MCAWorld.load(worldFolder.toPath(), worldUUID, configManager.getBlockIdConfig(), configManager.getBlockPropertiesConfig(), configManager.getBiomeConfig()); - worlds.put(worldUUID, world); - } catch (IOException e) { - Logger.global.logError("Failed to load map '" + id + "': Failed to read level.dat", e); - continue; - } - } - - //slice world to render edges if configured - if (mapConfig.isRenderEdges() && !(mapConfig.getMin().equals(RenderSettings.DEFAULT_MIN) && mapConfig.getMax().equals(RenderSettings.DEFAULT_MAX))) { - world = new SlicedWorld(world, mapConfig.getMin(), mapConfig.getMax()); - } - - HiresModelManager hiresModelManager = new HiresModelManager( - config.getWebDataPath().resolve(id).resolve("hires"), - resourcePack, - mapConfig, - new Vector2i(mapConfig.getHiresTileSize(), mapConfig.getHiresTileSize()), - getAsyncExecutor() - ); - - LowresModelManager lowresModelManager = new LowresModelManager( - config.getWebDataPath().resolve(id).resolve("lowres"), - new Vector2i(mapConfig.getLowresPointsPerLowresTile(), mapConfig.getLowresPointsPerLowresTile()), - new Vector2i(mapConfig.getLowresPointsPerHiresTile(), mapConfig.getLowresPointsPerHiresTile()) - ); - - TileRenderer tileRenderer = new TileRenderer(hiresModelManager, lowresModelManager); - - MapType mapType = new MapType(id, name, world, tileRenderer); - maps.put(id, mapType); - } - - //initialize render manager - renderManager = new RenderManager(config.getRenderThreadCount()); - renderManager.start(); - - //load render-manager state - try { - File saveFile = config.getDataPath().resolve("rmstate").toFile(); - saveFile.getParentFile().mkdirs(); - if (saveFile.exists()) { - try (DataInputStream in = new DataInputStream(new GZIPInputStream(new FileInputStream(saveFile)))) { - renderManager.readState(in); - } - } - saveFile.delete(); - } catch (IOException ex) { - Logger.global.logError("Failed to load render-manager state!", ex); - } - - //start map updater - this.updateHandler = new MapUpdateHandler(); - - //create/update webfiles - WebFilesManager webFilesManager = new WebFilesManager(config.getWebRoot()); - if (webFilesManager.needsUpdate()) { - webFilesManager.updateFiles(); - } - - WebSettings webSettings = new WebSettings(config.getWebDataPath().resolve("settings.json").toFile()); - webSettings.setAllEnabled(false); - for (MapType map : maps.values()) { - webSettings.setEnabled(true, map.getId()); - webSettings.setName(map.getName(), map.getId()); - webSettings.setFrom(map.getTileRenderer(), map.getId()); - } - int ordinal = 0; - for (MapConfig map : config.getMapConfigs()) { - if (!maps.containsKey(map.getId())) continue; //don't add not loaded maps - webSettings.setOrdinal(ordinal++, map.getId()); - webSettings.setHiresViewDistance(map.getHiresViewDistance(), map.getId()); - webSettings.setLowresViewDistance(map.getLowresViewDistance(), map.getId()); - } - webSettings.save(); - - //start webserver - if (config.isWebserverEnabled()) { - webServer = new BlueMapWebServer(config); - webServer.updateWebfiles(); - webServer.start(); - } - - //init commands - Sponge.getCommandManager().getOwnedBy(this).forEach(Sponge.getCommandManager()::removeMapping); - Sponge.getCommandManager().register(this, new Commands(this).createRootCommand(), "bluemap"); - - //metrics - Sponge.getScheduler().createTaskBuilder() - .async() - .delay(1, TimeUnit.MINUTES) - .interval(30, TimeUnit.MINUTES) - .execute(() -> { - if (Sponge.getMetricsConfigManager().areMetricsEnabled(this)) Metrics.sendReport("Sponge"); - }) - .submit(this); - - loaded = true; - } - - public synchronized void unload() { - //unregister commands - Sponge.getCommandManager().getOwnedBy(this).forEach(Sponge.getCommandManager()::removeMapping); - - //unregister listeners - if (updateHandler != null) Sponge.getEventManager().unregisterListeners(updateHandler); - - //stop scheduled tasks - Sponge.getScheduler().getScheduledTasks(this).forEach(t -> t.cancel()); - - //stop services - if (renderManager != null) renderManager.stop(); - if (webServer != null) webServer.close(); - - //save render-manager state - if (updateHandler != null) updateHandler.flushTileBuffer(); //first write all buffered tiles to the render manager to save them too - if (renderManager != null) { - try { - File saveFile = config.getDataPath().resolve("rmstate").toFile(); - saveFile.getParentFile().mkdirs(); - if (saveFile.exists()) saveFile.delete(); - saveFile.createNewFile(); - - try (DataOutputStream out = new DataOutputStream(new GZIPOutputStream(new FileOutputStream(saveFile)))) { - renderManager.writeState(out); - } - } catch (IOException ex) { - Logger.global.logError("Failed to save render-manager state!", ex); - } - } - - //save renders - for (MapType map : maps.values()) { - map.getTileRenderer().save(); - } - - //clear resources and configs - renderManager = null; - webServer = null; - updateHandler = null; - resourcePack = null; - config = null; - maps.clear(); - worlds.clear(); - - loaded = false; - } - - public synchronized void reload() throws IOException, ExecutionException, InterruptedException, ParseResourceException { - unload(); - load(); + this.bluemap = new Plugin("sponge", this); } @Listener public void onServerStart(GameStartingServerEvent evt) { - syncExecutor = Sponge.getScheduler().createSyncExecutor(this); asyncExecutor = Sponge.getScheduler().createAsyncExecutor(this); //save all world properties to generate level_sponge.dat files @@ -366,19 +85,23 @@ public void onServerStart(GameStartingServerEvent evt) { Sponge.getServer().saveWorldProperties(properties); } + Sponge.getCommandManager().register(this, new SpongeCommands(bluemap.getCommands()).createRootCommand(), "bluemap"); + asyncExecutor.execute(() -> { try { - load(); - if (isLoaded()) Logger.global.logInfo("Loaded!"); - } catch (Exception e) { - Logger.global.logError("Failed to load!", e); + Logger.global.logInfo("Loading..."); + bluemap.load(); + if (bluemap.isLoaded()) Logger.global.logInfo("Loaded!"); + } catch (Throwable t) { + Logger.global.logError("Failed to load!", t); } }); } @Listener public void onServerStop(GameStoppingEvent evt) { - unload(); + Logger.global.logInfo("Stopping..."); + bluemap.unload(); Logger.global.logInfo("Saved and stopped!"); } @@ -387,80 +110,40 @@ public void onServerReload(GameReloadEvent evt) { asyncExecutor.execute(() -> { try { Logger.global.logInfo("Reloading..."); - reload(); + bluemap.reload(); Logger.global.logInfo("Reloaded!"); } catch (Exception e) { Logger.global.logError("Failed to load!", e); } }); } - - private void handleMissingResources(File resourceFile, File mainConfigFile) { - if (config.isDownloadAccepted()) { - - //download file async - asyncExecutor.execute(() -> { - try { - Logger.global.logInfo("Downloading " + ResourcePack.MINECRAFT_CLIENT_URL + " to " + resourceFile + " ..."); - ResourcePack.downloadDefaultResource(resourceFile); - } catch (IOException e) { - Logger.global.logError("Failed to download resources!", e); - return; - } - // and reload - Logger.global.logInfo("Download finished! Reloading..."); - try { - reload(); - } catch (Exception e) { - Logger.global.logError("Failed to reload Bluemap!", e); - return; - } - Logger.global.logInfo("Reloaded!"); - }); - - } else { - Logger.global.logWarning("BlueMap is missing important resources!"); - Logger.global.logWarning("You need to accept the download of the required files in order of BlueMap to work!"); - try { Logger.global.logWarning("Please check: " + mainConfigFile.getCanonicalPath()); } catch (IOException ignored) {} - Logger.global.logInfo("If you have changed the config you can simply reload the plugin using: /bluemap reload"); + @Override + public void registerListener(ServerEventListener listener) { + Sponge.getEventManager().registerListeners(this, new EventForwarder(listener)); + } + + @Override + public void unregisterAllListeners() { + Sponge.getEventManager().unregisterPluginListeners(this); + } + + @Override + public UUID getUUIDForWorld(File worldFolder) throws IOException { + try { + CompoundTag levelSponge = (CompoundTag) NBTUtil.readTag(new File(worldFolder, "level_sponge.dat")); + CompoundTag spongeData = levelSponge.getCompoundTag("SpongeData"); + long most = spongeData.getLong("UUIDMost"); + long least = spongeData.getLong("UUIDLeast"); + return new UUID(most, least); + } catch (Throwable t) { + throw new IOException("Failed to read level_sponge.dat", t); } } - - public SpongeExecutorService getSyncExecutor(){ - return syncExecutor; - } - - public SpongeExecutorService getAsyncExecutor(){ - return asyncExecutor; - } - public World getWorld(UUID uuid){ - return worlds.get(uuid); - } - - public Collection getMapTypes(){ - return maps.values(); - } - - public RenderManager getRenderManager() { - return renderManager; - } - - public MapUpdateHandler getUpdateHandler() { - return updateHandler; - } - - public boolean isLoaded() { - return loaded; - } - - public Path getConfigPath(){ - return configurationDir; - } - - public static SpongePlugin getInstance() { - return instance; + @Override + public File getConfigFolder() { + return configurationDir.toFile(); } } diff --git a/build.gradle b/build.gradle index c4314b04..f79939c6 100644 --- a/build.gradle +++ b/build.gradle @@ -28,10 +28,13 @@ allprojects { dependencies { compile project(':BlueMapCLI') - //compile project(':BlueMapSponge') + compile project(':BlueMapBukkit') + compile project(':BlueMapSponge') } assemble.dependsOn shadowJar { + relocate 'org.bstats.bukkit', 'de.bluecolored.bluemap.bstats.bukkit' + baseName = 'BlueMap' version = null classifier = null diff --git a/settings.gradle b/settings.gradle index 1a85cd28..a481f48e 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,8 +1,12 @@ rootProject.name = 'BlueMap' include ':BlueMapCore' include ':BlueMapCLI' +include ':BlueMapCommon' include ':BlueMapSponge' +include ':BlueMapBukkit' project(':BlueMapCore').projectDir = "$rootDir/BlueMapCore" as File project(':BlueMapCLI').projectDir = "$rootDir/BlueMapCLI" as File -project(':BlueMapSponge').projectDir = "$rootDir/BlueMapSponge" as File \ No newline at end of file +project(':BlueMapCommon').projectDir = "$rootDir/BlueMapCommon" as File +project(':BlueMapSponge').projectDir = "$rootDir/BlueMapSponge" as File +project(':BlueMapBukkit').projectDir = "$rootDir/BlueMapBukkit" as File \ No newline at end of file