/* * 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.common.config.ConfigurationException; import de.bluecolored.bluemap.common.plugin.Plugin; import de.bluecolored.bluemap.common.plugin.PluginState; 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.*; import de.bluecolored.bluemap.common.serverinterface.CommandSource; 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.storage.Storage; import de.bluecolored.bluemap.core.world.Chunk; import de.bluecolored.bluemap.core.world.World; import de.bluecolored.bluemap.core.world.block.Block; import java.io.IOException; import java.nio.file.Path; import java.util.*; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Stream; 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(context -> this.reloadCommand(context, false)) .then(literal("light") .executes(context -> this.reloadCommand(context, true))) .build(); LiteralCommandNode debugCommand = literal("debug") .requires(requirementsUnloaded("bluemap.debug")) .then(literal("block") .requires(requirements("bluemap.debug")) .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") .requires(requirements("bluemap.debug")) .executes(this::debugFlushCommand) .then(argument("world", StringArgumentType.string()).suggests(new WorldSuggestionProvider<>(plugin)) .executes(this::debugFlushCommand))) .then(literal("cache") .requires(requirements("bluemap.debug")) .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 storagesCommand = literal("storages") .requires(requirements("bluemap.status")) .executes(this::storagesCommand) .then(argument("storage", StringArgumentType.string()).suggests(new StorageSuggestionProvider<>(plugin)) .executes(this::storagesInfoCommand) .then(literal("delete") .requires(requirements("bluemap.delete")) .then(argument("map", StringArgumentType.string()) .executes(this::storagesDeleteMapCommand)))) .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(storagesCommand); } 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 worldId) { for (var entry : plugin.getBlueMap().getWorlds().entrySet()) { if (entry.getKey().equals(worldId)) { return Optional.of(entry.getValue()); } } return Optional.empty(); } private Optional parseMap(String mapId) { for (BmMap map : plugin.getBlueMap().getMaps().values()) { if (map.getId().equals(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; } new Thread(() -> { source.sendMessages(helper.createStatusMessage()); }, "BlueMap-Plugin-StatusCommand").start(); return 1; } public int versionCommand(CommandContext context) { CommandSource source = commandSourceInterface.apply(context.getSource()); int renderThreadCount = 0; if (plugin.isLoaded()) { renderThreadCount = plugin.getRenderManager().getWorkerThreadCount(); } MinecraftVersion minecraftVersion = plugin.getServerInterface().getMinecraftVersion(); 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)); source.sendMessage(Text.of(TextColor.GRAY, "Implementation: ", TextColor.WHITE, plugin.getImplementationType())); source.sendMessage(Text.of( TextColor.GRAY, "Minecraft compatibility: ", TextColor.WHITE, minecraftVersion.getVersionString(), TextColor.GRAY, " (" + minecraftVersion.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 (minecraftVersion.isAtLeast(new MinecraftVersion(1, 15))) { String clipboardValue = "Version: " + BlueMap.VERSION + "\n" + "Commit: " + BlueMap.GIT_HASH + "\n" + "Implementation: " + plugin.getImplementationType() + "\n" + "Minecraft compatibility: " + minecraftVersion.getVersionString() + " (" + minecraftVersion.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, boolean light) { CommandSource source = commandSourceInterface.apply(context.getSource()); source.sendMessage(Text.of(TextColor.GOLD, "Reloading BlueMap...")); new Thread(() -> { try { if (light) { plugin.lightReload(); } else { 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 (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!")); } }, "BlueMap-Plugin-ReloadCommand").start(); return 1; } public int debugClearCacheCommand(CommandContext context) { CommandSource source = commandSourceInterface.apply(context.getSource()); for (World world : plugin.getBlueMap().getWorlds().values()) { 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 id: ", 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)) { 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); } }, "BlueMap-Plugin-DebugFlushCommand").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 id: ", 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<>(world, blockPos.getX(), blockPos.getY() - 1, blockPos.getZ()); source.sendMessages(Arrays.asList( Text.of(TextColor.GOLD, "Block at you: \n", formatBlock(block)), Text.of(TextColor.GOLD, "Block below you: \n", formatBlock(blockBelow)) )); }, "BlueMap-Plugin-DebugBlockCommand").start(); return 1; } private Text formatBlock(Block block) { World world = block.getWorld(); Chunk chunk = block.getChunk(); Map lines = new LinkedHashMap<>(); lines.put("world-id", world.getId()); lines.put("world-name", world.getName()); lines.put("chunk-is-generated", chunk.isGenerated()); lines.put("chunk-has-lightdata", chunk.hasLightData()); lines.put("chunk-inhabited-time", chunk.getInhabitedTime()); lines.put("block-state", block.getBlockState()); lines.put("biome", block.getBiomeId()); lines.put("position", block.getX() + " | " + block.getY() + " | " + block.getZ()); lines.put("block-light", block.getBlockLightLevel()); lines.put("sun-light", block.getSunLightLevel()); Object[] textElements = lines.entrySet().stream() .flatMap(e -> Stream.of(TextColor.GRAY, e.getKey(), ": ", TextColor.WHITE, e.getValue(), "\n")) .toArray(Object[]::new); textElements[textElements.length - 1] = ""; return Text.of(textElements); } public int debugDumpCommand(CommandContext context) { final CommandSource source = commandSourceInterface.apply(context.getSource()); try { Path file = plugin.getBlueMap().getConfig().getCoreConfig().getData().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(); }, "BlueMap-Plugin-StopCommand").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.getBlueMap().getConfig().getCoreConfig().resolveRenderThreadCount()); source.sendMessage(Text.of(TextColor.GREEN, "Render-Threads started!")); plugin.save(); }, "BlueMap-Plugin-StartCommand").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 id: ", 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(); }, "BlueMap-Plugin-FreezeCommand").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 id: ", 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(); }, "BlueMap-Plugin-UnfreezeCommand").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 id: ", 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.flushWorldUpdates(worldToRender); for (BmMap map : plugin.getBlueMap().getMaps().values()) { if (map.getWorld().equals(worldToRender)) maps.add(map); } } else { plugin.flushWorldUpdates(mapToRender.getWorld()); 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); } }, "BlueMap-Plugin-UpdateCommand").start(); return 1; } public int cancelCommand(CommandContext context) { CommandSource source = commandSourceInterface.apply(context.getSource()); Optional ref = getOptionalArgument(context,"task-ref", String.class); if (ref.isEmpty()) { 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.isEmpty()) { 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 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 id: ", TextColor.WHITE, mapString)); return 0; } new Thread(() -> { try { // delete map MapPurgeTask purgeTask = new MapPurgeTask(map); plugin.getRenderManager().scheduleRenderTaskNext(purgeTask); source.sendMessage(Text.of(TextColor.GREEN, "Created new Task to purge map '" + map.getId() + "'")); // cancel task if currently rendering the same map RenderTask currentRenderTask = plugin.getRenderManager().getCurrentRenderTask(); if (currentRenderTask instanceof MapUpdateTask && ((MapUpdateTask) currentRenderTask).getMap().getId().equals(map.getId())) { currentRenderTask.cancel(); } // start updating the map after the purge if (plugin.getPluginState().getMapState(map).isUpdateEnabled()) { RenderTask updateTask = new MapUpdateTask(map); plugin.getRenderManager().scheduleRenderTask(updateTask); source.sendMessage(Text.of(TextColor.GREEN, "Created new Update-Task for map '" + map.getId() + "'")); source.sendMessage(Text.of(TextColor.GRAY, "If you don't want this map to render again after the purge, use ", TextColor.DARK_GRAY, "/bluemap freeze " + map.getId(), TextColor.GRAY, " first!")); } source.sendMessage(Text.of(TextColor.GREEN, "Use ", TextColor.GRAY, "/bluemap", TextColor.GREEN, " to see the progress.")); } catch (IllegalArgumentException e) { source.sendMessage(Text.of(TextColor.RED, "There was an error trying to purge '" + map.getId() + "', see console for details.")); Logger.global.logError("Failed to purge map '" + map.getId() + "'!", e); } }, "BlueMap-Plugin-PurgeCommand").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 (var entry : plugin.getBlueMap().getWorlds().entrySet()) { source.sendMessage(Text.of(TextColor.GRAY, " - ", TextColor.WHITE, entry.getKey())); } return 1; } public int mapsCommand(CommandContext context) { List lines = new ArrayList<>(); lines.add(Text.of(TextColor.BLUE, "Maps loaded by BlueMap:")); for (BmMap map : plugin.getBlueMap().getMaps().values()) { boolean frozen = !plugin.getPluginState().getMapState(map).isUpdateEnabled(); lines.add(Text.of(TextColor.GRAY, " - ", TextColor.WHITE, map.getId(), TextColor.GRAY, " (" + map.getName() + ")")); lines.add(Text.of(TextColor.GRAY, "\u00A0\u00A0\u00A0World: ", TextColor.DARK_GRAY, map.getWorld().getId())); lines.add(Text.of(TextColor.GRAY, "\u00A0\u00A0\u00A0Last Update: ", TextColor.DARK_GRAY, helper.formatTime(map.getRenderState().getLatestRenderTime()))); if (frozen) lines.add(Text.of(TextColor.AQUA, TextFormat.ITALIC, "\u00A0\u00A0\u00A0This map is frozen!")); } CommandSource source = commandSourceInterface.apply(context.getSource()); source.sendMessages(lines); return 1; } public int storagesCommand(CommandContext context) { CommandSource source = commandSourceInterface.apply(context.getSource()); source.sendMessage(Text.of(TextColor.BLUE, "Storages loaded by BlueMap:")); for (var entry : plugin.getBlueMap().getConfig().getStorageConfigs().entrySet()) { source.sendMessage(Text.of(TextColor.GRAY, " - ", TextColor.WHITE, entry.getKey()) .setHoverText(Text.of(entry.getValue().getStorageType().name())) .setClickAction(Text.ClickAction.RUN_COMMAND, "/bluemap storages " + entry.getKey()) ); } return 1; } public int storagesInfoCommand(CommandContext context) { CommandSource source = commandSourceInterface.apply(context.getSource()); String storageId = context.getArgument("storage", String.class); Storage storage; try { storage = plugin.getBlueMap().getOrLoadStorage(storageId); } catch (ConfigurationException | InterruptedException ex) { Logger.global.logError("Unexpected exception trying to load storage '" + storageId + "'!", ex); source.sendMessage(Text.of(TextColor.RED, "There was an unexpected exception trying to load this storage. Please check the console for more details...")); return 0; } Collection mapIds; try { mapIds = storage.collectMapIds(); } catch (IOException ex) { Logger.global.logError("Unexpected exception trying to load mapIds from storage '" + storageId + "'!", ex); source.sendMessage(Text.of(TextColor.RED, "There was an unexpected exception trying to access this storage. Please check the console for more details...")); return 0; } source.sendMessage(Text.of(TextColor.BLUE, "Storage '", storageId, "':")); if (mapIds.isEmpty()) { source.sendMessage(Text.of(TextColor.GRAY, " ")); } else { for (String mapId : mapIds) { BmMap map = plugin.getBlueMap().getMaps().get(mapId); boolean isLoaded = map != null && map.getStorage().equals(storage); if (isLoaded) { source.sendMessage(Text.of(TextColor.GRAY, " - ", TextColor.WHITE, mapId, TextColor.GREEN, TextFormat.ITALIC, " (loaded)")); } else { source.sendMessage(Text.of(TextColor.GRAY, " - ", TextColor.WHITE, mapId, TextColor.DARK_GRAY, TextFormat.ITALIC, " (unloaded/static/remote)")); } } } return 1; } public int storagesDeleteMapCommand(CommandContext context) { CommandSource source = commandSourceInterface.apply(context.getSource()); String storageId = context.getArgument("storage", String.class); String mapId = context.getArgument("map", String.class); Storage storage; try { storage = plugin.getBlueMap().getOrLoadStorage(storageId); } catch (ConfigurationException | InterruptedException ex) { Logger.global.logError("Unexpected exception trying to load storage '" + storageId + "'!", ex); source.sendMessage(Text.of(TextColor.RED, "There was an unexpected exception trying to load this storage. Please check the console for more details...")); return 0; } BmMap map = plugin.getBlueMap().getMaps().get(mapId); boolean isLoaded = map != null && map.getStorage().equals(storage); if (isLoaded) { Text purgeCommand = Text.of(TextColor.WHITE, "/bluemap purge " + mapId) .setClickAction(Text.ClickAction.SUGGEST_COMMAND, "/bluemap purge " + mapId); source.sendMessage(Text.of(TextColor.RED, "Can't delete a loaded map!\n" + "Unload the map by removing its config-file first,\n" + "or use ", purgeCommand, " if you want to purge it.")); return 0; } // delete map StorageDeleteTask deleteTask = new StorageDeleteTask(storage, mapId); plugin.getRenderManager().scheduleRenderTaskNext(deleteTask); source.sendMessage(Text.of(TextColor.GREEN, "Created new Task to delete map '" + mapId + "' from storage '" + storageId + "'")); return 1; } }