/* * This file is part of BlueMap, licensed under the MIT License (MIT). * * Copyright (c) Blue (Lukas Rieger) * Copyright (c) contributors * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package de.bluecolored.bluemap.common.plugin.commands; import com.flowpowered.math.vector.Vector2i; import com.flowpowered.math.vector.Vector3d; import com.flowpowered.math.vector.Vector3i; import com.mojang.brigadier.Command; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.arguments.ArgumentType; import com.mojang.brigadier.arguments.DoubleArgumentType; import com.mojang.brigadier.arguments.IntegerArgumentType; import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.builder.ArgumentBuilder; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.builder.RequiredArgumentBuilder; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.tree.LiteralCommandNode; import de.bluecolored.bluemap.api.BlueMapAPI; import de.bluecolored.bluemap.api.BlueMapMap; import de.bluecolored.bluemap.api.marker.MarkerAPI; import de.bluecolored.bluemap.api.marker.MarkerSet; import de.bluecolored.bluemap.api.marker.POIMarker; import de.bluecolored.bluemap.common.plugin.Plugin; import de.bluecolored.bluemap.common.plugin.PluginState; 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.common.plugin.text.TextFormat; import de.bluecolored.bluemap.common.rendermanager.MapPurgeTask; import de.bluecolored.bluemap.common.rendermanager.MapUpdateTask; import de.bluecolored.bluemap.common.rendermanager.RenderTask; import de.bluecolored.bluemap.common.rendermanager.WorldRegionRenderTask; import de.bluecolored.bluemap.core.BlueMap; import de.bluecolored.bluemap.core.MinecraftVersion; import de.bluecolored.bluemap.core.debug.StateDumper; import de.bluecolored.bluemap.core.logger.Logger; import de.bluecolored.bluemap.core.map.BmMap; import de.bluecolored.bluemap.core.map.MapRenderState; import de.bluecolored.bluemap.core.resourcepack.ParseResourceException; import de.bluecolored.bluemap.core.world.Block; import de.bluecolored.bluemap.core.world.World; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.*; import java.util.function.Function; import java.util.function.Predicate; public class Commands { public static final String DEFAULT_MARKER_SET_ID = "markers"; private final Plugin plugin; private final CommandDispatcher dispatcher; private final Function commandSourceInterface; private final CommandHelper helper; public Commands(Plugin plugin, CommandDispatcher dispatcher, Function commandSourceInterface) { this.plugin = plugin; this.dispatcher = dispatcher; this.commandSourceInterface = commandSourceInterface; this.helper = new CommandHelper(plugin); init(); } public void init() { // commands LiteralCommandNode baseCommand = literal("bluemap") .requires(requirementsUnloaded("bluemap.status")) .executes(this::statusCommand) .build(); LiteralCommandNode versionCommand = literal("version") .requires(requirementsUnloaded("bluemap.version")) .executes(this::versionCommand) .build(); LiteralCommandNode helpCommand = literal("help") .requires(requirementsUnloaded("bluemap.help")) .executes(this::helpCommand) .build(); LiteralCommandNode reloadCommand = literal("reload") .requires(requirementsUnloaded("bluemap.reload")) .executes(this::reloadCommand) .build(); LiteralCommandNode debugCommand = literal("debug") .requires(requirements("bluemap.debug")) .then(literal("block") .executes(this::debugBlockCommand) .then(argument("world", StringArgumentType.string()).suggests(new WorldSuggestionProvider<>(plugin)) .then(argument("x", DoubleArgumentType.doubleArg()) .then(argument("y", DoubleArgumentType.doubleArg()) .then(argument("z", DoubleArgumentType.doubleArg()) .executes(this::debugBlockCommand)))))) .then(literal("flush") .executes(this::debugFlushCommand) .then(argument("world", StringArgumentType.string()).suggests(new WorldSuggestionProvider<>(plugin)) .executes(this::debugFlushCommand))) .then(literal("cache") .executes(this::debugClearCacheCommand)) .then(literal("dump") .executes(this::debugDumpCommand)) .build(); LiteralCommandNode stopCommand = literal("stop") .requires(requirements("bluemap.stop")) .executes(this::stopCommand) .build(); LiteralCommandNode startCommand = literal("start") .requires(requirements("bluemap.start")) .executes(this::startCommand) .build(); LiteralCommandNode freezeCommand = literal("freeze") .requires(requirements("bluemap.freeze")) .then(argument("map", StringArgumentType.string()).suggests(new MapSuggestionProvider<>(plugin)) .executes(this::freezeCommand)) .build(); LiteralCommandNode unfreezeCommand = literal("unfreeze") .requires(requirements("bluemap.freeze")) .then(argument("map", StringArgumentType.string()).suggests(new MapSuggestionProvider<>(plugin)) .executes(this::unfreezeCommand)) .build(); LiteralCommandNode forceUpdateCommand = addRenderArguments( literal("force-update") .requires(requirements("bluemap.update.force")), this::forceUpdateCommand ).build(); LiteralCommandNode updateCommand = addRenderArguments( literal("update") .requires(requirements("bluemap.update")), this::updateCommand ).build(); LiteralCommandNode purgeCommand = literal("purge") .requires(requirements("bluemap.purge")) .then(argument("map", StringArgumentType.string()).suggests(new MapSuggestionProvider<>(plugin)) .executes(this::purgeCommand)) .build(); LiteralCommandNode cancelCommand = literal("cancel") .requires(requirements("bluemap.cancel")) .executes(this::cancelCommand) .then(argument("task-ref", StringArgumentType.string()).suggests(new TaskRefSuggestionProvider<>(helper)) .executes(this::cancelCommand)) .build(); LiteralCommandNode worldsCommand = literal("worlds") .requires(requirements("bluemap.status")) .executes(this::worldsCommand) .build(); LiteralCommandNode mapsCommand = literal("maps") .requires(requirements("bluemap.status")) .executes(this::mapsCommand) .build(); LiteralCommandNode markerCommand = literal("marker") .requires(requirements("bluemap.marker")) .build(); LiteralCommandNode createMarkerCommand = literal("create") .requires(requirements("bluemap.marker")) .then(argument("id", StringArgumentType.word()) .then(argument("map", StringArgumentType.string()).suggests(new MapSuggestionProvider<>(plugin)) .then(argument("label", StringArgumentType.string()) .executes(this::createMarkerCommand)) .then(argument("x", DoubleArgumentType.doubleArg()) .then(argument("y", DoubleArgumentType.doubleArg()) .then(argument("z", DoubleArgumentType.doubleArg()) .then(argument("label", StringArgumentType.string()) .executes(this::createMarkerCommand))))))) .build(); LiteralCommandNode removeMarkerCommand = literal("remove") .requires(requirements("bluemap.marker")) .then(argument("id", StringArgumentType.word()).suggests(MarkerIdSuggestionProvider.getInstance()) .executes(this::removeMarkerCommand)) .build(); LiteralCommandNode listMarkersCommand = literal("list") .requires(requirements("bluemap.marker")) .executes(this::listMarkersCommand) .build(); // command tree dispatcher.getRoot().addChild(baseCommand); baseCommand.addChild(versionCommand); baseCommand.addChild(helpCommand); baseCommand.addChild(reloadCommand); baseCommand.addChild(debugCommand); baseCommand.addChild(stopCommand); baseCommand.addChild(startCommand); baseCommand.addChild(freezeCommand); baseCommand.addChild(unfreezeCommand); baseCommand.addChild(forceUpdateCommand); baseCommand.addChild(updateCommand); baseCommand.addChild(cancelCommand); baseCommand.addChild(purgeCommand); baseCommand.addChild(worldsCommand); baseCommand.addChild(mapsCommand); baseCommand.addChild(markerCommand); markerCommand.addChild(createMarkerCommand); markerCommand.addChild(removeMarkerCommand); markerCommand.addChild(listMarkersCommand); } private > B addRenderArguments(B builder, Command command) { return builder .executes(command) // /bluemap render .then(argument("radius", IntegerArgumentType.integer()) .executes(command)) // /bluemap render .then(argument("x", DoubleArgumentType.doubleArg()) .then(argument("z", DoubleArgumentType.doubleArg()) .then(argument("radius", IntegerArgumentType.integer()) .executes(command)))) // /bluemap render .then(argument("world|map", StringArgumentType.string()).suggests(new WorldOrMapSuggestionProvider<>(plugin)) .executes(command) // /bluemap render .then(argument("x", DoubleArgumentType.doubleArg()) .then(argument("z", DoubleArgumentType.doubleArg()) .then(argument("radius", IntegerArgumentType.integer()) .executes(command))))); // /bluemap render } private Predicate requirements(String permission){ return s -> { CommandSource source = commandSourceInterface.apply(s); return plugin.isLoaded() && source.hasPermission(permission); }; } private Predicate requirementsUnloaded(String permission){ return s -> { CommandSource source = commandSourceInterface.apply(s); return source.hasPermission(permission); }; } private LiteralArgumentBuilder literal(String name){ return LiteralArgumentBuilder.literal(name); } private RequiredArgumentBuilder argument(String name, ArgumentType type){ return RequiredArgumentBuilder.argument(name, type); } private Optional getOptionalArgument(CommandContext context, String argumentName, Class type) { try { return Optional.of(context.getArgument(argumentName, type)); } catch (IllegalArgumentException ex) { return Optional.empty(); } } private Optional parseWorld(String worldName) { for (World world : plugin.getWorlds()) { if (world.getName().equalsIgnoreCase(worldName)) { return Optional.of(world); } } return Optional.empty(); } private Optional parseMap(String mapId) { for (BmMap map : plugin.getMapTypes()) { if (map.getId().equalsIgnoreCase(mapId)) { return Optional.of(map); } } return Optional.empty(); } // --- COMMANDS --- public int statusCommand(CommandContext context) { CommandSource source = commandSourceInterface.apply(context.getSource()); if (!plugin.isLoaded()) { source.sendMessage(Text.of(TextColor.RED, "BlueMap is not loaded! Try /bluemap reload")); return 0; } source.sendMessages(helper.createStatusMessage()); return 1; } public int versionCommand(CommandContext context) { CommandSource source = commandSourceInterface.apply(context.getSource()); int renderThreadCount = 0; if (plugin.isLoaded()) { renderThreadCount = plugin.getRenderManager().getWorkerThreadCount(); } source.sendMessage(Text.of(TextFormat.BOLD, TextColor.BLUE, "Version: ", TextColor.WHITE, BlueMap.VERSION)); source.sendMessage(Text.of(TextColor.GRAY, "Commit: ", TextColor.WHITE, BlueMap.GIT_HASH + " (" + BlueMap.GIT_CLEAN + ")")); source.sendMessage(Text.of(TextColor.GRAY, "Implementation: ", TextColor.WHITE, plugin.getImplementationType())); source.sendMessage(Text.of( TextColor.GRAY, "Minecraft compatibility: ", TextColor.WHITE, plugin.getMinecraftVersion().getVersionString(), TextColor.GRAY, " (" + plugin.getMinecraftVersion().getResource().getVersion().getVersionString() + ")" )); source.sendMessage(Text.of(TextColor.GRAY, "Render-threads: ", TextColor.WHITE, renderThreadCount)); source.sendMessage(Text.of(TextColor.GRAY, "Available processors: ", TextColor.WHITE, Runtime.getRuntime().availableProcessors())); source.sendMessage(Text.of(TextColor.GRAY, "Available memory: ", TextColor.WHITE, (Runtime.getRuntime().maxMemory() / 1024L / 1024L) + " MiB")); if (plugin.getMinecraftVersion().isAtLeast(new MinecraftVersion(1, 15))) { String clipboardValue = "Version: " + BlueMap.VERSION + "\n" + "Commit: " + BlueMap.GIT_HASH + " (" + BlueMap.GIT_CLEAN + ")\n" + "Implementation: " + plugin.getImplementationType() + "\n" + "Minecraft compatibility: " + plugin.getMinecraftVersion().getVersionString() + " (" + plugin.getMinecraftVersion().getResource().getVersion().getVersionString() + ")\n" + "Render-threads: " + renderThreadCount + "\n" + "Available processors: " + Runtime.getRuntime().availableProcessors() + "\n" + "Available memory: " + Runtime.getRuntime().maxMemory() / 1024L / 1024L + " MiB"; source.sendMessage(Text.of(TextColor.DARK_GRAY, "[copy to clipboard]") .setClickAction(Text.ClickAction.COPY_TO_CLIPBOARD, clipboardValue) .setHoverText(Text.of(TextColor.GRAY, "click to copy the above text .. ", TextFormat.ITALIC, TextColor.GRAY, "duh!"))); } return 1; } public int helpCommand(CommandContext context) { CommandSource source = commandSourceInterface.apply(context.getSource()); source.sendMessage(Text.of(TextColor.BLUE, "BlueMap Commands:")); for (String usage : dispatcher.getAllUsage(dispatcher.getRoot().getChild("bluemap"), context.getSource(), true)) { Text usageText = Text.of(TextColor.GREEN, "/bluemap"); String[] arguments = usage.split(" "); for (String arg : arguments) { if (arg.isEmpty()) continue; if (arg.charAt(0) == '<' && arg.charAt(arg.length() - 1) == '>') { usageText.addChild(Text.of(TextColor.GRAY, " " + arg)); } else { usageText.addChild(Text.of(TextColor.WHITE, " " + arg)); } } source.sendMessage(usageText); } source.sendMessage( Text.of(TextColor.BLUE, "\nOpen this link to get a description for each command:\n") .addChild(Text.of(TextColor.GRAY, "https://bluecolo.red/bluemap-commands").setClickAction(Text.ClickAction.OPEN_URL, "https://bluecolo.red/bluemap-commands")) ); return 1; } public int reloadCommand(CommandContext context) { CommandSource source = commandSourceInterface.apply(context.getSource()); source.sendMessage(Text.of(TextColor.GOLD, "Reloading BlueMap...")); new Thread(() -> { try { plugin.reload(); if (plugin.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 (IOException | ParseResourceException | RuntimeException 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(); return 1; } public int debugClearCacheCommand(CommandContext context) { CommandSource source = commandSourceInterface.apply(context.getSource()); for (World world : plugin.getWorlds()) { world.invalidateChunkCache(); } source.sendMessage(Text.of(TextColor.GREEN, "All caches cleared!")); return 1; } public int debugFlushCommand(CommandContext context) { CommandSource source = commandSourceInterface.apply(context.getSource()); // parse arguments Optional worldName = getOptionalArgument(context, "world", String.class); final World world; if (worldName.isPresent()) { world = parseWorld(worldName.get()).orElse(null); if (world == null) { source.sendMessage(Text.of(TextColor.RED, "There is no ", helper.worldHelperHover(), " with this name: ", TextColor.WHITE, worldName.get())); return 0; } } else { world = source.getWorld().orElse(null); if (world == null) { source.sendMessage(Text.of(TextColor.RED, "Can't detect a location from this command-source, you'll have to define a world!")); return 0; } } new Thread(() -> { source.sendMessage(Text.of(TextColor.GOLD, "Saving world and flushing changes...")); try { if (plugin.flushWorldUpdates(world.getUUID())) { source.sendMessage(Text.of(TextColor.GREEN, "Successfully saved and flushed all changes.")); } else { source.sendMessage(Text.of(TextColor.RED, "This operation is not supported by this implementation (" + plugin.getImplementationType() + ")")); } } catch (IOException ex) { source.sendMessage(Text.of(TextColor.RED, "There was an unexpected exception trying to save the world. Please check the console for more details...")); Logger.global.logError("Unexpected exception trying to save the world!", ex); } }).start(); return 1; } public int debugBlockCommand(CommandContext context) { final CommandSource source = commandSourceInterface.apply(context.getSource()); // parse arguments Optional worldName = getOptionalArgument(context, "world", String.class); Optional x = getOptionalArgument(context, "x", Double.class); Optional y = getOptionalArgument(context, "y", Double.class); Optional z = getOptionalArgument(context, "z", Double.class); final World world; final Vector3d position; if (worldName.isPresent() && x.isPresent() && y.isPresent() && z.isPresent()) { world = parseWorld(worldName.get()).orElse(null); position = new Vector3d(x.get(), y.get(), z.get()); if (world == null) { source.sendMessage(Text.of(TextColor.RED, "There is no ", helper.worldHelperHover(), " with this name: ", TextColor.WHITE, worldName.get())); return 0; } } else { world = source.getWorld().orElse(null); position = source.getPosition().orElse(null); if (world == null || position == null) { source.sendMessage(Text.of(TextColor.RED, "Can't detect a location from this command-source, you'll have to define a world and position!")); return 0; } } new Thread(() -> { // collect and output debug info Vector3i blockPos = position.floor().toInt(); Block block = new Block<>(world, blockPos.getX(), blockPos.getY(), blockPos.getZ()); Block blockBelow = new Block<>(null, 0, 0, 0).copy(block, 0, -1, 0); // populate lazy-loaded values block.getBlockState(); block.getBiomeId(); block.getLightData(); blockBelow.getBlockState(); blockBelow.getBiomeId(); blockBelow.getLightData(); source.sendMessages(Arrays.asList( Text.of(TextColor.GOLD, "Block at you: ", TextColor.WHITE, block), Text.of(TextColor.GOLD, "Block below you: ", TextColor.WHITE, blockBelow) )); }).start(); return 1; } public int debugDumpCommand(CommandContext context) { final CommandSource source = commandSourceInterface.apply(context.getSource()); try { Path file = plugin.getCoreConfig().getDataFolder().toPath().resolve("dump.json"); StateDumper.global().dump(file); source.sendMessage(Text.of(TextColor.GREEN, "Dump created at: " + file)); return 1; } catch (IOException ex) { Logger.global.logError("Failed to create dump!", ex); source.sendMessage(Text.of(TextColor.RED, "Exception trying to create dump! See console for details.")); return 0; } } public int stopCommand(CommandContext context) { CommandSource source = commandSourceInterface.apply(context.getSource()); if (plugin.getRenderManager().isRunning()) { new Thread(() -> { plugin.getPluginState().setRenderThreadsEnabled(false); plugin.getRenderManager().stop(); source.sendMessage(Text.of(TextColor.GREEN, "Render-Threads stopped!")); plugin.save(); }).start(); } else { source.sendMessage(Text.of(TextColor.RED, "Render-Threads are already stopped!")); return 0; } return 1; } public int startCommand(CommandContext context) { CommandSource source = commandSourceInterface.apply(context.getSource()); if (!plugin.getRenderManager().isRunning()) { new Thread(() -> { plugin.getPluginState().setRenderThreadsEnabled(true); plugin.getRenderManager().start(plugin.getCoreConfig().getRenderThreadCount()); source.sendMessage(Text.of(TextColor.GREEN, "Render-Threads started!")); plugin.save(); }).start(); } else { source.sendMessage(Text.of(TextColor.RED, "Render-Threads are already running!")); return 0; } return 1; } public int freezeCommand(CommandContext context) { CommandSource source = commandSourceInterface.apply(context.getSource()); // parse map argument String mapString = context.getArgument("map", String.class); BmMap map = parseMap(mapString).orElse(null); if (map == null) { source.sendMessage(Text.of(TextColor.RED, "There is no ", helper.mapHelperHover(), " with this name: ", TextColor.WHITE, mapString)); return 0; } PluginState.MapState mapState = plugin.getPluginState().getMapState(map); if (mapState.isUpdateEnabled()) { new Thread(() -> { mapState.setUpdateEnabled(false); plugin.stopWatchingMap(map); plugin.getRenderManager().removeRenderTasksIf(task -> { if (task instanceof MapUpdateTask) return ((MapUpdateTask) task).getMap().equals(map); if (task instanceof WorldRegionRenderTask) return ((WorldRegionRenderTask) task).getMap().equals(map); return false; }); source.sendMessage(Text.of(TextColor.GREEN, "Map ", TextColor.WHITE, mapString, TextColor.GREEN, " is now frozen and will no longer be automatically updated!")); source.sendMessage(Text.of(TextColor.GRAY, "Any currently scheduled updates for this map have been cancelled.")); plugin.save(); }).start(); } else { source.sendMessage(Text.of(TextColor.RED, "This map is already frozen!")); return 0; } return 1; } public int unfreezeCommand(CommandContext context) { CommandSource source = commandSourceInterface.apply(context.getSource()); // parse map argument String mapString = context.getArgument("map", String.class); BmMap map = parseMap(mapString).orElse(null); if (map == null) { source.sendMessage(Text.of(TextColor.RED, "There is no ", helper.mapHelperHover(), " with this name: ", TextColor.WHITE, mapString)); return 0; } PluginState.MapState mapState = plugin.getPluginState().getMapState(map); if (!mapState.isUpdateEnabled()) { new Thread(() -> { mapState.setUpdateEnabled(true); plugin.startWatchingMap(map); plugin.getRenderManager().scheduleRenderTask(new MapUpdateTask(map)); source.sendMessage(Text.of(TextColor.GREEN, "Map ", TextColor.WHITE, mapString, TextColor.GREEN, " is no longer frozen and will be automatically updated!")); plugin.save(); }).start(); } else { source.sendMessage(Text.of(TextColor.RED, "This map is not frozen!")); return 0; } return 1; } public int forceUpdateCommand(CommandContext context) { return updateCommand(context, true); } public int updateCommand(CommandContext context) { return updateCommand(context, false); } public int updateCommand(CommandContext context, boolean force) { final CommandSource source = commandSourceInterface.apply(context.getSource()); // parse world/map argument Optional worldOrMap = getOptionalArgument(context, "world|map", String.class); final World worldToRender; final BmMap mapToRender; if (worldOrMap.isPresent()) { worldToRender = parseWorld(worldOrMap.get()).orElse(null); if (worldToRender == null) { mapToRender = parseMap(worldOrMap.get()).orElse(null); if (mapToRender == null) { source.sendMessage(Text.of(TextColor.RED, "There is no ", helper.worldHelperHover(), " or ", helper.mapHelperHover(), " with this name: ", TextColor.WHITE, worldOrMap.get())); return 0; } } else { mapToRender = null; } } else { worldToRender = source.getWorld().orElse(null); mapToRender = null; if (worldToRender == null) { source.sendMessage(Text.of(TextColor.RED, "Can't detect a world from this command-source, you'll have to define a world or a map to update!").setHoverText(Text.of(TextColor.GRAY, "/bluemap " + (force ? "force-update" : "update") + " "))); return 0; } } // parse radius and center arguments final int radius = getOptionalArgument(context, "radius", Integer.class).orElse(-1); final Vector2i center; if (radius >= 0) { Optional x = getOptionalArgument(context, "x", Double.class); Optional z = getOptionalArgument(context, "z", Double.class); if (x.isPresent() && z.isPresent()) { center = new Vector2i(x.get(), z.get()); } else { Vector3d position = source.getPosition().orElse(null); if (position == null) { source.sendMessage(Text.of(TextColor.RED, "Can't detect a position from this command-source, you'll have to define x,z coordinates to update with a radius!").setHoverText(Text.of(TextColor.GRAY, "/bluemap " + (force ? "force-update" : "update") + " " + radius))); return 0; } center = position.toVector2(true).floor().toInt(); } } else { center = null; } // execute render new Thread(() -> { try { List maps = new ArrayList<>(); if (worldToRender != null) { plugin.getServerInterface().persistWorldChanges(worldToRender.getUUID()); for (BmMap map : plugin.getMapTypes()) { if (map.getWorld().getUUID().equals(worldToRender.getUUID())) maps.add(map); } } else { plugin.getServerInterface().persistWorldChanges(mapToRender.getWorld().getUUID()); maps.add(mapToRender); } if (maps.isEmpty()) { source.sendMessage(Text.of(TextColor.RED, "No map has been found for this world that could be updated!")); return; } for (BmMap map : maps) { MapUpdateTask updateTask = new MapUpdateTask(map, center, radius); plugin.getRenderManager().scheduleRenderTask(updateTask); if (force) { MapRenderState state = map.getRenderState(); updateTask.getRegions().forEach(region -> state.setRenderTime(region, -1)); } source.sendMessage(Text.of(TextColor.GREEN, "Created new Update-Task for map '" + map.getId() + "' ", TextColor.GRAY, "(" + updateTask.getRegions().size() + " regions, ~" + updateTask.getRegions().size() * 1024L + " chunks)")); } source.sendMessage(Text.of(TextColor.GREEN, "Use ", TextColor.GRAY, "/bluemap", TextColor.GREEN, " to see the progress.")); } catch (IOException ex) { source.sendMessage(Text.of(TextColor.RED, "There was an unexpected exception trying to save the world. Please check the console for more details...")); Logger.global.logError("Unexpected exception trying to save the world!", ex); } }).start(); return 1; } public int cancelCommand(CommandContext context) { CommandSource source = commandSourceInterface.apply(context.getSource()); Optional ref = getOptionalArgument(context,"task-ref", String.class); if (!ref.isPresent()) { plugin.getRenderManager().removeAllRenderTasks(); source.sendMessage(Text.of(TextColor.GREEN, "All tasks cancelled!")); source.sendMessage(Text.of(TextColor.GRAY, "(Note, that an already started task might not be removed immediately. Some tasks needs to do some tidying-work first)")); return 1; } Optional task = helper.getTaskForRef(ref.get()); if (!task.isPresent()) { source.sendMessage(Text.of(TextColor.RED, "There is no task with this reference '" + ref.get() + "'!")); return 0; } if (plugin.getRenderManager().removeRenderTask(task.get())) { source.sendMessage(Text.of(TextColor.GREEN, "Task cancelled!")); source.sendMessage(Text.of(TextColor.GRAY, "(Note, that an already started task might not be removed immediately. Some tasks needs to do some tidying-work first)")); return 1; } else { source.sendMessage(Text.of(TextColor.RED, "This task is either completed or got cancelled already!")); return 0; } } public int purgeCommand(CommandContext context) { CommandSource source = commandSourceInterface.apply(context.getSource()); // parse map argument String mapId = context.getArgument("map", String.class); new Thread(() -> { try { Path mapFolder = plugin.getRenderConfig().getWebRoot().toPath().resolve("data").resolve(mapId); if (!Files.isDirectory(mapFolder)) { source.sendMessage(Text.of(TextColor.RED, "There is no map-data to purge for the map-id '" + mapId + "'!")); return; } Optional optMap = parseMap(mapId); // delete map MapPurgeTask purgeTask; if (optMap.isPresent()){ purgeTask = new MapPurgeTask(optMap.get()); } else { purgeTask = new MapPurgeTask(mapFolder); } plugin.getRenderManager().scheduleRenderTaskNext(purgeTask); source.sendMessage(Text.of(TextColor.GREEN, "Created new Task to purge map '" + mapId + "'")); // if map is loaded, reset it and start updating it after the purge if (optMap.isPresent()) { RenderTask updateTask = new MapUpdateTask(optMap.get()); plugin.getRenderManager().scheduleRenderTask(updateTask); source.sendMessage(Text.of(TextColor.GREEN, "Created new Update-Task for map '" + mapId + "'")); source.sendMessage(Text.of(TextColor.GRAY, "If you don't want to render this map again, you need to remove it from your configuration first!")); } source.sendMessage(Text.of(TextColor.GREEN, "Use ", TextColor.GRAY, "/bluemap", TextColor.GREEN, " to see the progress.")); } catch (IOException | IllegalArgumentException e) { source.sendMessage(Text.of(TextColor.RED, "There was an error trying to purge '" + mapId + "', see console for details.")); Logger.global.logError("Failed to purge map '" + mapId + "'!", e); } }).start(); return 1; } public int worldsCommand(CommandContext context) { CommandSource source = commandSourceInterface.apply(context.getSource()); source.sendMessage(Text.of(TextColor.BLUE, "Worlds loaded by BlueMap:")); for (World world : plugin.getWorlds()) { source.sendMessage(Text.of(TextColor.GRAY, " - ", TextColor.WHITE, world.getName()).setHoverText(Text.of(world.getSaveFolder(), TextColor.GRAY, " (" + world.getUUID() + ")"))); } return 1; } public int mapsCommand(CommandContext context) { CommandSource source = commandSourceInterface.apply(context.getSource()); source.sendMessage(Text.of(TextColor.BLUE, "Maps loaded by BlueMap:")); for (BmMap map : plugin.getMapTypes()) { boolean unfrozen = plugin.getPluginState().getMapState(map).isUpdateEnabled(); if (unfrozen) { source.sendMessage(Text.of( TextColor.GRAY, " - ", TextColor.WHITE, map.getId(), TextColor.GRAY, " (" + map.getName() + ")" ).setHoverText(Text.of(TextColor.WHITE, "World: ", TextColor.GRAY, map.getWorld().getName()))); } else { source.sendMessage(Text.of( TextColor.GRAY, " - ", TextColor.WHITE, map.getId(), TextColor.GRAY, " (" + map.getName() + ") - ", TextColor.AQUA, TextFormat.ITALIC, "frozen!" ).setHoverText(Text.of(TextColor.WHITE, "World: ", TextColor.GRAY, map.getWorld().getName()))); } } return 1; } public int createMarkerCommand(CommandContext context) { CommandSource source = commandSourceInterface.apply(context.getSource()); String markerId = context.getArgument("id", String.class); String markerLabel = context.getArgument("label", String.class) .replace("<", "<") .replace(">", ">"); //no html via commands // parse map argument String mapString = context.getArgument("map", String.class); BmMap map = parseMap(mapString).orElse(null); if (map == null) { source.sendMessage(Text.of(TextColor.RED, "There is no ", helper.mapHelperHover(), " with this name: ", TextColor.WHITE, mapString)); return 0; } // parse position Optional x = getOptionalArgument(context, "x", Double.class); Optional y = getOptionalArgument(context, "y", Double.class); Optional z = getOptionalArgument(context, "z", Double.class); Vector3d position; if (x.isPresent() && y.isPresent() && z.isPresent()) { position = new Vector3d(x.get(), y.get(), z.get()); } else { position = source.getPosition().orElse(null); if (position == null) { source.sendMessage(Text.of(TextColor.RED, "Can't detect a position from this command-source, you'll have to define the x,y,z coordinates for the marker!").setHoverText(Text.of(TextColor.GRAY, "/bluemap marker create " + markerId + " " + "[world|map]