974 lines
42 KiB
Java
974 lines
42 KiB
Java
/*
|
|
* 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.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<S> {
|
|
|
|
public static final String DEFAULT_MARKER_SET_ID = "markers";
|
|
|
|
private final Plugin plugin;
|
|
private final CommandDispatcher<S> dispatcher;
|
|
private final Function<S, CommandSource> commandSourceInterface;
|
|
|
|
private final CommandHelper helper;
|
|
|
|
public Commands(Plugin plugin, CommandDispatcher<S> dispatcher, Function<S, CommandSource> commandSourceInterface) {
|
|
this.plugin = plugin;
|
|
this.dispatcher = dispatcher;
|
|
this.commandSourceInterface = commandSourceInterface;
|
|
|
|
this.helper = new CommandHelper(plugin);
|
|
|
|
init();
|
|
}
|
|
|
|
public void init() {
|
|
// commands
|
|
LiteralCommandNode<S> baseCommand = literal("bluemap")
|
|
.requires(requirementsUnloaded("bluemap.status"))
|
|
.executes(this::statusCommand)
|
|
.build();
|
|
|
|
LiteralCommandNode<S> versionCommand = literal("version")
|
|
.requires(requirementsUnloaded("bluemap.version"))
|
|
.executes(this::versionCommand)
|
|
.build();
|
|
|
|
LiteralCommandNode<S> helpCommand = literal("help")
|
|
.requires(requirementsUnloaded("bluemap.help"))
|
|
.executes(this::helpCommand)
|
|
.build();
|
|
|
|
LiteralCommandNode<S> reloadCommand = literal("reload")
|
|
.requires(requirementsUnloaded("bluemap.reload"))
|
|
.executes(context -> this.reloadCommand(context, false))
|
|
|
|
.then(literal("light")
|
|
.executes(context -> this.reloadCommand(context, true)))
|
|
|
|
.build();
|
|
|
|
LiteralCommandNode<S> 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<S> stopCommand = literal("stop")
|
|
.requires(requirements("bluemap.stop"))
|
|
.executes(this::stopCommand)
|
|
.build();
|
|
|
|
LiteralCommandNode<S> startCommand = literal("start")
|
|
.requires(requirements("bluemap.start"))
|
|
.executes(this::startCommand)
|
|
.build();
|
|
|
|
LiteralCommandNode<S> freezeCommand = literal("freeze")
|
|
.requires(requirements("bluemap.freeze"))
|
|
.then(argument("map", StringArgumentType.string()).suggests(new MapSuggestionProvider<>(plugin))
|
|
.executes(this::freezeCommand))
|
|
.build();
|
|
|
|
LiteralCommandNode<S> unfreezeCommand = literal("unfreeze")
|
|
.requires(requirements("bluemap.freeze"))
|
|
.then(argument("map", StringArgumentType.string()).suggests(new MapSuggestionProvider<>(plugin))
|
|
.executes(this::unfreezeCommand))
|
|
.build();
|
|
|
|
LiteralCommandNode<S> forceUpdateCommand =
|
|
addRenderArguments(
|
|
literal("force-update")
|
|
.requires(requirements("bluemap.update.force")),
|
|
this::forceUpdateCommand
|
|
).build();
|
|
|
|
LiteralCommandNode<S> updateCommand =
|
|
addRenderArguments(
|
|
literal("update")
|
|
.requires(requirements("bluemap.update")),
|
|
this::updateCommand
|
|
).build();
|
|
|
|
LiteralCommandNode<S> purgeCommand = literal("purge")
|
|
.requires(requirements("bluemap.purge"))
|
|
.then(argument("map", StringArgumentType.string()).suggests(new MapSuggestionProvider<>(plugin))
|
|
.executes(this::purgeCommand))
|
|
.build();
|
|
|
|
LiteralCommandNode<S> 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<S> worldsCommand = literal("worlds")
|
|
.requires(requirements("bluemap.status"))
|
|
.executes(this::worldsCommand)
|
|
.build();
|
|
|
|
LiteralCommandNode<S> mapsCommand = literal("maps")
|
|
.requires(requirements("bluemap.status"))
|
|
.executes(this::mapsCommand)
|
|
.build();
|
|
|
|
LiteralCommandNode<S> 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 extends ArgumentBuilder<S, B>> B addRenderArguments(B builder, Command<S> command) {
|
|
return builder
|
|
.executes(command) // /bluemap render
|
|
|
|
.then(argument("radius", IntegerArgumentType.integer())
|
|
.executes(command)) // /bluemap render <radius>
|
|
|
|
.then(argument("x", DoubleArgumentType.doubleArg())
|
|
.then(argument("z", DoubleArgumentType.doubleArg())
|
|
.then(argument("radius", IntegerArgumentType.integer())
|
|
.executes(command)))) // /bluemap render <x> <z> <radius>
|
|
|
|
.then(argument("world|map", StringArgumentType.string()).suggests(new WorldOrMapSuggestionProvider<>(plugin))
|
|
.executes(command) // /bluemap render <world|map>
|
|
|
|
.then(argument("x", DoubleArgumentType.doubleArg())
|
|
.then(argument("z", DoubleArgumentType.doubleArg())
|
|
.then(argument("radius", IntegerArgumentType.integer())
|
|
.executes(command))))); // /bluemap render <world|map> <x> <z> <radius>
|
|
}
|
|
|
|
private Predicate<S> requirements(String permission){
|
|
return s -> {
|
|
CommandSource source = commandSourceInterface.apply(s);
|
|
return plugin.isLoaded() && source.hasPermission(permission);
|
|
};
|
|
}
|
|
|
|
private Predicate<S> requirementsUnloaded(String permission){
|
|
return s -> {
|
|
CommandSource source = commandSourceInterface.apply(s);
|
|
return source.hasPermission(permission);
|
|
};
|
|
}
|
|
|
|
private LiteralArgumentBuilder<S> literal(String name){
|
|
return LiteralArgumentBuilder.literal(name);
|
|
}
|
|
|
|
private <T> RequiredArgumentBuilder<S, T> argument(String name, ArgumentType<T> type){
|
|
return RequiredArgumentBuilder.argument(name, type);
|
|
}
|
|
|
|
private <T> Optional<T> getOptionalArgument(CommandContext<S> context, String argumentName, Class<T> type) {
|
|
try {
|
|
return Optional.of(context.getArgument(argumentName, type));
|
|
} catch (IllegalArgumentException ex) {
|
|
return Optional.empty();
|
|
}
|
|
}
|
|
|
|
private Optional<World> 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<BmMap> 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<S> 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<S> 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<S> 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<S> 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<S> 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<S> context) {
|
|
CommandSource source = commandSourceInterface.apply(context.getSource());
|
|
|
|
// parse arguments
|
|
Optional<String> 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<S> context) {
|
|
final CommandSource source = commandSourceInterface.apply(context.getSource());
|
|
|
|
// parse arguments
|
|
Optional<String> worldName = getOptionalArgument(context, "world", String.class);
|
|
Optional<Double> x = getOptionalArgument(context, "x", Double.class);
|
|
Optional<Double> y = getOptionalArgument(context, "y", Double.class);
|
|
Optional<Double> 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<String, Object> 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<S> 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<S> 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<S> 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<S> 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<S> 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<S> context) {
|
|
return updateCommand(context, true);
|
|
}
|
|
|
|
public int updateCommand(CommandContext<S> context) {
|
|
return updateCommand(context, false);
|
|
}
|
|
|
|
public int updateCommand(CommandContext<S> context, boolean force) {
|
|
final CommandSource source = commandSourceInterface.apply(context.getSource());
|
|
|
|
// parse world/map argument
|
|
Optional<String> 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") + " <world|map>")));
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// parse radius and center arguments
|
|
final int radius = getOptionalArgument(context, "radius", Integer.class).orElse(-1);
|
|
final Vector2i center;
|
|
if (radius >= 0) {
|
|
Optional<Double> x = getOptionalArgument(context, "x", Double.class);
|
|
Optional<Double> 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") + " <x> <z> " + radius)));
|
|
return 0;
|
|
}
|
|
|
|
center = position.toVector2(true).floor().toInt();
|
|
}
|
|
} else {
|
|
center = null;
|
|
}
|
|
|
|
// execute render
|
|
new Thread(() -> {
|
|
try {
|
|
List<BmMap> 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<S> context) {
|
|
CommandSource source = commandSourceInterface.apply(context.getSource());
|
|
|
|
Optional<String> 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<RenderTask> 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<S> 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<S> 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<S> context) {
|
|
List<Text> 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<S> 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<S> 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<String> 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, " <empty storage>"));
|
|
} 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<S> 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;
|
|
}
|
|
|
|
}
|