diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitPlayer.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitPlayer.java index 12d7551de..c0dc5c7e2 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitPlayer.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitPlayer.java @@ -38,7 +38,6 @@ import com.sk89q.worldedit.world.block.BlockStateHolder; import com.sk89q.worldedit.world.block.BlockTypes; import com.sk89q.worldedit.world.gamemode.GameMode; import com.sk89q.worldedit.world.gamemode.GameModes; -import com.sk89q.worldedit.util.formatting.text.TextComponent; import com.sk89q.worldedit.util.formatting.text.adapter.bukkit.TextAdapter; import org.bukkit.Bukkit; import org.bukkit.Location; diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitServerInterface.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitServerInterface.java index 8461826f9..34ad4c257 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitServerInterface.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitServerInterface.java @@ -72,7 +72,7 @@ public class BukkitServerInterface implements MultiUserPlatform { if (plugin.getBukkitImplAdapter() != null) { return plugin.getBukkitImplAdapter().getDataVersion(); } - return 0; + return -1; } @Override diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/WorldEdit.java b/worldedit-core/src/main/java/com/sk89q/worldedit/WorldEdit.java index e206f383f..205cfde5f 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/WorldEdit.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/WorldEdit.java @@ -303,6 +303,17 @@ public final class WorldEdit { if (exts.size() != 1) { exts = exts.subList(0, 1); } + } else { + int dot = filename.lastIndexOf('.'); + if (dot < 0 || dot == filename.length() - 1) { + String currentExt = filename.substring(dot + 1); + if (exts.contains(currentExt) && checkFilename(filename)) { + File f = new File(dir, filename); + if (f.exists()) { + return f; + } + } + } } File result = null; for (Iterator iter = exts.iterator(); iter.hasNext() && (result == null || (!isSave && !result.exists()));) { @@ -317,7 +328,7 @@ public final class WorldEdit { private File getSafeFileWithExtension(File dir, String filename, String extension) { if (extension != null) { int dot = filename.lastIndexOf('.'); - if (dot < 0 || !filename.substring(dot).equalsIgnoreCase(extension)) { + if (dot < 0 || dot == filename.length() - 1 || !filename.substring(dot + 1).equalsIgnoreCase(extension)) { filename += "." + extension; } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/BiomeCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/BiomeCommands.java index b751dfefe..bb4788f9e 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/BiomeCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/BiomeCommands.java @@ -45,7 +45,6 @@ import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.regions.Regions; import com.sk89q.worldedit.util.Location; import com.sk89q.worldedit.util.formatting.component.PaginationBox; -import com.sk89q.worldedit.util.formatting.text.TextComponent; import com.sk89q.worldedit.world.World; import com.sk89q.worldedit.world.biome.BiomeData; import com.sk89q.worldedit.world.biome.BiomeType; @@ -56,7 +55,7 @@ import org.enginehub.piston.annotation.param.Arg; import org.enginehub.piston.annotation.param.Switch; import java.util.HashSet; -import java.util.List; +import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; @@ -83,10 +82,11 @@ public class BiomeCommands { int page) throws WorldEditException { BiomeRegistry biomeRegistry = WorldEdit.getInstance().getPlatformManager() .queryCapability(Capability.GAME_HOOKS).getRegistries().getBiomeRegistry(); - List biomes = BiomeType.REGISTRY.values().stream().map(biomeRegistry::getData).collect(Collectors.toList()); - PaginationBox paginationBox = new PaginationBox("Available Biomes", "/biomelist %page%"); - paginationBox.setComponents(biomes.stream().map(biome -> TextComponent.of(biome.getName())).collect(Collectors.toList())); + PaginationBox paginationBox = PaginationBox.fromStrings("Available Biomes", "/biomelist %page%", + BiomeType.REGISTRY.values().stream() + .map(biomeRegistry::getData).filter(Objects::nonNull) + .map(BiomeData::getName).collect(Collectors.toList())); player.print(paginationBox.create(page)); } @@ -152,7 +152,7 @@ public class BiomeCommands { @Command( name = "/setbiome", - desc = "Sets the biome of the player's current block or region.", + desc = "Sets the biome of your current block or region.", descFooter = "By default, uses all the blocks in your selection" ) @Logging(REGION) diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/ChunkCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/ChunkCommands.java index 5dcce1699..8dc99dc15 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/ChunkCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/ChunkCommands.java @@ -34,7 +34,6 @@ import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.math.MathUtils; import com.sk89q.worldedit.util.Location; import com.sk89q.worldedit.util.formatting.component.PaginationBox; -import com.sk89q.worldedit.util.formatting.text.TextComponent; import com.sk89q.worldedit.world.storage.LegacyChunkStore; import com.sk89q.worldedit.world.storage.McRegionChunkStore; import org.enginehub.piston.annotation.Command; @@ -62,10 +61,10 @@ public class ChunkCommands { @Command( name = "chunkinfo", - desc = "Get information about the chunk that you are inside" + desc = "Get information about the chunk you're inside" ) @CommandPermissions("worldedit.chunkinfo") - public void chunkInfo(Player player) throws WorldEditException { + public void chunkInfo(Player player) { Location pos = player.getBlockIn(); int chunkX = (int) Math.floor(pos.getBlockX() / 16.0); int chunkZ = (int) Math.floor(pos.getBlockZ() / 16.0); @@ -90,8 +89,8 @@ public class ChunkCommands { @Arg(desc = "Page number.", def = "1") int page) throws WorldEditException { Set chunks = session.getSelection(player.getWorld()).getChunks(); - PaginationBox paginationBox = new PaginationBox("Selected Chunks", "/listchunks %page%"); - paginationBox.setComponents(chunks.stream().map(chunk -> TextComponent.of(chunk.toString())).collect(Collectors.toList())); + PaginationBox paginationBox = PaginationBox.fromStrings("Selected Chunks", "/listchunks %page%", + chunks.stream().map(BlockVector2::toString).collect(Collectors.toList())); player.print(paginationBox.create(page)); } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/SchematicCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/SchematicCommands.java index c06fe851f..fe4f5f1de 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/SchematicCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/SchematicCommands.java @@ -19,8 +19,6 @@ package com.sk89q.worldedit.command; -import com.google.common.collect.Multimap; -import com.google.common.io.Files; import com.sk89q.worldedit.LocalConfiguration; import com.sk89q.worldedit.LocalSession; import com.sk89q.worldedit.WorldEdit; @@ -39,6 +37,8 @@ import com.sk89q.worldedit.extent.clipboard.io.ClipboardWriter; import com.sk89q.worldedit.function.operation.Operations; import com.sk89q.worldedit.math.transform.Transform; import com.sk89q.worldedit.session.ClipboardHolder; +import com.sk89q.worldedit.util.formatting.component.PaginationBox; +import com.sk89q.worldedit.util.formatting.component.SchematicPaginationBox; import com.sk89q.worldedit.util.formatting.text.TextComponent; import com.sk89q.worldedit.util.io.Closer; import com.sk89q.worldedit.util.io.file.FilenameException; @@ -60,7 +60,6 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.regex.Pattern; import static com.google.common.base.Preconditions.checkNotNull; @@ -70,10 +69,6 @@ import static com.google.common.base.Preconditions.checkNotNull; @CommandContainer(superTypes = CommandPermissionsConditionGenerator.Registration.class) public class SchematicCommands { - /** - * 9 schematics per page fits in the MC chat window. - */ - private static final int SCHEMATICS_PER_PAGE = 9; private static final Logger log = LoggerFactory.getLogger(SchematicCommands.class); private final WorldEdit worldEdit; @@ -100,7 +95,9 @@ public class SchematicCommands { LocalConfiguration config = worldEdit.getConfiguration(); File dir = worldEdit.getWorkingDirectoryFile(config.saveDir); - File f = worldEdit.getSafeOpenFile(player, dir, filename, BuiltInClipboardFormat.SPONGE_SCHEMATIC.getPrimaryFileExtension(), ClipboardFormats.getFileExtensionArray()); + File f = worldEdit.getSafeOpenFile(player, dir, filename, + BuiltInClipboardFormat.SPONGE_SCHEMATIC.getPrimaryFileExtension(), + ClipboardFormats.getFileExtensionArray()); if (!f.exists()) { player.printError("Schematic " + filename + " does not exist!"); @@ -128,7 +125,7 @@ public class SchematicCommands { player.print(filename + " loaded. Paste it with //paste"); } catch (IOException e) { player.printError("Schematic could not read or it does not exist: " + e.getMessage()); - log.warn("Failed to load a saved clipboard", e); + log.warn("Failed to load schematic: " + e.getMessage()); } } @@ -277,7 +274,7 @@ public class SchematicCommands { @Switch(name = 'd', desc = "Sort by date, oldest first") boolean oldFirst, @Switch(name = 'n', desc = "Sort by date, newest first") - boolean newFirst) { + boolean newFirst) throws WorldEditException { if (oldFirst && newFirst) { throw new StopExecutionException(TextComponent.of("Cannot sort by oldest and newest.")); } @@ -292,16 +289,6 @@ public class SchematicCommands { File[] files = new File[fileList.size()]; fileList.toArray(files); - int pageCount = files.length / SCHEMATICS_PER_PAGE + 1; - if (page < 1) { - actor.printError("Page must be at least 1"); - return; - } - if (page > pageCount) { - actor.printError("Page must be less than " + (pageCount + 1)); - return; - } - final int sortType = oldFirst ? -1 : newFirst ? 1 : 0; // cleanup file list Arrays.sort(files, (f1, f2) -> { @@ -321,20 +308,9 @@ public class SchematicCommands { return res; }); - List schematics = listFiles(worldEdit.getConfiguration().saveDir, files); - int offset = (page - 1) * SCHEMATICS_PER_PAGE; - - actor.print("Available schematics (Filename: Format) [" + page + "/" + pageCount + "]:"); - StringBuilder build = new StringBuilder(); - int limit = Math.min(offset + SCHEMATICS_PER_PAGE, schematics.size()); - for (int i = offset; i < limit; ) { - build.append(schematics.get(i)); - if (++i != limit) { - build.append("\n"); - } - } - - actor.print(build.toString()); + String pageCommand = actor.isPlayer() ? "/schem list -p %page%" + (oldFirst ? " -d" : newFirst ? " -n" : "") : null; + PaginationBox paginationBox = new SchematicPaginationBox(worldEdit.getConfiguration().saveDir, files, pageCommand); + actor.print(paginationBox.create(page)); } private List allFiles(File root) { @@ -353,22 +329,4 @@ public class SchematicCommands { return fileList; } - private List listFiles(String prefix, File[] files) { - if (prefix == null) prefix = ""; - List result = new ArrayList<>(); - for (File file : files) { - StringBuilder build = new StringBuilder(); - - build.append("\u00a72"); - //ClipboardFormat format = ClipboardFormats.findByFile(file); - Multimap exts = ClipboardFormats.getFileExtensionMap(); - ClipboardFormat format = exts.get(Files.getFileExtension(file.getName())) - .stream().findFirst().orElse(null); - boolean inRoot = file.getParentFile().getName().equals(prefix); - build.append(inRoot ? file.getName() : file.getPath().split(Pattern.quote(prefix + File.separator))[1]) - .append(": ").append(format == null ? "Unknown" : format.getName() + "*"); - result.add(build.toString()); - } - return result; - } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/SelectionCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/SelectionCommands.java index c8674b085..d2e2a3708 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/SelectionCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/SelectionCommands.java @@ -624,7 +624,7 @@ public class SelectionCommands { } case UNKNOWN: default: - CommandListBox box = new CommandListBox("Selection modes"); + CommandListBox box = new CommandListBox("Selection modes", null); TextComponentProducer contents = box.getContents(); contents.append(SubtleFormat.wrap("Select one of the modes below:")).newline(); @@ -636,7 +636,7 @@ public class SelectionCommands { box.appendCommand("cyl", "Select a cylinder", "//sel cyl"); box.appendCommand("convex", "Select a convex polyhedral", "//sel convex"); - player.print(box.create()); + player.print(box.create(1)); return; } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/UtilityCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/UtilityCommands.java index ea292a855..37facdddb 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/UtilityCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/UtilityCommands.java @@ -51,6 +51,10 @@ import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.regions.CuboidRegion; import com.sk89q.worldedit.regions.CylinderRegion; import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.util.formatting.component.InvalidComponentException; +import com.sk89q.worldedit.util.formatting.component.SubtleFormat; +import com.sk89q.worldedit.util.formatting.text.TextComponent; +import com.sk89q.worldedit.util.formatting.text.format.TextColor; import com.sk89q.worldedit.world.World; import com.sk89q.worldedit.world.block.BlockTypes; import org.enginehub.piston.annotation.Command; @@ -58,8 +62,11 @@ import org.enginehub.piston.annotation.CommandContainer; import org.enginehub.piston.annotation.param.Arg; import org.enginehub.piston.annotation.param.Switch; +import java.text.DecimalFormat; +import java.text.NumberFormat; import java.util.ArrayList; import java.util.List; +import java.util.Locale; import java.util.function.Supplier; import static com.sk89q.worldedit.command.util.Logging.LogMode.PLACEMENT; @@ -474,6 +481,12 @@ public class UtilityCommands { return killed; } + // get the formatter with the system locale. in the future, if we can get a local from a player, we can use that + private static final DecimalFormat formatter = (DecimalFormat) NumberFormat.getInstance(Locale.getDefault()); + static { + formatter.applyPattern("#,##0.#####"); // pattern is locale-insensitive. this can translate to "1.234,56789" + } + @Command( name = "/calculate", aliases = { "/calc", "/eval", "/evaluate", "/solve" }, @@ -485,8 +498,14 @@ public class UtilityCommands { String input) { try { Expression expression = Expression.compile(input); - actor.print("= " + expression.evaluate( - new double[] {}, WorldEdit.getInstance().getSessionManager().get(actor).getTimeout())); + double result = expression.evaluate( + new double[]{}, WorldEdit.getInstance().getSessionManager().get(actor).getTimeout()); + String formatted = formatter.format(result); + actor.print(SubtleFormat.wrap(input + " = ") + .append(TextComponent.of(formatted, TextColor.LIGHT_PURPLE))); + //actor.print(SubtleFormat.wrap(input).append(Component.newline()) + // .append(SubtleFormat.wrap("= ")) + // .append(TextComponent.of(formatted, TextColor.LIGHT_PURPLE))); } catch (EvaluationException e) { actor.printError(String.format( "'%s' could not be evaluated (error: %s)", input, e.getMessage())); diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/PrintCommandHelp.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/PrintCommandHelp.java index b59578a49..df57539ba 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/PrintCommandHelp.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/PrintCommandHelp.java @@ -22,13 +22,10 @@ package com.sk89q.worldedit.command.util; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.sk89q.worldedit.WorldEdit; -import com.sk89q.worldedit.entity.Player; import com.sk89q.worldedit.extension.platform.Actor; -import com.sk89q.worldedit.util.formatting.component.CodeFormat; import com.sk89q.worldedit.util.formatting.component.CommandListBox; import com.sk89q.worldedit.util.formatting.component.CommandUsageBox; -import com.sk89q.worldedit.util.formatting.text.TextComponent; -import com.sk89q.worldedit.util.formatting.text.format.TextColor; +import com.sk89q.worldedit.util.formatting.component.InvalidComponentException; import org.enginehub.piston.Command; import org.enginehub.piston.CommandManager; @@ -46,7 +43,7 @@ import static java.util.stream.Collectors.toList; /** * Implementation of the //help command. */ -// Stored in a separate class to prevent import conflicts. +// Stored in a separate class to prevent import conflicts, and because it's aliased via /we help. public class PrintCommandHelp { private static Command detectCommand(CommandManager manager, String command) { @@ -69,17 +66,11 @@ public class PrintCommandHelp { return mapping.orElse(null); } - public static void help(List commandPath, int page, WorldEdit we, Actor actor) { - if (page < 1) { - actor.printError("Page must be >= 1."); - return; - } + public static void help(List commandPath, int page, WorldEdit we, Actor actor) throws InvalidComponentException { CommandManager manager = we.getPlatformManager().getPlatformCommandManager().getCommandManager(); - final int perPage = actor instanceof Player ? 8 : 20; // More pages for console - if (commandPath.isEmpty()) { - printAllCommands(page, perPage, manager.getAllCommands(), actor, ImmutableList.of()); + printCommands(page, manager.getAllCommands(), actor, ImmutableList.of()); return; } @@ -98,7 +89,11 @@ public class PrintCommandHelp { if (subCommands.isEmpty()) { actor.printError(String.format("'%s' has no sub-commands. (Maybe '%s' is for a parameter?)", - Joiner.on(" ").join(visited), subCommand)); + Joiner.on(" ").join(visited.stream().map(Command::getName).iterator()), subCommand)); + // full help for single command + CommandUsageBox box = new CommandUsageBox(visited, visited.stream() + .map(Command::getName).collect(Collectors.joining(" "))); + actor.print(box.create()); return; } @@ -107,7 +102,11 @@ public class PrintCommandHelp { visited.add(currentCommand); } else { actor.printError(String.format("The sub-command '%s' under '%s' could not be found.", - subCommand, Joiner.on(" ").join(visited))); + subCommand, Joiner.on(" ").join(visited.stream().map(Command::getName).iterator()))); + // list subcommands for currentCommand + CommandUsageBox box = new CommandUsageBox(visited, visited.stream() + .map(Command::getName).collect(Collectors.joining(" "))); + actor.print(box.create()); return; } } @@ -120,46 +119,35 @@ public class PrintCommandHelp { .map(Command::getName).collect(Collectors.joining(" "))); actor.print(box.create()); } else { - printAllCommands(page, perPage, subCommands.values().stream(), actor, visited); + printCommands(page, subCommands.values().stream(), actor, visited); } } - private static void printAllCommands(int page, int perPage, Stream commandStream, Actor actor, - List commandList) { + private static void printCommands(int page, Stream commandStream, Actor actor, + List commandList) throws InvalidComponentException { // Get a list of aliases List commands = commandStream .sorted(byCleanName()) .collect(toList()); - // Calculate pagination - int offset = perPage * (page - 1); - int pageTotal = (int) Math.ceil(commands.size() / (double) perPage); - - // Box - CommandListBox box = new CommandListBox(String.format("Help: page %d/%d ", page, pageTotal)); - TextComponent.Builder tip = box.getContents().getBuilder().color(TextColor.GRAY); - - if (offset >= commands.size()) { - tip.color(TextColor.RED) - .append(TextComponent.of(String.format("There is no page %d (total number of pages is %d).\n", page, pageTotal))); - } else { - List list = commands.subList(offset, Math.min(offset + perPage, commands.size())); - - tip.append(TextComponent.of("Type ")); - tip.append(CodeFormat.wrap("//help [] ")); - tip.append(TextComponent.of(" for more information.\n")); - - // Add each command - for (Command mapping : list) { - String alias = (commandList.isEmpty() ? "/" : "") + mapping.getName(); - String command = Stream.concat(commandList.stream(), Stream.of(mapping)) - .map(Command::getName) - .collect(Collectors.joining(" ", "/", "")); - box.appendCommand(alias, mapping.getDescription(), command); - } + String used = commandList.isEmpty() ? null + : Joiner.on(" ").join(commandList.stream().map(Command::getName).iterator()); + CommandListBox box = new CommandListBox( + (used == null ? "Help" : "Subcommands: " + used), + "//help %page%" + (used == null ? "" : " " + used)); + if (!actor.isPlayer()) { + box.formatForConsole(); } - actor.print(box.create()); + for (Command mapping : commands) { + String alias = (commandList.isEmpty() ? "/" : "") + mapping.getName(); + String command = Stream.concat(commandList.stream(), Stream.of(mapping)) + .map(Command::getName) + .collect(Collectors.joining(" ", "/", "")); + box.appendCommand(alias, mapping.getDescription(), command); + } + + actor.print(box.create(page)); } private PrintCommandHelp() { diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/Actor.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/Actor.java index 319c9cb02..cdcba32a2 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/Actor.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/Actor.java @@ -24,7 +24,6 @@ import com.sk89q.worldedit.session.SessionOwner; import com.sk89q.worldedit.util.Identifiable; import com.sk89q.worldedit.util.auth.Subject; import com.sk89q.worldedit.util.formatting.text.Component; -import com.sk89q.worldedit.util.formatting.text.TextComponent; import java.io.File; diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlatformCommandManager.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlatformCommandManager.java index 93ba549dd..34b5e0e82 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlatformCommandManager.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlatformCommandManager.java @@ -94,6 +94,7 @@ import com.sk89q.worldedit.util.command.parametric.ExceptionConverter; import com.sk89q.worldedit.util.eventbus.Subscribe; import com.sk89q.worldedit.util.formatting.text.TextComponent; import com.sk89q.worldedit.util.formatting.text.TranslatableComponent; +import com.sk89q.worldedit.util.formatting.text.event.HoverEvent; import com.sk89q.worldedit.util.formatting.text.format.TextColor; import com.sk89q.worldedit.util.logging.DynamicStreamHandler; import com.sk89q.worldedit.util.logging.LogFormat; @@ -142,7 +143,6 @@ public final class PlatformCommandManager { private static final Logger log = LoggerFactory.getLogger(PlatformCommandManager.class); private static final java.util.logging.Logger COMMAND_LOG = java.util.logging.Logger.getLogger("com.sk89q.worldedit.CommandLog"); - private static final Pattern numberFormatExceptionPattern = Pattern.compile("^For input string: \"(.*)\"$"); private final WorldEdit worldEdit; private final PlatformManager platformManager; @@ -507,7 +507,7 @@ public final class PlatformCommandManager { .append(e.getRichMessage()) .build()); ImmutableList cmd = e.getCommands(); - if (cmd.size() > 0) { + if (!cmd.isEmpty()) { actor.print(TextComponent.builder("Usage: ") .color(TextColor.RED) .append(TextComponent.of("/", ColorConfig.getMainText())) @@ -515,15 +515,14 @@ public final class PlatformCommandManager { .build()); } } catch (CommandExecutionException e) { - Throwable t = e.getCause(); - actor.printError("Please report this error: [See console]"); - actor.printRaw(t.getClass().getName() + ": " + t.getMessage()); - log.error("An unexpected error while handling a WorldEdit command", t); + handleUnknownException(actor, e.getCause()); } catch (CommandException e) { actor.print(TextComponent.builder("") - .color(TextColor.RED) - .append(e.getRichMessage()) - .build()); + .color(TextColor.RED) + .append(e.getRichMessage()) + .build()); + } catch (Throwable t) { + handleUnknownException(actor, t); } finally { Optional editSessionOpt = context.snapshotMemory().injectedValue(Key.of(EditSession.class)); @@ -554,6 +553,12 @@ public final class PlatformCommandManager { event.setCancelled(true); } + private void handleUnknownException(Actor actor, Throwable t) { + actor.printError("Please report this error: [See console]"); + actor.printRaw(t.getClass().getName() + ": " + t.getMessage()); + log.error("An unexpected error while handling a WorldEdit command", t); + } + @Subscribe public void handleCommandSuggestion(CommandSuggestionEvent event) { try { diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/SpongeSchematicReader.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/SpongeSchematicReader.java index 556a60570..4d33e82b7 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/SpongeSchematicReader.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/SpongeSchematicReader.java @@ -102,10 +102,11 @@ public class SpongeSchematicReader extends NBTSchematicReader { return readVersion1(schematicTag); } else if (version == 2) { int dataVersion = requireTag(schematic, "DataVersion", IntTag.class).getValue(); - if (dataVersion > WorldEdit.getInstance().getPlatformManager() - .queryCapability(Capability.WORLD_EDITING).getDataVersion()) { - // maybe should just warn? simple schematics may be compatible still. - throw new IOException("Schematic was made in a newer Minecraft version. Data may be incompatible."); + int liveDataVersion = WorldEdit.getInstance().getPlatformManager() + .queryCapability(Capability.WORLD_EDITING).getDataVersion(); + if (dataVersion > liveDataVersion) { + log.warn("Schematic was made in a newer Minecraft version ({} > {}). Data may be incompatible.", + dataVersion, liveDataVersion); } BlockArrayClipboard clip = readVersion1(schematicTag); return readVersion2(clip, schematicTag); diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/CommandListBox.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/CommandListBox.java index f39a74656..4c08f6863 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/CommandListBox.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/CommandListBox.java @@ -19,47 +19,75 @@ package com.sk89q.worldedit.util.formatting.component; +import com.google.common.collect.Lists; import com.sk89q.worldedit.util.formatting.text.Component; import com.sk89q.worldedit.util.formatting.text.TextComponent; import com.sk89q.worldedit.util.formatting.text.event.ClickEvent; import com.sk89q.worldedit.util.formatting.text.event.HoverEvent; import com.sk89q.worldedit.util.formatting.text.format.TextColor; -public class CommandListBox extends MessageBox { +import java.util.List; - private boolean first = true; +public class CommandListBox extends PaginationBox { + + private List commands = Lists.newArrayList(); /** * Create a new box. * * @param title the title */ - public CommandListBox(String title) { - super(title, new TextComponentProducer()); + public CommandListBox(String title, String pageCommand) { + super(title, pageCommand); } - public CommandListBox appendCommand(String alias, Component description) { - return appendCommand(alias, description, null); + @Override + public Component getComponent(int number) { + return commands.get(number).createComponent(); } - public CommandListBox appendCommand(String alias, String description, String insertion) { - return appendCommand(alias, TextComponent.of(description), insertion); + @Override + public int getComponentsSize() { + return commands.size(); } - public CommandListBox appendCommand(String alias, Component description, String insertion) { - if (!first) { - getContents().newline(); + public void appendCommand(String alias, Component description) { + appendCommand(alias, description, null); + } + + public void appendCommand(String alias, String description, String insertion) { + appendCommand(alias, TextComponent.of(description), insertion); + } + + public void appendCommand(String alias, Component description, String insertion) { + commands.add(new CommandEntry(alias, description, insertion)); + } + + private static class CommandEntry { + private final String alias; + private final Component description; + private final String insertion; + + CommandEntry(String alias, Component description, String insertion) { + this.alias = alias; + this.description = description; + this.insertion = insertion; } - TextComponent commandName = TextComponent.of(alias, TextColor.GOLD); - if (insertion != null) { - commandName = commandName - .clickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, insertion)) - .hoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Click to select"))); - } - getContents().append(commandName.append(TextComponent.of(": "))); - getContents().append(description); - first = false; - return this; - } + Component createComponent() { + TextComponentProducer line = new TextComponentProducer(); + line.append(SubtleFormat.wrap("? ") + .clickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "//help " + insertion)) + .hoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Additional Help")))); + TextComponent command = TextComponent.of(alias, TextColor.GOLD); + if (insertion == null) { + line.append(command); + } else { + line.append(command + .clickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, insertion)) + .hoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Click to select")))); + } + return line.append(TextComponent.of(": ")).append(description).create(); + } + } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/CommandUsageBox.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/CommandUsageBox.java index 6b6314b73..d2a97fcf2 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/CommandUsageBox.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/CommandUsageBox.java @@ -44,7 +44,7 @@ public class CommandUsageBox extends TextComponentProducer { * @param commands the commands to describe * @param commandString the commands that were used, such as "/we" or "/brush sphere" */ - public CommandUsageBox(List commands, String commandString) { + public CommandUsageBox(List commands, String commandString) throws InvalidComponentException { this(commands, commandString, null); } @@ -55,7 +55,7 @@ public class CommandUsageBox extends TextComponentProducer { * @param commandString the commands that were used, such as "/we" or "/brush sphere" * @param parameters list of parameters to use */ - public CommandUsageBox(List commands, String commandString, @Nullable CommandParameters parameters) { + public CommandUsageBox(List commands, String commandString, @Nullable CommandParameters parameters) throws InvalidComponentException { checkNotNull(commands); checkNotNull(commandString); Map subCommands = getSubCommands(Iterables.getLast(commands)); @@ -66,8 +66,9 @@ public class CommandUsageBox extends TextComponentProducer { } } - private void attachSubcommandUsage(Map dispatcher, String commandString, @Nullable CommandParameters parameters) { - CommandListBox box = new CommandListBox("Subcommands"); + private void attachSubcommandUsage(Map dispatcher, String commandString, @Nullable CommandParameters parameters) throws InvalidComponentException { + CommandListBox box = new CommandListBox(commandString.isEmpty() ? "Help" : "Subcommands:" + commandString, + "//help %page%" + (commandString.isEmpty() ? "" : " " + commandString)); String prefix = !commandString.isEmpty() ? commandString + " " : ""; List list = dispatcher.values().stream() @@ -80,7 +81,7 @@ public class CommandUsageBox extends TextComponentProducer { } } - append(box.create()); + append(box.create(1)); } private void attachCommandUsage(List commands, String commandString) { diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/MessageBox.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/MessageBox.java index 549943541..1497ddbaa 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/MessageBox.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/MessageBox.java @@ -21,9 +21,12 @@ package com.sk89q.worldedit.util.formatting.component; import static com.google.common.base.Preconditions.checkNotNull; +import com.google.common.base.Strings; +import com.google.common.collect.Sets; import com.sk89q.worldedit.util.formatting.text.Component; import com.sk89q.worldedit.util.formatting.text.TextComponent; import com.sk89q.worldedit.util.formatting.text.format.TextColor; +import com.sk89q.worldedit.util.formatting.text.format.TextDecoration; /** * Makes for a box with a border above and below. @@ -40,28 +43,38 @@ public class MessageBox extends TextComponentProducer { public MessageBox(String title, TextComponentProducer contents) { checkNotNull(title); - int leftOver = GUARANTEED_NO_WRAP_CHAT_PAGE_WIDTH - title.length() - 2; - int leftSide = (int) Math.floor(leftOver * 1.0/3); - int rightSide = (int) Math.floor(leftOver * 2.0/3); - if (leftSide > 0) { - append(TextComponent.of(createBorder(leftSide), TextColor.YELLOW)); - } - append(Component.space()); - append(title); - append(Component.space()); - if (rightSide > 0) { - append(TextComponent.of(createBorder(rightSide), TextColor.YELLOW)); - } - newline(); + append(centerAndBorder(TextComponent.of(title))).newline(); this.contents = contents; } - private String createBorder(int count) { - StringBuilder builder = new StringBuilder(); - for (int i = 0; i < count; i++) { - builder.append("-"); + protected Component centerAndBorder(TextComponent text) { + TextComponentProducer line = new TextComponentProducer(); + int leftOver = GUARANTEED_NO_WRAP_CHAT_PAGE_WIDTH - getLength(text); + int side = (int) Math.floor(leftOver / 2.0); + if (side > 0) { + if (side > 1) { + line.append(createBorder(side - 1)); + } + line.append(Component.space()); } - return builder.toString(); + line.append(text); + if (side > 0) { + line.append(Component.space()); + if (side > 1) { + line.append(createBorder(side - 1)); + } + } + return line.create(); + } + + private static int getLength(TextComponent text) { + return text.content().length() + text.children().stream().filter(c -> c instanceof TextComponent) + .mapToInt(c -> getLength((TextComponent) c)).sum(); + } + + private TextComponent createBorder(int count) { + return TextComponent.of(Strings.repeat("-", count), + TextColor.YELLOW, Sets.newHashSet(TextDecoration.STRIKETHROUGH)); } /** diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/PaginationBox.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/PaginationBox.java index fecdac40c..bba74a4db 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/PaginationBox.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/PaginationBox.java @@ -25,14 +25,14 @@ import com.sk89q.worldedit.util.formatting.text.event.ClickEvent; import com.sk89q.worldedit.util.formatting.text.event.HoverEvent; import com.sk89q.worldedit.util.formatting.text.format.TextColor; +import javax.annotation.Nullable; import java.util.List; -public class PaginationBox extends MessageBox { +public abstract class PaginationBox extends MessageBox { - public static final int IDEAL_ROWS_FOR_PLAYER = 8; + private static final int IDEAL_ROWS_FOR_PLAYER = 8; private String pageCommand; - private List components; private int componentsPerPage = IDEAL_ROWS_FOR_PLAYER; /** @@ -40,30 +40,30 @@ public class PaginationBox extends MessageBox { * * @param title The title */ - public PaginationBox(String title) { + protected PaginationBox(String title) { this(title, null); } - /** - * Sets the components to create pages for. - * - * @param components The components - */ - public void setComponents(List components) { - this.components = components; - } + public abstract Component getComponent(int number); + + public abstract int getComponentsSize(); public void setComponentsPerPage(int componentsPerPage) { this.componentsPerPage = componentsPerPage; } + public void formatForConsole() { + this.pageCommand = null; + this.componentsPerPage = 20; + } + /** * Creates a Paginated component * * @param title The title * @param pageCommand The command to run to switch page, with %page% representing page number */ - public PaginationBox(String title, String pageCommand) { + protected PaginationBox(String title, @Nullable String pageCommand) { super(title, new TextComponentProducer()); if (pageCommand != null && !pageCommand.contains("%page%")) { @@ -72,48 +72,76 @@ public class PaginationBox extends MessageBox { this.pageCommand = pageCommand; } - public TextComponent create(int page) throws InvalidComponentException { - if (components == null) { - throw new IllegalStateException("You must provide components before creating."); - } - if (page == 1 && components.isEmpty()) { + public Component create(int page) throws InvalidComponentException { + if (page == 1 && getComponentsSize() == 0) { return getContents().reset().append("There's nothing to see here").create(); } - int pageCount = (int) Math.ceil(components.size() / (double) componentsPerPage); + int pageCount = (int) Math.ceil(getComponentsSize() / (double) componentsPerPage); if (page < 1 || page > pageCount) { throw new InvalidComponentException("Invalid page number."); } - getContents().reset(); - for (int i = (page - 1) * componentsPerPage; i < Math.min(page * componentsPerPage, components.size()); i++) { - getContents().append(components.get(i)).newline(); + final int lastComp = Math.min(page * componentsPerPage, getComponentsSize()); + for (int i = (page - 1) * componentsPerPage; i < lastComp; i++) { + getContents().append(getComponent(i)); + if (i + 1 != lastComp) { + getContents().newline(); + } } + if (pageCount == 1) { + return super.create(); + } + getContents().newline(); TextComponent pageNumberComponent = TextComponent.of("Page ", TextColor.YELLOW) .append(TextComponent.of(String.valueOf(page), TextColor.GOLD)) .append(TextComponent.of(" of ")) .append(TextComponent.of(String.valueOf(pageCount), TextColor.GOLD)); - if (pageCommand != null) { + TextComponentProducer navProducer = new TextComponentProducer(); if (page > 1) { TextComponent prevComponent = TextComponent.of("<<< ", TextColor.GOLD) .clickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, pageCommand.replace("%page%", String.valueOf(page - 1)))) .hoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Click to navigate"))); - getContents().append(prevComponent); + navProducer.append(prevComponent); } - getContents().append(pageNumberComponent); + navProducer.append(pageNumberComponent); if (page < pageCount) { TextComponent nextComponent = TextComponent.of(" >>>", TextColor.GOLD) .clickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, pageCommand.replace("%page%", String.valueOf(page + 1)))) .hoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Click to navigate"))); - getContents().append(nextComponent); + navProducer.append(nextComponent); } + getContents().append(centerAndBorder(navProducer.create())); } else { - getContents().append(pageNumberComponent); + getContents().append(centerAndBorder(pageNumberComponent)); } - return TextComponent.of("").append(Component.newline()).append(super.create()); + return super.create(); } @Override public TextComponent create() { throw new IllegalStateException("Pagination components must be created with a page"); } + + public static PaginationBox fromStrings(String header, @Nullable String pageCommand, List lines) { + return new ListPaginationBox(header, pageCommand, lines); + } + + private static class ListPaginationBox extends PaginationBox { + private final List lines; + + ListPaginationBox(String header, String pageCommand, List lines) { + super(header, pageCommand); + this.lines = lines; + } + + @Override + public Component getComponent(int number) { + return TextComponent.of(lines.get(number)); + } + + @Override + public int getComponentsSize() { + return lines.size(); + } + } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/SchematicPaginationBox.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/SchematicPaginationBox.java new file mode 100644 index 000000000..6a1f0712c --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/SchematicPaginationBox.java @@ -0,0 +1,56 @@ +package com.sk89q.worldedit.util.formatting.component; + +import com.google.common.collect.Multimap; +import com.google.common.io.Files; +import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormat; +import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormats; +import com.sk89q.worldedit.util.formatting.text.Component; +import com.sk89q.worldedit.util.formatting.text.TextComponent; +import com.sk89q.worldedit.util.formatting.text.event.ClickEvent; +import com.sk89q.worldedit.util.formatting.text.event.HoverEvent; +import com.sk89q.worldedit.util.formatting.text.format.TextColor; + +import java.io.File; +import java.util.regex.Pattern; + +import static com.google.common.base.Preconditions.checkArgument; + +public class SchematicPaginationBox extends PaginationBox { + private final String prefix; + private final File[] files; + + public SchematicPaginationBox(String rootDir, File[] files, String pageCommand) { + super("Available schematics", pageCommand); + this.prefix = rootDir == null ? "" : rootDir; + this.files = files; + } + + @Override + public Component getComponent(int number) { + checkArgument(number < files.length - 1 && number >= 0); + File file = files[number]; + Multimap exts = ClipboardFormats.getFileExtensionMap(); + String format = exts.get(Files.getFileExtension(file.getName())) + .stream().findFirst().map(ClipboardFormat::getName).orElse("Unknown"); + boolean inRoot = file.getParentFile().getName().equals(prefix); + + String path = inRoot ? file.getName() : file.getPath().split(Pattern.quote(prefix + File.separator))[1]; + + return TextComponent.builder() + .content("") + .append(TextComponent.of("[L]") + .color(TextColor.GOLD) + .clickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/schem load " + path)) + .hoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Click to load")))) + .append(Component.space()) + .append(TextComponent.of(path) + .color(TextColor.DARK_GREEN) + .hoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextComponent.of(format)))) + .build(); + } + + @Override + public int getComponentsSize() { + return files.length; + } +}