Move commands to common and start implementing spigot plugin

This commit is contained in:
Blue (Lukas Rieger) 2020-01-17 00:21:46 +01:00
parent b6cc6c5ea7
commit 3daab62714
14 changed files with 783 additions and 378 deletions

View File

@ -0,0 +1,31 @@
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) {
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

@ -4,21 +4,19 @@
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import de.bluecolored.bluemap.common.plugin.Plugin;
import de.bluecolored.bluemap.common.plugin.Commands;
public class Commands implements CommandExecutor {
private BukkitPlugin plugin;
private Plugin bluemap;
public class BukkitCommands implements CommandExecutor {
public Commands(BukkitPlugin plugin) {
this.plugin = plugin;
this.bluemap = plugin.getBlueMap();
private Commands commands;
public BukkitCommands(Commands commands) {
this.commands = commands;
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
commands.executeRootCommand(new BukkitCommandSource(sender));
return true;
}

View File

@ -17,14 +17,14 @@ public class BukkitPlugin extends JavaPlugin implements ServerInterface {
private Plugin bluemap;
private EventForwarder eventForwarder;
private Commands commands;
private BukkitCommands commands;
public BukkitPlugin() {
Logger.global = new JavaLogger(getLogger());
this.eventForwarder = new EventForwarder();
this.commands = new Commands(this);
this.bluemap = new Plugin("bukkit", this);
this.commands = new BukkitCommands(bluemap.getCommands());
}
@Override
@ -64,16 +64,18 @@ public void unregisterAllListeners() {
@Override
public UUID getUUIDForWorld(File worldFolder) throws IOException {
worldFolder = worldFolder.getCanonicalFile();
for (World world : getServer().getWorlds()) {
if (worldFolder.equals(world.getWorldFolder())) return world.getUID();
Logger.global.logInfo("Found world-folder: " + world.getWorldFolder().getCanonicalPath());
if (worldFolder.equals(world.getWorldFolder().getCanonicalFile())) return world.getUID();
}
throw new IOException("There is no world with this folder loaded!");
throw new IOException("There is no world with this folder loaded: " + worldFolder.getCanonicalPath());
}
@Override
public File getConfigFolder() {
return getConfigFolder();
return getDataFolder();
}
public Plugin getBlueMap() {

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

@ -1,7 +1,8 @@
name: BlueMap
main: de.bluecolored.bluemap.bukkit.BukkitPlugin
version: 0.1.0
version: 0.2.1
author: Blue (TBlueF / Lukas Rieger)
authors: [Blue (TBlueF / Lukas Rieger)]
website: "https://github.com/BlueMap-Minecraft"
commands:
bluemap:

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

@ -56,6 +56,7 @@ public class Plugin {
private String implementationType;
private ServerInterface serverInterface;
private Commands commands;
private MainConfig config;
private ResourcePack resourcePack;
@ -74,7 +75,9 @@ public class Plugin {
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<>();
@ -321,6 +324,10 @@ public ServerInterface getServerInterface() {
return serverInterface;
}
public Commands getCommands() {
return commands;
}
public MainConfig getMainConfig() {
return config;
}

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

@ -14,25 +14,29 @@ public class Text {
private String clickCommand;
private List<Text> children = new ArrayList<>();
public void setHoverText(Text hoverText) {
public Text setHoverText(Text hoverText) {
this.hoverText = hoverText;
return this;
}
public void setClickCommand(String clickCommand) {
public Text setClickCommand(String clickCommand) {
this.clickCommand = clickCommand;
return this;
}
public void addChild(Text child) {
public Text addChild(Text child) {
children.add(child);
return this;
}
public String toJSONString() {
StringBuilder sb = new StringBuilder();
sb.append("{");
if (!content.isEmpty()) {
sb.append(quote("text")).append(":").append(quote(content)).append(',');
}
sb.append(quote("text")).append(":").append(quote(content)).append(',');
if (color != null) {
sb.append(quote("color")).append(":").append(quote(color.getId())).append(',');
@ -46,23 +50,23 @@ public String toJSONString() {
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("}");
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("}");
sb.append("},");
}
if (!children.isEmpty()) {
sb.append(quote("")).append(":[");
sb.append(quote("extra")).append(":[");
for (Text child : children) {
sb.append(child.toJSONString()).append(',');
}
sb.deleteCharAt(sb.length() - 1); //delete last ,
sb.append("]");
sb.append("],");
}
if (sb.charAt(sb.length() - 1) == ',') sb.deleteCharAt(sb.length() - 1); //delete last ,
@ -87,7 +91,8 @@ public String toFormattingCodedString(char escapeChar) {
}
for (Text child : children) {
sb.append(escapeChar).append('r').append(child.withParentFormat(this).toFormattingCodedString(escapeChar));
if (sb.length() > 0) sb.append(escapeChar).append('r');
sb.append(child.withParentFormat(this).toFormattingCodedString(escapeChar));
}
return sb.toString();
@ -126,7 +131,7 @@ private String quote(String value) {
private String escape(String value) {
value = value.replace("\\", "\\\\");
value = value.replace("\"", "\\\"");
value = value.replace("§", "\\u00a76");
value = value.replace("§", "\\u00a7");
value = value.replace("\n", "\\n");
return value;
}
@ -194,7 +199,7 @@ public static Text of(Object... objects) {
}
if (text.children.isEmpty()) return text;
if (text.children.size() == 1) return text.children.get(1);
if (text.children.size() == 1) return text.children.get(0);
return text;
}

View File

@ -1,341 +0,0 @@
package de.bluecolored.bluemap.sponge;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
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.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.Plugin;
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;
private Plugin bluemap;
public Commands(SpongePlugin plugin) {
this.plugin = plugin;
this.bluemap = plugin.getBlueMap();
}
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 = bluemap.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 {
bluemap.reload();
if (bluemap.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 (bluemap.getRenderManager().isRunning()) {
bluemap.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 (!bluemap.getRenderManager().isRunning()) {
bluemap.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 = bluemap.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 : bluemap.getRenderManager().getRenderTasks()) {
if (task.getUuid().equals(uuid.get())) {
bluemap.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 : bluemap.getRenderManager().getRenderTasks()) {
if (task.getUuid().equals(uuid.get())) {
bluemap.getRenderManager().removeRenderTask(task);
break;
}
}
source.sendMessages(createStatusMessage());
return CommandResult.success();
})
.build();
}
private List<Text> createStatusMessage(){
List<Text> lines = new ArrayList<>();
RenderManager renderer = bluemap.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, " + " + bluemap.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 : bluemap.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();
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(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,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

@ -85,7 +85,7 @@ public void onServerStart(GameStartingServerEvent evt) {
Sponge.getServer().saveWorldProperties(properties);
}
Sponge.getCommandManager().register(this, new Commands(this).createRootCommand(), "bluemap");
Sponge.getCommandManager().register(this, new SpongeCommands(bluemap.getCommands()).createRootCommand(), "bluemap");
asyncExecutor.execute(() -> {
try {
@ -146,12 +146,4 @@ public File getConfigFolder() {
return configurationDir.toFile();
}
public SpongeExecutorService getAsyncExecutor() {
return asyncExecutor;
}
public Plugin getBlueMap() {
return bluemap;
}
}