Merge branch 'feature/plugins'

This commit is contained in:
Blue (Lukas Rieger) 2020-01-18 01:02:09 +01:00
commit 6b8dadd1dc
35 changed files with 2224 additions and 771 deletions

View File

@ -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')
}

View File

@ -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('§'));
});
}
}

View File

@ -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<Command> 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);
}
}
}

View File

@ -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<UUID> 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;
}
}

View File

@ -0,0 +1,133 @@
/*
* This file is part of BlueMap, licensed under the MIT License (MIT).
*
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
* 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<ServerEventListener> 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));
}
}

View File

@ -0,0 +1,70 @@
/*
* This file is part of BlueMapSponge, licensed under the MIT License (MIT).
*
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
* 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);
}
}

View File

@ -0,0 +1,10 @@
accept-download: false
metrics: true
renderThreadCount: -2
data: "bluemap"
webroot: "bluemap/web"
webserver {
enabled: true
port: 8100
maxConnectionCount: 100
}

View File

@ -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: <data>/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 "<webroot>/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
}
]

View File

@ -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: |
/<command>
/<command> reload
/<command> pause
/<command> resume
/<command> render [world]
/<command> 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

View File

@ -31,10 +31,8 @@
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ForkJoinPool; import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -49,7 +47,6 @@
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import com.flowpowered.math.vector.Vector2i; import com.flowpowered.math.vector.Vector2i;
import com.flowpowered.math.vector.Vector3i;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import de.bluecolored.bluemap.core.config.ConfigManager; import de.bluecolored.bluemap.core.config.ConfigManager;
@ -162,14 +159,7 @@ public void renderMaps() throws IOException {
} }
HiresModelManager hiresModelManager = map.getTileRenderer().getHiresModelManager(); HiresModelManager hiresModelManager = map.getTileRenderer().getHiresModelManager();
Set<Vector2i> tiles = new HashSet<>(); Collection<Vector2i> tiles = hiresModelManager.getTilesForChunks(chunks);
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)));
}
Logger.global.logInfo("Found " + tiles.size() + " tiles to render! (" + chunks.size() + " chunks)"); Logger.global.logInfo("Found " + tiles.size() + " tiles to render! (" + chunks.size() + " chunks)");
if (!forceRender && chunks.size() == 0) { 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)"); 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)");

View File

@ -0,0 +1,3 @@
dependencies {
compile project(':BlueMapCore')
}

View File

@ -22,7 +22,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE. * THE SOFTWARE.
*/ */
package de.bluecolored.bluemap.sponge; package de.bluecolored.bluemap.common;
import java.io.IOException; import java.io.IOException;

View File

