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 c79ec4fec..e705930a6 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 @@ -19,18 +19,15 @@ package com.sk89q.worldedit.command; -import static com.sk89q.worldedit.command.util.Logging.LogMode.POSITION; -import static com.sk89q.worldedit.command.util.Logging.LogMode.REGION; - -import com.sk89q.minecraft.util.commands.Command; -import com.sk89q.minecraft.util.commands.CommandContext; -import com.sk89q.minecraft.util.commands.CommandException; -import com.sk89q.minecraft.util.commands.CommandPermissions; import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.LocalSession; import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.WorldEditException; import com.sk89q.worldedit.blocks.BaseItemStack; +import com.sk89q.worldedit.command.argument.ExpandAmount; +import com.sk89q.worldedit.command.argument.SelectorChoice; +import com.sk89q.worldedit.command.util.CommandPermissions; +import com.sk89q.worldedit.command.util.CommandPermissionsConditionGenerator; import com.sk89q.worldedit.command.util.Logging; import com.sk89q.worldedit.entity.Player; import com.sk89q.worldedit.extension.input.ParserContext; @@ -39,6 +36,8 @@ import com.sk89q.worldedit.extent.clipboard.Clipboard; import com.sk89q.worldedit.function.block.BlockDistributionCounter; import com.sk89q.worldedit.function.operation.Operations; import com.sk89q.worldedit.function.visitor.RegionVisitor; +import com.sk89q.worldedit.internal.annotation.Direction; +import com.sk89q.worldedit.internal.annotation.MultiDirection; import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.regions.Region; @@ -64,44 +63,43 @@ import com.sk89q.worldedit.world.block.BaseBlock; import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.item.ItemTypes; import com.sk89q.worldedit.world.storage.ChunkStore; +import org.enginehub.piston.annotation.Command; +import org.enginehub.piston.annotation.CommandContainer; +import org.enginehub.piston.annotation.param.Arg; +import org.enginehub.piston.annotation.param.Switch; -import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.Set; +import java.util.stream.Stream; + +import static com.sk89q.worldedit.command.util.Logging.LogMode.POSITION; +import static com.sk89q.worldedit.command.util.Logging.LogMode.REGION; /** * Selection commands. */ +@CommandContainer(superTypes = CommandPermissionsConditionGenerator.Registration.class) public class SelectionCommands { private final WorldEdit we; - + public SelectionCommands(WorldEdit we) { this.we = we; } @Command( - aliases = { "/pos1" }, - usage = "[coordinates]", - desc = "Set position 1", - min = 0, - max = 1 + name = "/pos1", + desc = "Set position 1" ) @Logging(POSITION) @CommandPermissions("worldedit.selection.pos") - public void pos1(Player player, LocalSession session, CommandContext args) throws WorldEditException { - + public void pos1(Player player, LocalSession session, + @Arg(desc = "Coordinates to set position 1 to", def = "") + BlockVector3 coordinates) throws WorldEditException { Location pos; - - if (args.argsLength() == 1) { - if (args.getString(0).matches("-?\\d+,-?\\d+,-?\\d+")) { - String[] coords = args.getString(0).split(","); - pos = new Location(player.getWorld(), Integer.parseInt(coords[0]), Integer.parseInt(coords[1]), Integer.parseInt(coords[2])); - } else { - player.printError("Invalid coordinates " + args.getString(0)); - return; - } + if (coordinates != null) { + pos = new Location(player.getWorld(), coordinates.toVector3()); } else { pos = player.getBlockIn(); } @@ -116,27 +114,17 @@ public class SelectionCommands { } @Command( - aliases = { "/pos2" }, - usage = "[coordinates]", - desc = "Set position 2", - min = 0, - max = 1 + name = "/pos2", + desc = "Set position 2" ) @Logging(POSITION) @CommandPermissions("worldedit.selection.pos") - public void pos2(Player player, LocalSession session, CommandContext args) throws WorldEditException { - + public void pos2(Player player, LocalSession session, + @Arg(desc = "Coordinates to set position 2 to", def = "") + BlockVector3 coordinates) throws WorldEditException { Location pos; - if (args.argsLength() == 1) { - if (args.getString(0).matches("-?\\d+,-?\\d+,-?\\d+")) { - String[] coords = args.getString(0).split(","); - pos = new Location(player.getWorld(), Integer.parseInt(coords[0]), - Integer.parseInt(coords[1]), - Integer.parseInt(coords[2])); - } else { - player.printError("Invalid coordinates " + args.getString(0)); - return; - } + if (coordinates != null) { + pos = new Location(player.getWorld(), coordinates.toVector3()); } else { pos = player.getBlockIn(); } @@ -151,11 +139,8 @@ public class SelectionCommands { } @Command( - aliases = { "/hpos1" }, - usage = "", - desc = "Set position 1 to targeted block", - min = 0, - max = 0 + name = "/hpos1", + desc = "Set position 1 to targeted block" ) @CommandPermissions("worldedit.selection.hpos") public void hpos1(Player player, LocalSession session) throws WorldEditException { @@ -176,11 +161,8 @@ public class SelectionCommands { } @Command( - aliases = { "/hpos2" }, - usage = "", - desc = "Set position 2 to targeted block", - min = 0, - max = 0 + name = "/hpos2", + desc = "Set position 2 to targeted block" ) @CommandPermissions("worldedit.selection.hpos") public void hpos2(Player player, LocalSession session) throws WorldEditException { @@ -201,28 +183,22 @@ public class SelectionCommands { } @Command( - aliases = { "/chunk" }, - usage = "[x,z coordinates]", - flags = "sc", - desc = "Set the selection to your current chunk.", - help = - "Set the selection to the chunk you are currently in.\n" + - "With the -s flag, your current selection is expanded\n" + - "to encompass all chunks that are part of it.\n\n" + - "Specifying coordinates will use those instead of your\n"+ - "current position. Use -c to specify chunk coordinates,\n" + - "otherwise full coordinates will be implied.\n" + - "(for example, the coordinates 5,5 are the same as -c 0,0)", - min = 0, - max = 1 + name = "/chunk", + desc = "Set the selection to your current chunk." ) @Logging(POSITION) @CommandPermissions("worldedit.selection.chunk") - public void chunk(Player player, LocalSession session, CommandContext args) throws WorldEditException { + public void chunk(Player player, LocalSession session, + @Arg(desc = "The chunk to select", def = "") + BlockVector2 coordinates, + @Switch(name = 's', desc = "Expand your selection to encompass all chunks that are part of it") + boolean expandSelection, + @Switch(name = 'c', desc = "Use chunk coordinates instead of block coordinates") + boolean useChunkCoordinates) throws WorldEditException { final BlockVector3 min; final BlockVector3 max; final World world = player.getWorld(); - if (args.hasFlag('s')) { + if (expandSelection) { Region region = session.getSelection(world); final BlockVector2 min2D = ChunkStore.toChunk(region.getMinimumPoint()); @@ -236,16 +212,11 @@ public class SelectionCommands { + max2D.getBlockX() + ", " + max2D.getBlockZ() + ")"); } else { final BlockVector2 min2D; - if (args.argsLength() == 1) { + if (coordinates != null) { // coords specified - String[] coords = args.getString(0).split(","); - if (coords.length != 2) { - throw new InsufficientArgumentsException("Invalid coordinates specified."); - } - int x = Integer.parseInt(coords[0]); - int z = Integer.parseInt(coords[1]); - BlockVector2 pos = BlockVector2.at(x, z); - min2D = (args.hasFlag('c')) ? pos : ChunkStore.toChunk(pos.toBlockVector3()); + min2D = useChunkCoordinates + ? coordinates + : ChunkStore.toChunk(coordinates.toBlockVector3()); } else { // use player loc min2D = ChunkStore.toChunk(player.getBlockIn().toVector().toBlockPoint()); @@ -273,29 +244,21 @@ public class SelectionCommands { } @Command( - aliases = { "/wand" }, - usage = "", - desc = "Get the wand object", - min = 0, - max = 0 + name = "/wand", + desc = "Get the wand object" ) @CommandPermissions("worldedit.wand") public void wand(Player player) throws WorldEditException { - player.giveItem(new BaseItemStack(ItemTypes.get(we.getConfiguration().wandItem), 1)); player.print("Left click: select pos #1; Right click: select pos #2"); } @Command( - aliases = { "toggleeditwand" }, - usage = "", - desc = "Toggle functionality of the edit wand", - min = 0, - max = 0 + name = "toggleeditwand", + desc = "Toggle functionality of the edit wand" ) @CommandPermissions("worldedit.wand.toggle") - public void toggleWand(Player player, LocalSession session, CommandContext args) throws WorldEditException { - + public void toggleWand(Player player, LocalSession session) throws WorldEditException { session.setToolControl(!session.isToolControlEnabled()); if (session.isToolControlEnabled()) { @@ -306,20 +269,23 @@ public class SelectionCommands { } @Command( - aliases = { "/expand" }, - usage = " [reverse-amount] ", - desc = "Expand the selection area", - min = 1, - max = 3 + name = "/expand", + desc = "Expand the selection area" ) @Logging(REGION) @CommandPermissions("worldedit.selection.expand") - public void expand(Player player, LocalSession session, CommandContext args) throws WorldEditException { + public void expand(Player player, LocalSession session, + @Arg(desc = "Amount to expand the selection by, can be `vert` to expand to the whole vertical column") + ExpandAmount amount, + @Arg(desc = "Amount to expand the selection by in the other direction", def = "0") + int reverseAmount, + @Arg(desc = "Direction to expand", def = Direction.AIM) + @MultiDirection + List direction) throws WorldEditException { // Special syntax (//expand vert) to expand the selection between // sky and bedrock. - if (args.getString(0).equalsIgnoreCase("vert") - || args.getString(0).equalsIgnoreCase("vertical")) { + if (amount.isVert()) { Region region = session.getSelection(player.getWorld()); try { int oldSize = region.getArea(); @@ -338,134 +304,56 @@ public class SelectionCommands { return; } - List dirs = new ArrayList<>(); - int change = args.getInteger(0); - int reverseChange = 0; - - switch (args.argsLength()) { - case 2: - // Either a reverse amount or a direction - try { - reverseChange = args.getInteger(1); - dirs.add(we.getDirection(player, "me")); - } catch (NumberFormatException e) { - if (args.getString(1).contains(",")) { - String[] split = args.getString(1).split(","); - for (String s : split) { - dirs.add(we.getDirection(player, s.toLowerCase())); - } - } else { - dirs.add(we.getDirection(player, args.getString(1).toLowerCase())); - } - } - break; - - case 3: - // Both reverse amount and direction - reverseChange = args.getInteger(1); - if (args.getString(2).contains(",")) { - String[] split = args.getString(2).split(","); - for (String s : split) { - dirs.add(we.getDirection(player, s.toLowerCase())); - } - } else { - dirs.add(we.getDirection(player, args.getString(2).toLowerCase())); - } - break; - - default: - dirs.add(we.getDirection(player, "me")); - break; - - } - Region region = session.getSelection(player.getWorld()); int oldSize = region.getArea(); - if (reverseChange == 0) { - for (BlockVector3 dir : dirs) { - region.expand(dir.multiply(change)); + if (reverseAmount == 0) { + for (BlockVector3 dir : direction) { + region.expand(dir.multiply(amount.getAmount())); } } else { - for (BlockVector3 dir : dirs) { - region.expand(dir.multiply(change), dir.multiply(-reverseChange)); + for (BlockVector3 dir : direction) { + region.expand(dir.multiply(amount.getAmount()), dir.multiply(-reverseAmount)); } } session.getRegionSelector(player.getWorld()).learnChanges(); int newSize = region.getArea(); - + session.getRegionSelector(player.getWorld()).explainRegionAdjust(player, session); - player.print("Region expanded " + (newSize - oldSize) + " blocks."); + player.print("Region expanded " + (newSize - oldSize) + " block(s)."); } @Command( - aliases = { "/contract" }, - usage = " [reverse-amount] [direction]", - desc = "Contract the selection area", - min = 1, - max = 3 + name = "/contract", + desc = "Contract the selection area" ) @Logging(REGION) @CommandPermissions("worldedit.selection.contract") - public void contract(Player player, LocalSession session, CommandContext args) throws WorldEditException { - - List dirs = new ArrayList<>(); - int change = args.getInteger(0); - int reverseChange = 0; - - switch (args.argsLength()) { - case 2: - // Either a reverse amount or a direction - try { - reverseChange = args.getInteger(1); - dirs.add(we.getDirection(player, "me")); - } catch (NumberFormatException e) { - if (args.getString(1).contains(",")) { - String[] split = args.getString(1).split(","); - for (String s : split) { - dirs.add(we.getDirection(player, s.toLowerCase())); - } - } else { - dirs.add(we.getDirection(player, args.getString(1).toLowerCase())); - } - } - break; - - case 3: - // Both reverse amount and direction - reverseChange = args.getInteger(1); - if (args.getString(2).contains(",")) { - String[] split = args.getString(2).split(","); - for (String s : split) { - dirs.add(we.getDirection(player, s.toLowerCase())); - } - } else { - dirs.add(we.getDirection(player, args.getString(2).toLowerCase())); - } - break; - - default: - dirs.add(we.getDirection(player, "me")); - break; - } - + public void contract(Player player, LocalSession session, + @Arg(desc = "Amount to contract the selection by") + int amount, + @Arg(desc = "Amount to contract the selection by in the other direction", def = "0") + int reverseAmount, + @Arg(desc = "Direction to contract", def = Direction.AIM) + @MultiDirection + List direction) throws WorldEditException { try { Region region = session.getSelection(player.getWorld()); int oldSize = region.getArea(); - if (reverseChange == 0) { - for (BlockVector3 dir : dirs) { - region.contract(dir.multiply(change)); + if (reverseAmount == 0) { + for (BlockVector3 dir : direction) { + region.contract(dir.multiply(amount)); } } else { - for (BlockVector3 dir : dirs) { - region.contract(dir.multiply(change), dir.multiply(-reverseChange)); + for (BlockVector3 dir : direction) { + region.contract(dir.multiply(amount), dir.multiply(-reverseAmount)); } } session.getRegionSelector(player.getWorld()).learnChanges(); int newSize = region.getArea(); - + session.getRegionSelector(player.getWorld()).explainRegionAdjust(player, session); @@ -476,35 +364,22 @@ public class SelectionCommands { } @Command( - aliases = { "/shift" }, - usage = " [direction]", - desc = "Shift the selection area", - min = 1, - max = 2 + name = "/shift", + desc = "Shift the selection area" ) @Logging(REGION) @CommandPermissions("worldedit.selection.shift") - public void shift(Player player, LocalSession session, CommandContext args) throws WorldEditException { - - List dirs = new ArrayList<>(); - int change = args.getInteger(0); - if (args.argsLength() == 2) { - if (args.getString(1).contains(",")) { - for (String s : args.getString(1).split(",")) { - dirs.add(we.getDirection(player, s.toLowerCase())); - } - } else { - dirs.add(we.getDirection(player, args.getString(1).toLowerCase())); - } - } else { - dirs.add(we.getDirection(player, "me")); - } - + public void shift(Player player, LocalSession session, + @Arg(desc = "Amount to shift the selection by") + int amount, + @Arg(desc = "Direction to contract", def = Direction.AIM) + @MultiDirection + List direction) throws WorldEditException { try { Region region = session.getSelection(player.getWorld()); - for (BlockVector3 dir : dirs) { - region.shift(dir.multiply(change)); + for (BlockVector3 dir : direction) { + region.shift(dir.multiply(amount)); } session.getRegionSelector(player.getWorld()).learnChanges(); @@ -518,81 +393,72 @@ public class SelectionCommands { } @Command( - aliases = { "/outset" }, - usage = "", - desc = "Outset the selection area", - help = - "Expands the selection by the given amount in all directions.\n" + - "Flags:\n" + - " -h only expand horizontally\n" + - " -v only expand vertically\n", - flags = "hv", - min = 1, - max = 1 + name = "/outset", + desc = "Outset the selection area" ) @Logging(REGION) @CommandPermissions("worldedit.selection.outset") - public void outset(Player player, LocalSession session, CommandContext args) throws WorldEditException { + public void outset(Player player, LocalSession session, + @Arg(desc = "Amount to expand the selection by in all directions") + int amount, + @Switch(name = 'h', desc = "Only expand horizontally") + boolean onlyHorizontal, + @Switch(name = 'v', desc = "Only expand vertically") + boolean onlyVertical) throws WorldEditException { Region region = session.getSelection(player.getWorld()); - region.expand(getChangesForEachDir(args)); + region.expand(getChangesForEachDir(amount, onlyHorizontal, onlyVertical)); session.getRegionSelector(player.getWorld()).learnChanges(); session.getRegionSelector(player.getWorld()).explainRegionAdjust(player, session); player.print("Region outset."); } @Command( - aliases = { "/inset" }, - usage = "", - desc = "Inset the selection area", - help = - "Contracts the selection by the given amount in all directions.\n" + - "Flags:\n" + - " -h only contract horizontally\n" + - " -v only contract vertically\n", - flags = "hv", - min = 1, - max = 1 + name = "/inset", + desc = "Inset the selection area" ) @Logging(REGION) @CommandPermissions("worldedit.selection.inset") - public void inset(Player player, LocalSession session, CommandContext args) throws WorldEditException { + public void inset(Player player, LocalSession session, + @Arg(desc = "Amount to contract the selection by in all directions") + int amount, + @Switch(name = 'h', desc = "Only contract horizontally") + boolean onlyHorizontal, + @Switch(name = 'v', desc = "Only contract vertically") + boolean onlyVertical) throws WorldEditException { Region region = session.getSelection(player.getWorld()); - region.contract(getChangesForEachDir(args)); + region.contract(getChangesForEachDir(amount, onlyHorizontal, onlyVertical)); session.getRegionSelector(player.getWorld()).learnChanges(); session.getRegionSelector(player.getWorld()).explainRegionAdjust(player, session); player.print("Region inset."); } - private BlockVector3[] getChangesForEachDir(CommandContext args) { - List changes = new ArrayList<>(6); - int change = args.getInteger(0); + private BlockVector3[] getChangesForEachDir(int amount, boolean onlyHorizontal, boolean onlyVertical) { + Stream.Builder changes = Stream.builder(); - if (!args.hasFlag('h')) { - changes.add((BlockVector3.UNIT_Y).multiply(change)); - changes.add((BlockVector3.UNIT_MINUS_Y).multiply(change)); + if (!onlyHorizontal) { + changes.add(BlockVector3.UNIT_Y); + changes.add(BlockVector3.UNIT_MINUS_Y); } - if (!args.hasFlag('v')) { - changes.add((BlockVector3.UNIT_X).multiply(change)); - changes.add((BlockVector3.UNIT_MINUS_X).multiply(change)); - changes.add((BlockVector3.UNIT_Z).multiply(change)); - changes.add((BlockVector3.UNIT_MINUS_Z).multiply(change)); + if (!onlyVertical) { + changes.add(BlockVector3.UNIT_X); + changes.add(BlockVector3.UNIT_MINUS_X); + changes.add(BlockVector3.UNIT_Z); + changes.add(BlockVector3.UNIT_MINUS_Z); } - return changes.toArray(new BlockVector3[0]); + return changes.build().map(v -> v.multiply(amount)).toArray(BlockVector3[]::new); } @Command( - aliases = { "/size" }, - flags = "c", - usage = "", - desc = "Get information about the selection", - min = 0, - max = 0 + name = "/size", + desc = "Get information about the selection" ) @CommandPermissions("worldedit.selection.size") - public void size(Player player, LocalSession session, CommandContext args) throws WorldEditException { - if (args.hasFlag('c')) { + public void size(Player player, LocalSession session, + @Switch(name = 'c', desc = "Get clipboard info instead") + boolean clipboardInfo) throws WorldEditException { + if (clipboardInfo) { ClipboardHolder holder = session.getClipboard(); Clipboard clipboard = holder.getClipboard(); Region region = clipboard.getRegion(); @@ -602,7 +468,7 @@ public class SelectionCommands { player.print("Cuboid dimensions (max - min): " + size); player.print("Offset: " + origin); player.print("Cuboid distance: " + size.distance(BlockVector3.ONE)); - player.print("# of blocks: " + (int) (size.getX() * size.getY() * size.getZ())); + player.print("# of blocks: " + (size.getX() * size.getY() * size.getZ())); return; } @@ -626,48 +492,41 @@ public class SelectionCommands { @Command( - aliases = { "/count" }, - usage = "", - flags = "f", - desc = "Counts the number of a certain type of block", - min = 1, - max = 1 + name = "/count", + desc = "Counts the number of a certain type of block" ) @CommandPermissions("worldedit.analysis.count") - public void count(Player player, LocalSession session, EditSession editSession, CommandContext args) throws WorldEditException { - + public void count(Player player, LocalSession session, EditSession editSession, + @Arg(desc = "The block type(s) to count") + String blocks, + @Switch(name = 'f', desc = "Fuzzy, match states using a wildcard") + boolean fuzzy) throws WorldEditException { ParserContext context = new ParserContext(); context.setActor(player); context.setExtent(player.getExtent()); context.setWorld(player.getWorld()); context.setSession(session); context.setRestricted(false); - context.setPreferringWildcard(args.hasFlag('f')); + context.setPreferringWildcard(fuzzy); - Set searchBlocks = we.getBlockFactory().parseFromListInput(args.getString(0), context); + Set searchBlocks = we.getBlockFactory().parseFromListInput(blocks, context); int count = editSession.countBlocks(session.getSelection(player.getWorld()), searchBlocks); player.print("Counted: " + count); } @Command( - aliases = { "/distr" }, - usage = "", - desc = "Get the distribution of blocks in the selection", - help = - "Gets the distribution of blocks in the selection.\n" + - "The -c flag gets the distribution of your clipboard.\n" + - "The -d flag separates blocks by state", - flags = "cd", - min = 0, - max = 0 + name = "/distr", + desc = "Get the distribution of blocks in the selection" ) @CommandPermissions("worldedit.analysis.distr") - public void distr(Player player, LocalSession session, EditSession editSession, CommandContext args) throws WorldEditException, CommandException { - - boolean separateStates = args.hasFlag('d'); + public void distr(Player player, LocalSession session, EditSession editSession, + @Switch(name = 'c', desc = "Get the distribution of the clipboard instead") + boolean clipboardDistr, + @Switch(name = 'd', desc = "Separate blocks by state") + boolean separateStates) throws WorldEditException { List> distribution; - if (args.hasFlag('c')) { + if (clipboardDistr) { Clipboard clipboard = session.getClipboard().getClipboard(); // throws if missing BlockDistributionCounter count = new BlockDistributionCounter(clipboard, separateStates); RegionVisitor visitor = new RegionVisitor(clipboard.getRegion(), count); @@ -707,73 +566,86 @@ public class SelectionCommands { } @Command( - aliases = { "/sel", ";", "/desel", "/deselect" }, - flags = "d", - usage = "[cuboid|extend|poly|ellipsoid|sphere|cyl|convex]", - desc = "Choose a region selector", - min = 0, - max = 1 + name = "/sel", + aliases = { ";", "/desel", "/deselect" }, + desc = "Choose a region selector" ) - public void select(Player player, LocalSession session, CommandContext args) throws WorldEditException { + public void select(Player player, LocalSession session, + @Arg(desc = "Selector to switch to", def = "") + SelectorChoice selector, + @Switch(name = 'd', desc = "Set default selector") + boolean setDefaultSelector) throws WorldEditException { final World world = player.getWorld(); - if (args.argsLength() == 0) { + if (selector == null) { session.getRegionSelector(world).clear(); session.dispatchCUISelection(player); player.print("Selection cleared."); return; } - final String typeName = args.getString(0); final RegionSelector oldSelector = session.getRegionSelector(world); - final RegionSelector selector; - if (typeName.equalsIgnoreCase("cuboid")) { - selector = new CuboidRegionSelector(oldSelector); - player.print("Cuboid: left click for point 1, right click for point 2"); - } else if (typeName.equalsIgnoreCase("extend")) { - selector = new ExtendingCuboidRegionSelector(oldSelector); - player.print("Cuboid: left click for a starting point, right click to extend"); - } else if (typeName.equalsIgnoreCase("poly")) { - selector = new Polygonal2DRegionSelector(oldSelector); - player.print("2D polygon selector: Left/right click to add a point."); - Optional limit = ActorSelectorLimits.forActor(player).getPolygonVertexLimit(); - limit.ifPresent(integer -> player.print(integer + " points maximum.")); - } else if (typeName.equalsIgnoreCase("ellipsoid")) { - selector = new EllipsoidRegionSelector(oldSelector); - player.print("Ellipsoid selector: left click=center, right click to extend"); - } else if (typeName.equalsIgnoreCase("sphere")) { - selector = new SphereRegionSelector(oldSelector); - player.print("Sphere selector: left click=center, right click to set radius"); - } else if (typeName.equalsIgnoreCase("cyl")) { - selector = new CylinderRegionSelector(oldSelector); - player.print("Cylindrical selector: Left click=center, right click to extend."); - } else if (typeName.equalsIgnoreCase("convex") || typeName.equalsIgnoreCase("hull") || typeName.equalsIgnoreCase("polyhedron")) { - selector = new ConvexPolyhedralRegionSelector(oldSelector); - player.print("Convex polyhedral selector: Left click=First vertex, right click to add more."); - Optional limit = ActorSelectorLimits.forActor(player).getPolyhedronVertexLimit(); - limit.ifPresent(integer -> player.print(integer + " points maximum.")); - } else { - CommandListBox box = new CommandListBox("Selection modes"); - StyledFragment contents = box.getContents(); - StyledFragment tip = contents.createFragment(Style.RED); - tip.append("Select one of the modes below:").newLine(); + final RegionSelector newSelector; + switch (selector) { + case CUBOID: + newSelector = new CuboidRegionSelector(oldSelector); + player.print("Cuboid: left click for point 1, right click for point 2"); + break; + case EXTEND: + newSelector = new ExtendingCuboidRegionSelector(oldSelector); + player.print("Cuboid: left click for a starting point, right click to extend"); + break; + case POLY: { + newSelector = new Polygonal2DRegionSelector(oldSelector); + player.print("2D polygon selector: Left/right click to add a point."); + Optional limit = ActorSelectorLimits.forActor(player).getPolygonVertexLimit(); + limit.ifPresent(integer -> player.print(integer + " points maximum.")); + break; + } + case ELLIPSOID: + newSelector = new EllipsoidRegionSelector(oldSelector); + player.print("Ellipsoid selector: left click=center, right click to extend"); + break; + case SPHERE: + newSelector = new SphereRegionSelector(oldSelector); + player.print("Sphere selector: left click=center, right click to set radius"); + break; + case CYL: + newSelector = new CylinderRegionSelector(oldSelector); + player.print("Cylindrical selector: Left click=center, right click to extend."); + break; + case CONVEX: + case HULL: + case POLYHEDRON: { + newSelector = new ConvexPolyhedralRegionSelector(oldSelector); + player.print("Convex polyhedral selector: Left click=First vertex, right click to add more."); + Optional limit = ActorSelectorLimits.forActor(player).getPolyhedronVertexLimit(); + limit.ifPresent(integer -> player.print(integer + " points maximum.")); + break; + } + case UNKNOWN: + default: + CommandListBox box = new CommandListBox("Selection modes"); + StyledFragment contents = box.getContents(); + StyledFragment tip = contents.createFragment(Style.RED); + tip.append("Select one of the modes below:").newLine(); - box.appendCommand("cuboid", "Select two corners of a cuboid"); - box.appendCommand("extend", "Fast cuboid selection mode"); - box.appendCommand("poly", "Select a 2D polygon with height"); - box.appendCommand("ellipsoid", "Select an ellipsoid"); - box.appendCommand("sphere", "Select a sphere"); - box.appendCommand("cyl", "Select a cylinder"); - box.appendCommand("convex", "Select a convex polyhedral"); + box.appendCommand("cuboid", "Select two corners of a cuboid"); + box.appendCommand("extend", "Fast cuboid selection mode"); + box.appendCommand("poly", "Select a 2D polygon with height"); + box.appendCommand("ellipsoid", "Select an ellipsoid"); + box.appendCommand("sphere", "Select a sphere"); + box.appendCommand("cyl", "Select a cylinder"); + box.appendCommand("convex", "Select a convex polyhedral"); - player.printRaw(ColorCodeBuilder.asColorCodes(box)); - return; + player.printRaw(ColorCodeBuilder.asColorCodes(box)); + return; } - if (args.hasFlag('d')) { + if (setDefaultSelector) { RegionSelectorType found = null; for (RegionSelectorType type : RegionSelectorType.values()) { - if (type.getSelectorClass() == selector.getClass()) { + if (type.getSelectorClass() == newSelector.getClass()) { found = type; break; } @@ -787,7 +659,7 @@ public class SelectionCommands { } } - session.setRegionSelector(world, selector); + session.setRegionSelector(world, newSelector); session.dispatchCUISelection(player); } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/CommaSeparatedValuesConverter.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/CommaSeparatedValuesConverter.java index d2947a44a..70db3963f 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/CommaSeparatedValuesConverter.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/CommaSeparatedValuesConverter.java @@ -59,9 +59,8 @@ public class CommaSeparatedValuesConverter implements ArgumentConverter { if (maximum > -1) { result.append("up to ").append(maximum).append(' '); } - result.append("comma separated values of ") + result.append("comma separated values of: ") .append(delegate.describeAcceptableArguments()); - result.setCharAt(0, Character.toUpperCase(result.charAt(0))); return result.toString(); } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/DirectionConverter.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/DirectionConverter.java index f454191da..092b7bf5d 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/DirectionConverter.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/DirectionConverter.java @@ -26,6 +26,7 @@ import com.sk89q.worldedit.UnknownDirectionException; import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.entity.Player; import com.sk89q.worldedit.internal.annotation.Direction; +import com.sk89q.worldedit.internal.annotation.MultiDirection; import com.sk89q.worldedit.math.BlockVector3; import org.enginehub.piston.CommandManager; import org.enginehub.piston.converter.ArgumentConverter; @@ -46,15 +47,23 @@ public class DirectionConverter implements ArgumentConverter { return new AutoAnnotation_DirectionConverter_direction(includeDiagonals); } + @AutoAnnotation + private static MultiDirection multiDirection(boolean includeDiagonals) { + return new AutoAnnotation_DirectionConverter_multiDirection(includeDiagonals); + } + public static void register(WorldEdit worldEdit, CommandManager commandManager) { - commandManager.registerConverter( - Key.of(BlockVector3.class, direction(false)), - new DirectionConverter(worldEdit, false) - ); - commandManager.registerConverter( - Key.of(BlockVector3.class, direction(true)), - new DirectionConverter(worldEdit, true) - ); + for (boolean includeDiagonals : new boolean[] { false, true }) { + DirectionConverter directionConverter = new DirectionConverter(worldEdit, includeDiagonals); + commandManager.registerConverter( + Key.of(BlockVector3.class, direction(includeDiagonals)), + directionConverter + ); + commandManager.registerConverter( + Key.of(BlockVector3.class, multiDirection(includeDiagonals)), + CommaSeparatedValuesConverter.wrap(directionConverter) + ); + } } private static final ImmutableSet NON_DIAGONALS = ImmutableSet.of( diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/EnumConverter.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/EnumConverter.java new file mode 100644 index 000000000..353a46a77 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/EnumConverter.java @@ -0,0 +1,52 @@ +package com.sk89q.worldedit.command.argument; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSortedMap; +import org.enginehub.piston.CommandManager; +import org.enginehub.piston.converter.ArgumentConverter; +import org.enginehub.piston.converter.ConversionResult; +import org.enginehub.piston.converter.FailedConversion; +import org.enginehub.piston.converter.SuccessfulConversion; +import org.enginehub.piston.inject.InjectedValueAccess; +import org.enginehub.piston.inject.Key; + +import javax.annotation.Nullable; +import java.util.EnumSet; + +public class EnumConverter> implements ArgumentConverter { + + public static void register(CommandManager commandManager) { + commandManager.registerConverter(Key.of(SelectorChoice.class), + new EnumConverter<>(SelectorChoice.class, SelectorChoice.UNKNOWN)); + } + + private final ImmutableMap map; + @Nullable + private final E unknownValue; + + private EnumConverter(Class enumClass, @Nullable E unknownValue) { + ImmutableSortedMap.Builder map = ImmutableSortedMap.orderedBy(String.CASE_INSENSITIVE_ORDER); + EnumSet validValues = EnumSet.allOf(enumClass); + if (unknownValue != null) { + validValues.remove(unknownValue); + } + for (E e : validValues) { + map.put(e.name(), e); + } + this.map = map.build(); + this.unknownValue = unknownValue; + } + + @Override + public String describeAcceptableArguments() { + return String.join("|", map.keySet()); + } + + @Override + public ConversionResult convert(String argument, InjectedValueAccess context) { + E result = map.getOrDefault(argument, unknownValue); + return result == null + ? FailedConversion.from(new IllegalArgumentException("Not a valid choice: " + argument)) + : SuccessfulConversion.fromSingle(result); + } +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/ExpandAmount.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/ExpandAmount.java new file mode 100644 index 000000000..d567c3d19 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/ExpandAmount.java @@ -0,0 +1,32 @@ +package com.sk89q.worldedit.command.argument; + +import javax.annotation.Nullable; + +import static com.google.common.base.Preconditions.checkNotNull; + +public final class ExpandAmount { + + public static ExpandAmount vert() { + return new ExpandAmount(null); + } + + public static ExpandAmount from(int amount) { + return new ExpandAmount(amount); + } + + @Nullable + private final Integer amount; + + private ExpandAmount(@Nullable Integer amount) { + this.amount = amount; + } + + public boolean isVert() { + return amount == null; + } + + public int getAmount() { + return checkNotNull(amount, "This amount is vertical, i.e. undefined"); + } + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/ExpandAmountConverter.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/ExpandAmountConverter.java new file mode 100644 index 000000000..4c6e64c77 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/ExpandAmountConverter.java @@ -0,0 +1,48 @@ +package com.sk89q.worldedit.command.argument; + +import com.google.common.reflect.TypeToken; +import org.enginehub.piston.CommandManager; +import org.enginehub.piston.converter.ArgumentConverter; +import org.enginehub.piston.converter.ArgumentConverters; +import org.enginehub.piston.converter.ConversionResult; +import org.enginehub.piston.converter.SuccessfulConversion; +import org.enginehub.piston.inject.InjectedValueAccess; +import org.enginehub.piston.inject.Key; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class ExpandAmountConverter implements ArgumentConverter { + + public static void register(CommandManager commandManager) { + commandManager.registerConverter(Key.of(ExpandAmount.class), new ExpandAmountConverter()); + } + + private final ArgumentConverter integerConverter = + ArgumentConverters.get(TypeToken.of(int.class)); + + private ExpandAmountConverter() { + } + + @Override + public String describeAcceptableArguments() { + return "`vert` or " + integerConverter.describeAcceptableArguments(); + } + + @Override + public List getSuggestions(String input) { + return Stream.concat(Stream.of("vert"), integerConverter.getSuggestions(input).stream()) + .filter(x -> x.startsWith(input)) + .collect(Collectors.toList()); + } + + @Override + public ConversionResult convert(String argument, InjectedValueAccess context) { + if (argument.equalsIgnoreCase("vert") + || argument.equalsIgnoreCase("vertical")) { + return SuccessfulConversion.fromSingle(ExpandAmount.vert()); + } + return integerConverter.convert(argument, context).mapSingle(ExpandAmount::from); + } +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/SelectorChoice.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/SelectorChoice.java new file mode 100644 index 000000000..8aa63e6e0 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/SelectorChoice.java @@ -0,0 +1,14 @@ +package com.sk89q.worldedit.command.argument; + +public enum SelectorChoice { + CUBOID, + EXTEND, + POLY, + ELLIPSOID, + SPHERE, + CYL, + CONVEX, + HULL, + POLYHEDRON, + UNKNOWN +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/VectorConverter.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/VectorConverter.java new file mode 100644 index 000000000..fda47dd0f --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/VectorConverter.java @@ -0,0 +1,109 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.command.argument; + +import com.google.common.collect.ImmutableList; +import com.google.common.reflect.TypeToken; +import com.sk89q.worldedit.math.BlockVector2; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.math.Vector2; +import com.sk89q.worldedit.math.Vector3; +import org.enginehub.piston.CommandManager; +import org.enginehub.piston.converter.ArgumentConverter; +import org.enginehub.piston.converter.ArgumentConverters; +import org.enginehub.piston.converter.ConversionResult; +import org.enginehub.piston.converter.FailedConversion; +import org.enginehub.piston.converter.SimpleArgumentConverter; +import org.enginehub.piston.converter.SuccessfulConversion; +import org.enginehub.piston.inject.InjectedValueAccess; +import org.enginehub.piston.inject.Key; + +import java.util.List; +import java.util.function.Function; + +public class VectorConverter implements ArgumentConverter { + public static void register(CommandManager commandManager) { + CommaSeparatedValuesConverter intConverter = CommaSeparatedValuesConverter.wrap(ArgumentConverters.get(TypeToken.of(int.class))); + CommaSeparatedValuesConverter doubleConverter = CommaSeparatedValuesConverter.wrap(ArgumentConverters.get(TypeToken.of(double.class))); + commandManager.registerConverter(Key.of(BlockVector2.class), + new VectorConverter<>( + intConverter, + 2, + cmps -> BlockVector2.at(cmps.get(0), cmps.get(1)), + "block vector with x and z" + )); + commandManager.registerConverter(Key.of(Vector2.class), + new VectorConverter<>( + doubleConverter, + 3, + cmps -> Vector2.at(cmps.get(0), cmps.get(1)), + "vector with x and z" + )); + commandManager.registerConverter(Key.of(BlockVector3.class), + new VectorConverter<>( + intConverter, + 2, + cmps -> BlockVector3.at(cmps.get(0), cmps.get(1), cmps.get(2)), + "block vector with x, y, and z" + )); + commandManager.registerConverter(Key.of(Vector3.class), + new VectorConverter<>( + doubleConverter, + 3, + cmps -> Vector3.at(cmps.get(0), cmps.get(1), cmps.get(2)), + "vector with x, y, and z" + )); + } + + private final ArgumentConverter componentConverter; + private final int componentCount; + private final Function, T> vectorConstructor; + private final String acceptableArguments; + + + private VectorConverter(ArgumentConverter componentConverter, + int componentCount, + Function, T> vectorConstructor, + String acceptableArguments) { + this.componentConverter = componentConverter; + this.componentCount = componentCount; + this.vectorConstructor = vectorConstructor; + this.acceptableArguments = acceptableArguments; + } + + @Override + public String describeAcceptableArguments() { + return "any " + acceptableArguments; + } + + @Override + public ConversionResult convert(String argument, InjectedValueAccess context) { + ConversionResult components = componentConverter.convert(argument, context); + if (!components.isSuccessful()) { + return components.failureAsAny(); + } + if (components.get().size() != componentCount) { + return FailedConversion.from(new IllegalArgumentException( + "Must have exactly " + componentCount + " vector components")); + } + T vector = vectorConstructor.apply(ImmutableList.copyOf(components.get())); + return SuccessfulConversion.fromSingle(vector); + } +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlatformCommandMananger.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlatformCommandMananger.java index a0a94591d..037617ab3 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlatformCommandMananger.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlatformCommandMananger.java @@ -48,11 +48,16 @@ import com.sk89q.worldedit.command.SchematicCommands; import com.sk89q.worldedit.command.SchematicCommandsRegistration; import com.sk89q.worldedit.command.ScriptingCommands; import com.sk89q.worldedit.command.ScriptingCommandsRegistration; +import com.sk89q.worldedit.command.SelectionCommands; +import com.sk89q.worldedit.command.SelectionCommandsRegistration; import com.sk89q.worldedit.command.argument.Arguments; import com.sk89q.worldedit.command.argument.CommaSeparatedValuesConverter; import com.sk89q.worldedit.command.argument.DirectionConverter; +import com.sk89q.worldedit.command.argument.EnumConverter; +import com.sk89q.worldedit.command.argument.ExpandAmountConverter; import com.sk89q.worldedit.command.argument.MaskConverter; import com.sk89q.worldedit.command.argument.PatternConverter; +import com.sk89q.worldedit.command.argument.VectorConverter; import com.sk89q.worldedit.command.util.CommandPermissionsConditionGenerator; import com.sk89q.worldedit.command.util.PermissionCondition; import com.sk89q.worldedit.entity.Entity; @@ -193,6 +198,9 @@ public final class PlatformCommandMananger { ), count) ); } + VectorConverter.register(commandManager); + EnumConverter.register(commandManager); + ExpandAmountConverter.register(commandManager); } private void registerAlwaysInjectedValues() { @@ -305,13 +313,17 @@ public final class PlatformCommandMananger { ScriptingCommandsRegistration.builder(), new ScriptingCommands(worldEdit) ); + register( + commandManager, + SelectionCommandsRegistration.builder(), + new SelectionCommands(worldEdit) + ); // Unported commands are below. Delete once they're added to the main manager above. /* dispatcher = new CommandGraph() .builder(builder) .commands() - .registerMethods(new SelectionCommands(worldEdit)) .registerMethods(new SnapshotUtilCommands(worldEdit)) .registerMethods(new ToolUtilCommands(worldEdit)) .registerMethods(new ToolCommands(worldEdit)) diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/annotation/MultiDirection.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/annotation/MultiDirection.java new file mode 100644 index 000000000..efd1ba701 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/annotation/MultiDirection.java @@ -0,0 +1,37 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.internal.annotation; + +import org.enginehub.piston.inject.InjectAnnotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotates a {@code List} parameter to inject multiple direction. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.PARAMETER) +@InjectAnnotation +public @interface MultiDirection { + boolean includeDiagonals() default false; +}