@ -1,4 +1,4 @@
package de.bluecolored.bluemap.sponge; package de.bluecolored.bluemap.common;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.io.DataOutputStream; 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<MapType> mapTypes) throws IOException {
//read renderTickets //read renderTickets
int mapCount = in.readInt(); int mapCount = in.readInt();
for (int i = 0; i < mapCount; i++) { for (int i = 0; i < mapCount; i++) {
String mapId = in.readUTF(); String mapId = in.readUTF();
MapType mapType = null; MapType mapType = null;
for (MapType map : SpongePlugin.getInstance().getMapTypes()) { for (MapType map : mapTypes) {
if (map.getId().equals(mapId)) { if (map.getId().equals(mapId)) {
mapType = map; mapType = map;
break; break;
@ -227,7 +227,7 @@ public void readState(DataInputStream in) throws IOException {
int taskCount = in.readInt(); int taskCount = in.readInt();
for (int i = 0; i < taskCount; i++) { for (int i = 0; i < taskCount; i++) {
try { try {
RenderTask task = RenderTask.read(in); RenderTask task = RenderTask.read(in, mapTypes);
addRenderTask(task); addRenderTask(task);
} catch (IOException ex) { } catch (IOException ex) {
Logger.global.logWarning("A render-task can not be loaded. It will be discared. (Error message: " + ex.toString() + ")"); Logger.global.logWarning("A render-task can not be loaded. It will be discared. (Error message: " + ex.toString() + ")");

View File

@ -1,4 +1,4 @@
package de.bluecolored.bluemap.sponge; package de.bluecolored.bluemap.common;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.io.DataOutputStream; 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<MapType> mapTypes) throws IOException {
String name = in.readUTF(); String name = in.readUTF();
String mapId = in.readUTF(); String mapId = in.readUTF();
MapType mapType = null; MapType mapType = null;
for (MapType map : SpongePlugin.getInstance().getMapTypes()) { for (MapType map : mapTypes) {
if (map.getId().equals(mapId)) { if (map.getId().equals(mapId)) {
mapType = map; mapType = map;
break; break;

View File

@ -1,4 +1,4 @@
package de.bluecolored.bluemap.sponge; package de.bluecolored.bluemap.common;
import java.io.IOException; import java.io.IOException;
import java.util.Objects; import java.util.Objects;

View File

@ -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:
*
* <ul>
* <li>/bluemap</li>
* <li>/bluemap reload</li>
* <li>/bluemap pause</li>
* <li>/bluemap resume</li>
* <li>/bluemap render [world]</li>
* <li>/bluemap render prioritize [task-uuid]</li>
* <li>/bluemap render remove [task-uuid]</li>
* <li>/bluemap debug</li>
* </ul>
*/
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<Text> createStatusMessage(){
List<Text> 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<Vector2i> 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<Vector2i> 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;
}
}

View File

@ -1,45 +1,34 @@
package de.bluecolored.bluemap.sponge; package de.bluecolored.bluemap.common.plugin;
import java.util.Iterator; import java.util.Iterator;
import java.util.Optional;
import java.util.UUID; 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.Vector2i;
import com.flowpowered.math.vector.Vector3i; import com.flowpowered.math.vector.Vector3i;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
import com.google.common.collect.MultimapBuilder; 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<MapType, Vector2i> updateBuffer; public Multimap<MapType, Vector2i> updateBuffer;
public MapUpdateHandler() { public MapUpdateHandler() {
updateBuffer = MultimapBuilder.hashKeys().hashSetValues().build(); updateBuffer = MultimapBuilder.hashKeys().hashSetValues().build();
Sponge.getEventManager().registerListeners(SpongePlugin.getInstance(), this);
} }
@Listener(order = Order.POST) @Override
public void onWorldSave(SaveWorldEvent.Post evt) { public void onWorldSaveToDisk(UUID world) {
UUID worldUuid = evt.getTargetWorld().getUniqueId(); RenderManager renderManager = Plugin.getInstance().getRenderManager();
RenderManager renderManager = SpongePlugin.getInstance().getRenderManager();
synchronized (updateBuffer) { synchronized (updateBuffer) {
Iterator<MapType> iterator = updateBuffer.keys().iterator(); Iterator<MapType> iterator = updateBuffer.keys().iterator();
while (iterator.hasNext()) { while (iterator.hasNext()) {
MapType map = iterator.next(); MapType map = iterator.next();
if (map.getWorld().getUUID().equals(worldUuid)) { if (map.getWorld().getUUID().equals(world)) {
renderManager.createTickets(map, updateBuffer.get(map)); renderManager.createTickets(map, updateBuffer.get(map));
iterator.remove(); iterator.remove();
} }
@ -48,27 +37,17 @@ public void onWorldSave(SaveWorldEvent.Post evt) {
} }
} }
@Listener(order = Order.POST) @Override
@Exclude({ChangeBlockEvent.Post.class, ChangeBlockEvent.Pre.class, ChangeBlockEvent.Modify.class}) public void onBlockChange(UUID world, Vector3i blockPos) {
public void onBlockChange(ChangeBlockEvent evt) {
synchronized (updateBuffer) { synchronized (updateBuffer) {
for (Transaction<BlockSnapshot> tr : evt.getTransactions()) { updateBlock(world, blockPos);
if (!tr.isValid()) continue;
Optional<Location<org.spongepowered.api.world.World>> ow = tr.getFinal().getLocation();
if (ow.isPresent()) {
updateBlock(ow.get().getExtent().getUniqueId(), ow.get().getPosition().toInt());
}
}
} }
} }
@Listener(order = Order.POST) @Override
public void onChunkPopulate(PopulateChunkEvent.Post evt) { public void onChunkFinishedGeneration(UUID world, Vector2i chunkPos) {
UUID world = evt.getTargetChunk().getWorld().getUniqueId(); int x = chunkPos.getX();
int z = chunkPos.getY();
int x = evt.getTargetChunk().getPosition().getX();
int z = evt.getTargetChunk().getPosition().getZ();
// also update the chunks around, because they might be modified or not rendered yet due to finalizations // also update the chunks around, because they might be modified or not rendered yet due to finalizations
for (int dx = -1; dx <= 1; dx++) { 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){ private void updateBlock(UUID world, Vector3i pos){
synchronized (updateBuffer) { synchronized (updateBuffer) {
for (MapType mapType : SpongePlugin.getInstance().getMapTypes()) { for (MapType mapType : Plugin.getInstance().getMapTypes()) {
if (mapType.getWorld().getUUID().equals(world)) { if (mapType.getWorld().getUUID().equals(world)) {
mapType.getWorld().invalidateChunkCache(mapType.getWorld().blockPosToChunkPos(pos)); mapType.getWorld().invalidateChunkCache(mapType.getWorld().blockPosToChunkPos(pos));
@ -112,7 +91,7 @@ public int getUpdateBufferCount() {
} }
public void flushTileBuffer() { public void flushTileBuffer() {
RenderManager renderManager = SpongePlugin.getInstance().getRenderManager(); RenderManager renderManager = Plugin.getInstance().getRenderManager();
synchronized (updateBuffer) { synchronized (updateBuffer) {
for (MapType map : updateBuffer.keySet()) { for (MapType map : updateBuffer.keySet()) {

View File

@ -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<UUID, World> worlds;
private Map<String, MapType> 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<File> 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<MapType> 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;
}
}

View File

@ -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<Text> textLines) {
for (Text text : textLines) {
sendMessage(text);
}
}
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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<TextFormat> formats = new HashSet<>();
private Text hoverText;
private String clickCommand;
private List<Text> 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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -4,7 +4,7 @@
"name": "BlueMap", "name": "BlueMap",
"version": "0.2.1", "version": "0.2.1",
"description": "A 3d-map of your Minecraft worlds view-able in your browser using three.js (WebGL)", "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": [ "authorList": [
"Blue (TBlueF, Lukas Rieger)" "Blue (TBlueF, Lukas Rieger)"
], ],

View File

@ -385,7 +385,10 @@ public static MCAWorld load(Path worldFolder, UUID uuid, BlockIdMapper blockIdMa
); );
try { try {
ListTag<? extends Tag<?>> blockIdReg = level.getCompoundTag("FML").getCompoundTag("Registries").getCompoundTag("minecraft:blocks").getListTag("ids"); CompoundTag fmlTag = level.getCompoundTag("FML");
if (fmlTag == null) fmlTag = level.getCompoundTag("fml");
ListTag<? extends Tag<?>> blockIdReg = fmlTag.getCompoundTag("Registries").getCompoundTag("minecraft:blocks").getListTag("ids");
for (Tag<?> tag : blockIdReg) { for (Tag<?> tag : blockIdReg) {
if (tag instanceof CompoundTag) { if (tag instanceof CompoundTag) {
CompoundTag entry = (CompoundTag) tag; CompoundTag entry = (CompoundTag) tag;

View File

@ -31,6 +31,9 @@
import java.io.PrintWriter; import java.io.PrintWriter;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Path; 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.concurrent.ExecutorService;
import java.util.zip.GZIPOutputStream; 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<Vector2i> getTilesForChunks(Iterable<Vector2i> chunks){
Set<Vector2i> 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 * Returns the region of blocks that a tile includes
*/ */

View File

@ -1,5 +1,5 @@
dependencies { dependencies {
shadow "org.spongepowered:spongeapi:7.1.0-SNAPSHOT" shadow "org.spongepowered:spongeapi:7.1.0-SNAPSHOT"
compile group: 'org.bstats', name: 'bstats-sponge-lite', version: '1.5' compile group: 'org.bstats', name: 'bstats-sponge-lite', version: '1.5'
compile project(':BlueMapCore') compile project(':BlueMapCommon')
} }

View File

@ -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<org.spongepowered.api.world.World> 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.<WorldProperties>getOne("world").orElse(null);
if (spongeWorld == null && source instanceof Locatable) {
Location<org.spongepowered.api.world.World> 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> uuid = args.<UUID>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> uuid = args.<UUID>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<Text> createStatusMessage(){
List<Text> 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<Vector2i> 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<Vector2i> 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!"));
}
}

View File

@ -0,0 +1,76 @@
/*
* This file is part of BlueMap, licensed under the MIT License (MIT).
*
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
* 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<BlockSnapshot> tr : evt.getTransactions()) {
if(!tr.isValid()) continue;
Optional<Location<org.spongepowered.api.world.World>> 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()));
}
}

View File

@ -0,0 +1,46 @@
/*
* This file is part of BlueMap, licensed under the MIT License (MIT).
*
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
* 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);
}
}

View File

@ -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.<WorldProperties>getOne("world").orElse(null);
if (spongeWorld == null && source instanceof Locatable) {
Location<org.spongepowered.api.world.World> 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> uuid = args.<UUID>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> uuid = args.<UUID>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<org.spongepowered.api.world.World> 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();
}
}

View File

@ -24,29 +24,13 @@
*/ */
package de.bluecolored.bluemap.sponge; package de.bluecolored.bluemap.sponge;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.net.URL;
import java.nio.file.Path; 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.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 javax.inject.Inject;
import org.apache.commons.io.FileUtils;
import org.bstats.sponge.MetricsLite2; import org.bstats.sponge.MetricsLite2;
import org.spongepowered.api.Sponge; import org.spongepowered.api.Sponge;
import org.spongepowered.api.config.ConfigDir; import org.spongepowered.api.config.ConfigDir;
@ -54,47 +38,24 @@
import org.spongepowered.api.event.game.GameReloadEvent; import org.spongepowered.api.event.game.GameReloadEvent;
import org.spongepowered.api.event.game.state.GameStartingServerEvent; import org.spongepowered.api.event.game.state.GameStartingServerEvent;
import org.spongepowered.api.event.game.state.GameStoppingEvent; import org.spongepowered.api.event.game.state.GameStoppingEvent;
import org.spongepowered.api.plugin.Plugin;
import org.spongepowered.api.scheduler.SpongeExecutorService; import org.spongepowered.api.scheduler.SpongeExecutorService;
import org.spongepowered.api.world.storage.WorldProperties; import org.spongepowered.api.world.storage.WorldProperties;
import com.flowpowered.math.vector.Vector2i; import de.bluecolored.bluemap.common.plugin.Plugin;
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener;
import de.bluecolored.bluemap.core.BlueMap; import de.bluecolored.bluemap.common.plugin.serverinterface.ServerInterface;
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.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.CompoundTag;
import net.querz.nbt.NBTUtil; import net.querz.nbt.NBTUtil;
@Plugin( @org.spongepowered.api.plugin.Plugin(
id = SpongePlugin.PLUGIN_ID, id = Plugin.PLUGIN_ID,
name = SpongePlugin.PLUGIN_NAME, name = Plugin.PLUGIN_NAME,
authors = { "Blue (Lukas Rieger)" }, authors = { "Blue (Lukas Rieger)" },
description = "This plugin provides a fully 3D map of your world for your browser!", 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 @Inject
@ConfigDir(sharedRoot = false) @ConfigDir(sharedRoot = false)
@ -104,261 +65,19 @@ public class SpongePlugin {
@Inject @Inject
private MetricsLite2 metrics; private MetricsLite2 metrics;
private MainConfig config; private Plugin bluemap;
private ResourcePack resourcePack;
private Map<UUID, World> worlds;
private Map<String, MapType> maps;
private RenderManager renderManager;
private MapUpdateHandler updateHandler;
private BlueMapWebServer webServer;
private SpongeExecutorService syncExecutor;
private SpongeExecutorService asyncExecutor; private SpongeExecutorService asyncExecutor;
private boolean loaded = false;
@Inject @Inject
public SpongePlugin(org.slf4j.Logger logger) { public SpongePlugin(org.slf4j.Logger logger) {
Logger.global = new Slf4jLogger(logger); Logger.global = new Slf4jLogger(logger);
this.maps = new HashMap<>(); this.bluemap = new Plugin("sponge", this);
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<File> 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();
} }
@Listener @Listener
public void onServerStart(GameStartingServerEvent evt) { public void onServerStart(GameStartingServerEvent evt) {
syncExecutor = Sponge.getScheduler().createSyncExecutor(this);
asyncExecutor = Sponge.getScheduler().createAsyncExecutor(this); asyncExecutor = Sponge.getScheduler().createAsyncExecutor(this);
//save all world properties to generate level_sponge.dat files //save all world properties to generate level_sponge.dat files
@ -366,19 +85,23 @@ public void onServerStart(GameStartingServerEvent evt) {
Sponge.getServer().saveWorldProperties(properties); Sponge.getServer().saveWorldProperties(properties);
} }
Sponge.getCommandManager().register(this, new SpongeCommands(bluemap.getCommands()).createRootCommand(), "bluemap");
asyncExecutor.execute(() -> { asyncExecutor.execute(() -> {
try { try {
load(); Logger.global.logInfo("Loading...");
if (isLoaded()) Logger.global.logInfo("Loaded!"); bluemap.load();
} catch (Exception e) { if (bluemap.isLoaded()) Logger.global.logInfo("Loaded!");
Logger.global.logError("Failed to load!", e); } catch (Throwable t) {
Logger.global.logError("Failed to load!", t);
} }
}); });
} }
@Listener @Listener
public void onServerStop(GameStoppingEvent evt) { public void onServerStop(GameStoppingEvent evt) {
unload(); Logger.global.logInfo("Stopping...");
bluemap.unload();
Logger.global.logInfo("Saved and stopped!"); Logger.global.logInfo("Saved and stopped!");
} }
@ -387,7 +110,7 @@ public void onServerReload(GameReloadEvent evt) {
asyncExecutor.execute(() -> { asyncExecutor.execute(() -> {
try { try {
Logger.global.logInfo("Reloading..."); Logger.global.logInfo("Reloading...");
reload(); bluemap.reload();
Logger.global.logInfo("Reloaded!"); Logger.global.logInfo("Reloaded!");
} catch (Exception e) { } catch (Exception e) {
Logger.global.logError("Failed to load!", e); Logger.global.logError("Failed to load!", e);
@ -395,72 +118,32 @@ public void onServerReload(GameReloadEvent evt) {
}); });
} }
private void handleMissingResources(File resourceFile, File mainConfigFile) { @Override
if (config.isDownloadAccepted()) { public void registerListener(ServerEventListener listener) {
Sponge.getEventManager().registerListeners(this, new EventForwarder(listener));
}
//download file async @Override
asyncExecutor.execute(() -> { public void unregisterAllListeners() {
Sponge.getEventManager().unregisterPluginListeners(this);
}
@Override
public UUID getUUIDForWorld(File worldFolder) throws IOException {
try { try {
Logger.global.logInfo("Downloading " + ResourcePack.MINECRAFT_CLIENT_URL + " to " + resourceFile + " ..."); CompoundTag levelSponge = (CompoundTag) NBTUtil.readTag(new File(worldFolder, "level_sponge.dat"));
ResourcePack.downloadDefaultResource(resourceFile); CompoundTag spongeData = levelSponge.getCompoundTag("SpongeData");
} catch (IOException e) { long most = spongeData.getLong("UUIDMost");
Logger.global.logError("Failed to download resources!", e); long least = spongeData.getLong("UUIDLeast");
return; return new UUID(most, least);
} } catch (Throwable t) {
throw new IOException("Failed to read level_sponge.dat", t);
// 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");
} }
} }
public SpongeExecutorService getSyncExecutor(){ @Override
return syncExecutor; public File getConfigFolder() {
} return configurationDir.toFile();
public SpongeExecutorService getAsyncExecutor(){
return asyncExecutor;
}
public World getWorld(UUID uuid){
return worlds.get(uuid);
}
public Collection<MapType> 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;
} }
} }

View File

@ -28,10 +28,13 @@ allprojects {
dependencies { dependencies {
compile project(':BlueMapCLI') compile project(':BlueMapCLI')
//compile project(':BlueMapSponge') compile project(':BlueMapBukkit')
compile project(':BlueMapSponge')
} }
assemble.dependsOn shadowJar { assemble.dependsOn shadowJar {
relocate 'org.bstats.bukkit', 'de.bluecolored.bluemap.bstats.bukkit'
baseName = 'BlueMap' baseName = 'BlueMap'
version = null version = null
classifier = null classifier = null

View File

@ -1,8 +1,12 @@
rootProject.name = 'BlueMap' rootProject.name = 'BlueMap'
include ':BlueMapCore' include ':BlueMapCore'
include ':BlueMapCLI' include ':BlueMapCLI'
include ':BlueMapCommon'
include ':BlueMapSponge' include ':BlueMapSponge'
include ':BlueMapBukkit'
project(':BlueMapCore').projectDir = "$rootDir/BlueMapCore" as File project(':BlueMapCore').projectDir = "$rootDir/BlueMapCore" as File
project(':BlueMapCLI').projectDir = "$rootDir/BlueMapCLI" as File project(':BlueMapCLI').projectDir = "$rootDir/BlueMapCLI" as File
project(':BlueMapCommon').projectDir = "$rootDir/BlueMapCommon" as File
project(':BlueMapSponge').projectDir = "$rootDir/BlueMapSponge" as File project(':BlueMapSponge').projectDir = "$rootDir/BlueMapSponge" as File
project(':BlueMapBukkit').projectDir = "$rootDir/BlueMapBukkit" as File