Plex-FAWE/worldedit-core/src/main/java/com/sk89q/worldedit/command/UtilityCommands.java

995 lines
42 KiB
Java
Raw Normal View History

/*
2014-04-04 22:03:18 +00:00
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
2014-04-04 22:03:18 +00:00
* 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.
*
2014-04-04 22:03:18 +00:00
* 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.
*
2014-04-04 22:03:18 +00:00
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
2014-04-04 22:03:18 +00:00
*/
package com.sk89q.worldedit.command;
import static com.sk89q.worldedit.command.util.Logging.LogMode.PLACEMENT;
import com.boydti.fawe.Fawe;
2019-07-18 06:56:57 +00:00
import com.boydti.fawe.config.BBC;
import com.boydti.fawe.config.Settings;
import com.boydti.fawe.object.DelegateConsumer;
import com.boydti.fawe.object.FawePlayer;
import com.boydti.fawe.object.RunnableVal3;
import com.boydti.fawe.util.MainUtil;
import com.boydti.fawe.util.MathMan;
import com.boydti.fawe.util.chat.Message;
2018-09-06 20:40:13 +00:00
import com.boydti.fawe.util.image.ImageUtil;
2018-12-23 16:19:33 +00:00
import com.sk89q.minecraft.util.commands.CommandContext;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.IncompleteRegionException;
2018-12-23 16:19:33 +00:00
import com.sk89q.worldedit.LocalConfiguration;
import com.sk89q.worldedit.LocalSession;
import com.sk89q.worldedit.MaxChangedBlocksException;
2018-12-23 16:19:33 +00:00
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.command.util.CommandPermissions;
import com.sk89q.worldedit.command.util.CommandPermissionsConditionGenerator;
2019-07-18 17:10:04 +00:00
import com.sk89q.worldedit.command.util.CommandQueued;
import com.sk89q.worldedit.command.util.CommandQueuedConditionGenerator;
import com.sk89q.worldedit.command.util.CreatureButcher;
import com.sk89q.worldedit.command.util.EntityRemover;
import com.sk89q.worldedit.command.util.Logging;
import com.sk89q.worldedit.command.util.PrintCommandHelp;
import com.sk89q.worldedit.command.util.WorldEditAsyncCommandBuilder;
import com.sk89q.worldedit.entity.Entity;
import com.sk89q.worldedit.entity.Player;
import com.sk89q.worldedit.extension.platform.Actor;
import com.sk89q.worldedit.extension.platform.Capability;
import com.sk89q.worldedit.extension.platform.Platform;
import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormat;
import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormats;
import com.sk89q.worldedit.function.EntityFunction;
import com.sk89q.worldedit.function.mask.BlockTypeMask;
import com.sk89q.worldedit.function.mask.ExistingBlockMask;
import com.sk89q.worldedit.function.mask.Mask;
import com.sk89q.worldedit.function.operation.Operations;
import com.sk89q.worldedit.function.pattern.Pattern;
import com.sk89q.worldedit.function.visitor.EntityVisitor;
2019-07-18 06:56:57 +00:00
import com.sk89q.worldedit.internal.expression.Expression;
import com.sk89q.worldedit.internal.expression.ExpressionException;
2018-12-23 16:19:33 +00:00
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.command.CommandMapping;
import com.sk89q.worldedit.util.command.Dispatcher;
import com.sk89q.worldedit.util.command.binding.Range;
import com.sk89q.worldedit.util.command.parametric.Optional;
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 java.awt.RenderingHints;
2018-09-06 20:40:13 +00:00
import java.awt.image.BufferedImage;
import java.io.File;
2018-09-06 20:40:13 +00:00
import java.io.IOException;
import java.net.URI;
2018-09-06 20:40:13 +00:00
import java.nio.file.Files;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
2018-09-06 20:40:13 +00:00
import java.util.List;
import java.util.Locale;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.function.Supplier;
import javax.imageio.ImageIO;
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.ArgFlag;
import org.enginehub.piston.annotation.param.Switch;
/**
* Utility commands.
*/
2019-07-18 17:10:04 +00:00
@CommandContainer(superTypes = {CommandPermissionsConditionGenerator.Registration.class, CommandQueuedConditionGenerator.Registration.class})
2019-07-17 16:31:13 +00:00
@Command(aliases = {}, desc = "Various utility commands: [More Info](http://wiki.sk89q.com/wiki/WorldEdit/Utilities)")
public class UtilityCommands {
2019-04-03 11:28:57 +00:00
private final WorldEdit we;
public UtilityCommands(WorldEdit we) {
2019-04-03 11:28:57 +00:00
this.we = we;
}
@Command(
name = "/heightmapinterface",
2018-09-07 15:09:31 +00:00
desc = "Generate the heightmap interface: https://github.com/boy0001/HeightMap"
2018-09-06 20:40:13 +00:00
)
@CommandPermissions("fawe.admin")
2019-07-17 06:11:55 +00:00
public void heightmapInterface(Player player, @Arg(name = "min", desc = "int", def = "100") int min, @Arg(name = "max", desc = "int", def = "200") int max) throws IOException {
player.print("Please wait while we generate the minified heightmaps.");
2018-09-06 20:40:13 +00:00
File srcFolder = MainUtil.getFile(Fawe.imp().getDirectory(), Settings.IMP.PATHS.HEIGHTMAP);
File webSrc = new File(Fawe.imp().getDirectory(), "web" + File.separator + "heightmap");
File minImages = new File(webSrc, "images" + File.separator + "min");
File maxImages = new File(webSrc, "images" + File.separator + "max");
final int sub = srcFolder.getAbsolutePath().length();
List<String> images = new ArrayList<>();
2019-03-28 19:02:37 +00:00
MainUtil.iterateFiles(srcFolder, file -> {
switch (file.getName().substring(file.getName().lastIndexOf('.')).toLowerCase()) {
case ".png":
case ".jpeg":
break;
default:
return;
}
try {
String name = file.getAbsolutePath().substring(sub);
if (name.startsWith(File.separator)) name = name.replaceFirst(java.util.regex.Pattern.quote(File.separator), "");
BufferedImage img = MainUtil.readImage(file);
BufferedImage minImg = ImageUtil.getScaledInstance(img, min, min, RenderingHints.VALUE_INTERPOLATION_BILINEAR, true);
BufferedImage maxImg = max == -1 ? img : ImageUtil.getScaledInstance(img, max, max, RenderingHints.VALUE_INTERPOLATION_BILINEAR, true);
player.print("Writing " + name);
2019-03-28 19:02:37 +00:00
File minFile = new File(minImages, name);
File maxFile = new File(maxImages, name);
minFile.getParentFile().mkdirs();
maxFile.getParentFile().mkdirs();
ImageIO.write(minImg, "png", minFile);
ImageIO.write(maxImg, "png", maxFile);
images.add(name);
} catch (IOException e) {
throw new RuntimeException(e);
2018-09-06 20:40:13 +00:00
}
});
StringBuilder config = new StringBuilder();
config.append("var images = [\n");
for (String image : images) {
config.append('"' + image.replace(File.separator, "/") + "\",\n");
}
config.append("];\n");
config.append("// The low res images (they should all be the same size)\n");
config.append("var src_min = \"images/min/\";\n");
config.append("// The max resolution images (Use the same if there are no higher resolution ones available)\n");
config.append("var src_max = \"images/max/\";\n");
config.append("// The local source for the image (used in commands)\n");
config.append("var src_local = \"file://\";\n");
File configFile = new File(webSrc, "config.js");
player.print("Writing " + configFile);
2018-09-06 20:40:13 +00:00
Files.write(configFile.toPath(), config.toString().getBytes());
player.print("Done! See: `FastAsyncWorldEdit/web/heightmap`");
2018-09-06 20:40:13 +00:00
}
@Command(
name = "/cancel",
aliases= {"fcancel"},
desc = "Cancel your current command"
)
@CommandPermissions("fawe.cancel")
2019-07-18 17:10:04 +00:00
@CommandQueued(false)
public void cancel(FawePlayer player) {
int cancelled = player.cancel(false);
BBC.WORLDEDIT_CANCEL_COUNT.send(player, cancelled);
}
@Command(
name = "/fill",
desc = "Fill a hole"
)
@CommandPermissions("worldedit.fill")
@Logging(PLACEMENT)
public int fill(Player player, LocalSession session, EditSession editSession,
@Arg(desc = "The blocks to fill with")
Pattern pattern,
@Arg(desc = "The radius to fill in")
2019-07-17 16:31:13 +00:00
@Range(min=1) double radius,
@Arg(desc = "The depth to fill", def = "1")
2019-07-17 16:31:13 +00:00
@Range(min=1) int depth,
@Arg(desc = "Direction to fill", def = "down") BlockVector3 direction) throws WorldEditException {
we.checkMaxRadius(radius);
2019-01-09 07:13:44 +00:00
BlockVector3 pos = session.getPlacementPosition(player);
int affected = editSession.fillDirection(pos, pattern, radius, depth, direction);
player.print(affected + " block(s) have been created.");
return affected;
}
/*
@Command(
name = "patterns",
desc = "View help about patterns",
2019-07-17 05:43:18 +00:00
descFooter = "Patterns determine what blocks are placed\n" +
" - Use [brackets] for arguments\n" +
" - Use , to OR multiple\n" +
"e.g. #surfacespread[10][#existing],andesite\n" +
"More Info: https://git.io/vSPmA"
)
2019-07-18 17:10:04 +00:00
@CommandQueued(false)
@CommandPermissions("worldedit.patterns")
public void patterns(Player player, LocalSession session, CommandContext args) throws WorldEditException {
displayModifierHelp(player, DefaultPatternParser.class, args);
}
@Command(
name = "masks",
desc = "View help about masks",
2019-07-17 05:43:18 +00:00
descFooter = "Masks determine if a block can be placed\n" +
" - Use [brackets] for arguments\n" +
" - Use , to OR multiple\n" +
" - Use & to AND multiple\n" +
"e.g. >[stone,dirt],#light[0][5],$jungle\n" +
"More Info: https://git.io/v9r4K"
)
2019-07-18 17:10:04 +00:00
@CommandQueued(false)
@CommandPermissions("worldedit.masks")
public void masks(Player player, LocalSession session, CommandContext args) throws WorldEditException {
displayModifierHelp(player, DefaultMaskParser.class, args);
}
@Command(
name = "transforms",
desc = "View help about transforms",
2019-07-17 05:43:18 +00:00
descFooter = "Transforms modify how a block is placed\n" +
" - Use [brackets] for arguments\n" +
" - Use , to OR multiple\n" +
" - Use & to AND multiple\n" +
"More Info: https://git.io/v9KHO",
)
2019-07-18 17:10:04 +00:00
@CommandQueued(false)
@CommandPermissions("worldedit.transforms")
public void transforms(Player player, LocalSession session, CommandContext args) throws WorldEditException {
displayModifierHelp(player, DefaultTransformParser.class, args);
}
private void displayModifierHelp(Player player, Class<? extends FaweParser> clazz, CommandContext args) {
FaweParser parser = FaweAPI.getParser(clazz);
if (args.argsLength() == 0) {
String base = getCommand().aliases()[0];
UsageMessage msg = new UsageMessage(getCallable(), "/" + base, args.getLocals());
msg.newline().paginate(base, 0, 1).send(player);
return;
}
if (parser != null) {
CommandMapping mapping = parser.getDispatcher().get(args.getString(0));
if (mapping != null) {
new UsageMessage(mapping.getCallable(), args.getString(0), args.getLocals()) {
@Override
public String separateArg(String arg) {
return "&7[" + arg + "&7]";
}
}.send(player);
} else {
UtilityCommands.help(args, player, getCommand().aliases()[0] + " ", parser.getDispatcher());
}
}
}
*/
@Command(
name = "/fillr",
desc = "Fill a hole recursively"
)
@CommandPermissions("worldedit.fill.recursive")
@Logging(PLACEMENT)
public int fillr(Player player, LocalSession session, EditSession editSession,
@Arg(desc = "The blocks to fill with")
Pattern pattern,
@Arg(desc = "The radius to fill in")
2019-07-17 16:31:13 +00:00
@Range(min=1) double radius,
@Arg(desc = "The depth to fill", def = "")
2019-07-17 16:31:13 +00:00
@Range(min=1) Integer depth) throws WorldEditException {
we.checkMaxRadius(radius);
depth = depth == null ? Integer.MAX_VALUE : Math.max(1, depth);
we.checkMaxRadius(radius);
2019-01-09 07:13:44 +00:00
BlockVector3 pos = session.getPlacementPosition(player);
int affected = editSession.fillXZ(pos, pattern, radius, depth, true);
player.print(affected + " block(s) have been created.");
return affected;
}
@Command(
name = "/drain",
desc = "Drain a pool"
)
@CommandPermissions("worldedit.drain")
@Logging(PLACEMENT)
public int drain(Player player, LocalSession session, EditSession editSession,
@Arg(desc = "The radius to drain")
2019-07-17 16:31:13 +00:00
@Range(min=0) double radius,
@Switch(name = 'w', desc = "Also un-waterlog blocks")
boolean waterlogged) throws WorldEditException {
we.checkMaxRadius(radius);
int affected = editSession.drainArea(
session.getPlacementPosition(player), radius, waterlogged);
player.print(affected + " block(s) have been changed.");
return affected;
}
@Command(
name = "fixlava",
aliases = { "/fixlava" },
desc = "Fix lava to be stationary"
)
@CommandPermissions("worldedit.fixlava")
@Logging(PLACEMENT)
public int fixLava(Player player, LocalSession session, EditSession editSession,
@Arg(desc = "The radius to fix in")
2019-07-17 16:31:13 +00:00
@Range(min=0) double radius) throws WorldEditException {
we.checkMaxRadius(radius);
int affected = editSession.fixLiquid(session.getPlacementPosition(player), radius, BlockTypes.LAVA);
player.print(affected + " block(s) have been changed.");
return affected;
}
@Command(
name = "fixwater",
aliases = { "/fixwater" },
desc = "Fix water to be stationary"
)
@CommandPermissions("worldedit.fixwater")
@Logging(PLACEMENT)
public int fixWater(Player player, LocalSession session, EditSession editSession,
@Arg(desc = "The radius to fix in")
2019-07-17 16:31:13 +00:00
@Range(min=0) double radius) throws WorldEditException {
we.checkMaxRadius(radius);
int affected = editSession.fixLiquid(session.getPlacementPosition(player), radius, BlockTypes.WATER);
2019-04-05 16:04:27 +00:00
BBC.VISITOR_BLOCK.send(player, affected);
return affected;
}
@Command(
name = "removeabove",
aliases = { "/removeabove" },
desc = "Remove blocks above your head."
)
@CommandPermissions("worldedit.removeabove")
@Logging(PLACEMENT)
public int removeAbove(Player player, LocalSession session, EditSession editSession,
@Arg(desc = "The apothem of the square to remove from", def = "1")
2019-07-17 16:31:13 +00:00
@Range(min=1) int size,
@Arg(desc = "The maximum height above you to remove from", def = "")
Integer height) throws WorldEditException {
we.checkMaxRadius(size);
int affected = editSession.removeAbove(session.getPlacementPosition(player), size, height);
2019-04-05 16:04:27 +00:00
BBC.VISITOR_BLOCK.send(player, affected);
return affected;
}
@Command(
name = "removebelow",
aliases = { "/removebelow" },
desc = "Remove blocks below you."
)
@CommandPermissions("worldedit.removebelow")
@Logging(PLACEMENT)
public int removeBelow(Player player, LocalSession session, EditSession editSession,
@Arg(desc = "The apothem of the square to remove from", def = "1")
2019-07-17 16:31:13 +00:00
@Range(min=1) int size,
@Arg(desc = "The maximum height below you to remove from", def = "")
Integer height) throws WorldEditException {
we.checkMaxRadius(size);
World world = player.getWorld();
height = height != null ? Math.min((world.getMaxY() + 1), height + 1) : (world.getMaxY() + 1);
int affected = editSession.removeBelow(session.getPlacementPosition(player), size, height);
2019-04-05 16:04:27 +00:00
BBC.VISITOR_BLOCK.send(player, affected);
return affected;
}
@Command(
name = "removenear",
aliases = { "/removenear" },
desc = "Remove blocks near you."
)
@CommandPermissions("worldedit.removenear")
@Logging(PLACEMENT)
public int removeNear(Player player, LocalSession session, EditSession editSession,
@Arg(desc = "The mask of blocks to remove")
Mask mask,
@Arg(desc = "The radius of the square to remove from", def = "50")
2019-07-17 16:31:13 +00:00
@Range(min=1) int radius) throws WorldEditException {
we.checkMaxRadius(radius);
int affected = editSession.removeNear(session.getPlacementPosition(player), mask, radius);
2019-04-05 16:04:27 +00:00
BBC.VISITOR_BLOCK.send(player, affected);
return affected;
}
@Command(
name = "replacenear",
aliases = { "/replacenear" },
desc = "Replace nearby blocks"
)
@CommandPermissions("worldedit.replacenear")
@Logging(PLACEMENT)
public int replaceNear(Player player, LocalSession session, EditSession editSession,
@Arg(desc = "The radius of the square to remove in")
2019-07-17 16:31:13 +00:00
@Range(min=1) int radius,
@Arg(desc = "The mask matching blocks to remove", def = "")
Mask from,
@Arg(desc = "The pattern of blocks to replace with")
Pattern to) throws WorldEditException {
we.checkMaxRadius(radius);
BlockVector3 base = session.getPlacementPosition(player);
BlockVector3 min = base.subtract(radius, radius, radius);
BlockVector3 max = base.add(radius, radius, radius);
Region region = new CuboidRegion(player.getWorld(), min, max);
if (from == null) {
from = new ExistingBlockMask(editSession);
}
int affected = editSession.replaceBlocks(region, from, to);
BBC.VISITOR_BLOCK.send(player, affected);
return affected;
}
@Command(
name = "snow",
aliases = { "/snow" },
desc = "Simulates snow"
)
@CommandPermissions("worldedit.snow")
@Logging(PLACEMENT)
public int snow(Player player, LocalSession session, EditSession editSession,
@Arg(desc = "The radius of the circle to snow in", def = "10")
2019-07-17 16:31:13 +00:00
@Range(min=1) double size) throws WorldEditException {
we.checkMaxRadius(size);
int affected = editSession.simulateSnow(session.getPlacementPosition(player), size);
player.print(affected + " surfaces covered. Let it snow~");
return affected;
}
@Command(
name = "thaw",
aliases = { "/thaw" },
desc = "Thaws the area"
)
@CommandPermissions("worldedit.thaw")
@Logging(PLACEMENT)
public int thaw(Player player, LocalSession session, EditSession editSession,
@Arg(desc = "The radius of the circle to thaw in", def = "10")
2019-07-17 16:31:13 +00:00
@Range(min=1) double size) throws WorldEditException {
we.checkMaxRadius(size);
int affected = editSession.thaw(session.getPlacementPosition(player), size);
player.print(affected + " surfaces thawed.");
return affected;
}
@Command(
name = "green",
aliases = { "/green" },
desc = "Converts dirt to grass blocks in the area"
)
@CommandPermissions("worldedit.green")
@Logging(PLACEMENT)
public int green(Player player, LocalSession session, EditSession editSession,
@Arg(desc = "The radius of the circle to convert in", def = "10")
2019-07-17 16:31:13 +00:00
@Range(min=1) double size,
@Switch(name = 'f', desc = "Also convert coarse dirt")
boolean convertCoarse) throws WorldEditException {
we.checkMaxRadius(size);
final boolean onlyNormalDirt = !convertCoarse;
final int affected = editSession.green(session.getPlacementPosition(player), size, onlyNormalDirt);
2019-04-05 16:04:27 +00:00
BBC.VISITOR_BLOCK.send(player, affected);
return affected;
}
@Command(
name = "extinguish",
aliases = { "/ex", "/ext", "/extinguish", "ex", "ext" },
desc = "Extinguish nearby fire"
)
@CommandPermissions("worldedit.extinguish")
@Logging(PLACEMENT)
public void extinguish(Player player, LocalSession session, EditSession editSession,
@Arg(desc = "The radius of the square to remove in", def = "")
2019-07-17 16:31:13 +00:00
@Range(min=1) Integer radius) throws WorldEditException {
LocalConfiguration config = we.getConfiguration();
int defaultRadius = config.maxRadius != -1 ? Math.min(40, config.maxRadius) : 40;
2019-07-17 16:31:13 +00:00
int size = radius != null ? radius : defaultRadius;
we.checkMaxRadius(size);
Mask mask = new BlockTypeMask(editSession, BlockTypes.FIRE);
int affected = editSession.removeNear(session.getPlacementPosition(player), mask, size);
2019-04-05 16:04:27 +00:00
BBC.VISITOR_BLOCK.send(player, affected);
}
@Command(
name = "butcher",
desc = "Kill all or nearby mobs"
)
@CommandPermissions("worldedit.butcher")
@Logging(PLACEMENT)
public int butcher(Actor actor,
@Arg(desc = "Radius to kill mobs in", def = "")
Integer radius,
@Switch(name = 'p', desc = "Also kill pets")
boolean killPets,
@Switch(name = 'n', desc = "Also kill NPCs")
boolean killNpcs,
@Switch(name = 'g', desc = "Also kill golems")
boolean killGolems,
@Switch(name = 'a', desc = "Also kill animals")
boolean killAnimals,
@Switch(name = 'b', desc = "Also kill ambient mobs")
boolean killAmbient,
@Switch(name = 't', desc = "Also kill mobs with name tags")
boolean killWithName,
@Switch(name = 'f', desc = "Also kill all friendly mobs (Applies the flags `-abgnpt`)")
boolean killFriendly,
@Switch(name = 'r', desc = "Also destroy armor stands")
boolean killArmorStands) throws WorldEditException {
LocalConfiguration config = we.getConfiguration();
Player player = actor instanceof Player ? (Player) actor : null;
if (radius == null) {
radius = config.butcherDefaultRadius;
} else if (radius < -1) {
actor.printError("Use -1 to remove all mobs in loaded chunks");
return 0;
} else if (radius == -1) {
if (config.butcherMaxRadius != -1) {
radius = config.butcherMaxRadius;
}
}
if (config.butcherMaxRadius != -1) {
radius = Math.min(radius, config.butcherMaxRadius);
}
CreatureButcher flags = new CreatureButcher(actor);
flags.or(CreatureButcher.Flags.FRIENDLY, killFriendly); // No permission check here. Flags will instead be filtered by the subsequent calls.
flags.or(CreatureButcher.Flags.PETS, killPets, "worldedit.butcher.pets");
flags.or(CreatureButcher.Flags.NPCS, killNpcs, "worldedit.butcher.npcs");
flags.or(CreatureButcher.Flags.GOLEMS, killGolems, "worldedit.butcher.golems");
flags.or(CreatureButcher.Flags.ANIMALS, killAnimals, "worldedit.butcher.animals");
flags.or(CreatureButcher.Flags.AMBIENT, killAmbient, "worldedit.butcher.ambient");
flags.or(CreatureButcher.Flags.TAGGED, killWithName, "worldedit.butcher.tagged");
flags.or(CreatureButcher.Flags.ARMOR_STAND, killArmorStands, "worldedit.butcher.armorstands");
int killed = killMatchingEntities(radius, player, flags::createFunction);
actor.print("Killed " + killed + (killed != 1 ? " mobs" : " mob") + (radius < 0 ? "" : " in a radius of " + radius) + ".");
return killed;
}
@Command(
name = "remove",
aliases = { "rem", "rement" },
desc = "Remove all entities of a type"
)
@CommandPermissions("worldedit.remove")
@Logging(PLACEMENT)
public int remove(Actor actor,
@Arg(desc = "The type of entity to remove")
EntityRemover remover,
@Arg(desc = "The radius of the cuboid to remove from")
int radius) throws WorldEditException {
Player player = actor instanceof Player ? (Player) actor : null;
if (radius < -1) {
actor.printError("Use -1 to remove all entities in loaded chunks");
return 0;
}
int removed = killMatchingEntities(radius, player, remover::createFunction);
actor.print("Marked " + removed + (removed != 1 ? " entities" : " entity") + " for removal.");
return removed;
}
private int killMatchingEntities(Integer radius, Player player, Supplier<EntityFunction> func) throws IncompleteRegionException, MaxChangedBlocksException {
List<EntityVisitor> visitors = new ArrayList<>();
LocalSession session = null;
EditSession editSession = null;
if (player != null) {
session = we.getSessionManager().get(player);
2018-12-23 16:19:33 +00:00
BlockVector3 center = session.getPlacementPosition(player);
editSession = session.createEditSession(player);
List<? extends Entity> entities;
if (radius >= 0) {
CylinderRegion region = CylinderRegion.createRadius(editSession, center, radius);
entities = editSession.getEntities(region);
} else {
entities = editSession.getEntities();
}
visitors.add(new EntityVisitor(entities.iterator(), func.get()));
} else {
Platform platform = we.getPlatformManager().queryCapability(Capability.WORLD_EDITING);
for (World world : platform.getWorlds()) {
List<? extends Entity> entities = world.getEntities();
visitors.add(new EntityVisitor(entities.iterator(), func.get()));
}
}
int killed = 0;
for (EntityVisitor visitor : visitors) {
Operations.completeLegacy(visitor);
killed += visitor.getAffected();
}
BBC.KILL_SUCCESS.send(player, killed, radius);
if (editSession != null) {
session.remember(editSession);
2018-10-21 01:54:58 +00:00
editSession.flushSession();
}
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" },
desc = "Evaluate a mathematical expression"
)
@CommandPermissions("worldedit.calc")
public void calc(Actor actor,
@Arg(desc = "Expression to evaluate", variable = true)
List<String> input) {
Expression expression;
try {
expression = Expression.compile(String.join(" ", input));
} catch (ExpressionException e) {
actor.printError(String.format(
"'%s' could not be parsed as a valid expression", input));
return;
}
WorldEditAsyncCommandBuilder.createAndSendMessage(actor, () -> {
double result = expression.evaluateTimeout(WorldEdit.getInstance().getSessionManager().get(actor).getTimeout());
String formatted = Double.isNaN(result) ? "NaN" : formatter.format(result);
return SubtleFormat.wrap(input + " = ").append(TextComponent.of(formatted, TextColor.LIGHT_PURPLE));
}, null);
}
@Command(
name = "/confirm",
desc = "Confirm a command"
)
@CommandPermissions("fawe.confirm")
public void confirm(FawePlayer fp) throws WorldEditException {
if (!fp.confirm()) {
BBC.NOTHING_CONFIRMED.send(fp);
}
}
@Command(
name = "/help",
desc = "Displays help for WorldEdit commands"
)
@CommandPermissions("worldedit.help")
public void help(Actor actor,
@Switch(name = 's', desc = "List sub-commands of the given command, if applicable")
boolean listSubCommands,
@ArgFlag(name = 'p', desc = "The page to retrieve", def = "1")
int page,
@Arg(desc = "The command to retrieve help for", def = "", variable = true)
List<String> command) throws WorldEditException {
PrintCommandHelp.help(command, page, listSubCommands, we, actor);
}
protected static CommandMapping detectCommand(Dispatcher dispatcher, String command, boolean isRootLevel) {
CommandMapping mapping;
// First try the command as entered
mapping = dispatcher.get(command);
if (mapping != null) {
return mapping;
}
// Then if we're looking at root commands and the user didn't use
// any slashes, let's try double slashes and then single slashes.
// However, be aware that there exists different single slash
// and double slash commands in WorldEdit
if (isRootLevel && !command.contains("/")) {
mapping = dispatcher.get("//" + command);
if (mapping != null) {
return mapping;
}
mapping = dispatcher.get("/" + command);
if (mapping != null) {
return mapping;
}
}
return null;
}
public static void list(File dir, Actor actor, CommandContext args, @Range(min = 0) int page, String formatName, boolean playerFolder, String onClickCmd) {
list(dir, actor, args, page, -1, formatName, playerFolder, new RunnableVal3<Message, URI, String>() {
@Override
public void run(Message m, URI uri, String fileName) {
m.text(BBC.SCHEMATIC_LIST_ELEM, fileName, "");
if (onClickCmd != null) m.cmdTip(onClickCmd + " " + fileName);
}
});
}
public static void list(File dir, Actor actor, CommandContext args, @Range(min = 0) int page, int perPage, String formatName, boolean playerFolder, RunnableVal3<Message, URI, String> eachMsg) {
AtomicInteger pageInt = new AtomicInteger(page);
List<File> fileList = new ArrayList<>();
if (perPage == -1) perPage = actor instanceof Player ? 12 : 20; // More pages for console
page = getFiles(dir, actor, args, page, perPage, formatName, playerFolder, fileList::add);
if (fileList.isEmpty()) {
BBC.SCHEMATIC_NONE.send(actor);
return;
}
int pageCount = (fileList.size() + perPage - 1) / perPage;
if (page < 1) {
BBC.SCHEMATIC_PAGE.send(actor, ">0");
return;
}
if (page > pageCount) {
BBC.SCHEMATIC_PAGE.send(actor, "<" + (pageCount + 1));
return;
}
final int sortType = args.hasFlag('d') ? -1 : args.hasFlag('n') ? 1 : 0;
// cleanup file list
2019-03-28 19:02:37 +00:00
fileList.sort((f1, f2) -> {
boolean dir1 = f1.isDirectory();
boolean dir2 = f2.isDirectory();
if (dir1 != dir2)
return dir1 ? -1 : 1;
int res;
if (sortType == 0) { // use name by default
int p = f1.getParent().compareTo(f2.getParent());
if (p == 0) { // same parent, compare names
res = f1.getName().compareTo(f2.getName());
} else { // different parent, sort by that
res = p;
}
2019-03-28 19:02:37 +00:00
} else {
res = Long.compare(f1.lastModified(), f2.lastModified()); // use date if there is a flag
if (sortType == 1)
res = -res; // flip date for newest first instead of oldest first
}
2019-03-28 19:02:37 +00:00
return res;
});
int offset = (page - 1) * perPage;
int limit = Math.min(offset + perPage, fileList.size());
String fullArgs = (String) args.getLocals().get("arguments");
String baseCmd = null;
if (fullArgs != null) {
baseCmd = fullArgs.endsWith(" " + page) ? fullArgs.substring(0, fullArgs.length() - (" " + page).length()) : fullArgs;
}
Message m = new Message(BBC.SCHEMATIC_LIST, page, pageCount);
UUID uuid = playerFolder ? actor.getUniqueId() : null;
for (int i = offset; i < limit; i++) {
m.newline();
File file = fileList.get(i);
eachMsg.run(m, file.toURI(), getPath(dir, file, uuid));
}
if (baseCmd != null) {
m.newline().paginate(baseCmd, page, pageCount);
}
m.send(actor);
}
public static int getFiles(File dir, Actor actor, CommandContext args, @Range(min = 0) int page, int perPage, String formatName, boolean playerFolder, Consumer<File> forEachFile) {
Consumer<File> rootFunction = forEachFile;
int len = args.argsLength();
List<String> filters = new ArrayList<>();
String dirFilter = File.separator;
boolean listMine = false;
boolean listGlobal = !Settings.IMP.PATHS.PER_PLAYER_SCHEMATICS;
if (len > 0) {
int max = len;
if (MathMan.isInteger(args.getString(len - 1))) {
page = args.getInteger(--len);
}
for (int i = 0; i < len; i++) {
String arg = args.getString(i);
switch (arg.toLowerCase()) {
case "me":
case "mine":
case "local":
case "private":
listMine = true;
break;
case "public":
case "global":
listGlobal = true;
break;
case "all":
listMine = true;
listGlobal = true;
break;
default:
if (arg.endsWith("/") || arg.endsWith(File.separator)) {
arg = arg.replace("/", File.separator);
String newDirFilter = dirFilter + arg;
boolean exists = new File(dir, newDirFilter).exists() || playerFolder && MainUtil.resolveRelative(new File(dir, actor.getUniqueId() + newDirFilter)).exists();
if (!exists) {
arg = arg.substring(0, arg.length() - File.separator.length());
if (arg.length() > 3 && arg.length() <= 16) {
UUID fromName = Fawe.imp().getUUID(arg);
if (fromName != null) {
newDirFilter = dirFilter + fromName + File.separator;
listGlobal = true;
}
}
}
dirFilter = newDirFilter;
}
else {
filters.add(arg);
}
break;
}
}
}
if (!listMine && !listGlobal) {
listMine = true;
}
List<File> toFilter = new ArrayList<>();
if (!filters.isEmpty()) {
forEachFile = new DelegateConsumer<File>(forEachFile) {
@Override
public void accept(File file) {
toFilter.add(file);
}
};
}
if (formatName != null) {
final ClipboardFormat cf = ClipboardFormats.findByAlias(formatName);
forEachFile = new DelegateConsumer<File>(forEachFile) {
@Override
public void accept(File file) {
if (cf.isFormat(file)) super.accept(file);
}
};
} else {
forEachFile = new DelegateConsumer<File>(forEachFile) {
@Override
public void accept(File file) {
if (!file.toString().endsWith(".cached")) super.accept(file);
}
};
}
if (playerFolder) {
if (listMine) {
File playerDir = MainUtil.resolveRelative(new File(dir, actor.getUniqueId() + dirFilter));
if (playerDir.exists()) allFiles(playerDir.listFiles(), false, forEachFile);
}
if (listGlobal) {
File rel = MainUtil.resolveRelative(new File(dir, dirFilter));
forEachFile = new DelegateConsumer<File>(forEachFile) {
@Override
public void accept(File f) {
try {
if (f.isDirectory()) {
UUID uuid = UUID.fromString(f.getName());
return;
}
} catch (IllegalArgumentException ignored) {}
super.accept(f);
}
};
if (rel.exists()) allFiles(rel.listFiles(), false, forEachFile);
}
} else {
File rel = MainUtil.resolveRelative(new File(dir, dirFilter));
if (rel.exists()) allFiles(rel.listFiles(), false, forEachFile);
}
if (!filters.isEmpty() && !toFilter.isEmpty()) {
List<File> result = filter(toFilter, filters);
for (File file : result) rootFunction.accept(file);
}
return page;
}
private static List<File> filter(List<File> fileList, List<String> filters) {
String[] normalizedNames = new String[fileList.size()];
for (int i = 0; i < fileList.size(); i++) {
String normalized = fileList.get(i).getName().toLowerCase();
if (normalized.startsWith("../")) normalized = normalized.substring(3);
normalizedNames[i] = normalized.replace("/", File.separator);
}
for (String filter : filters) {
if (fileList.isEmpty()) return fileList;
String lowerFilter = filter.toLowerCase().replace("/", File.separator);
List<File> newList = new ArrayList<>();
for (int i = 0; i < normalizedNames.length; i++) {
if (normalizedNames[i].startsWith(lowerFilter)) newList.add(fileList.get(i));
}
if (newList.isEmpty()) {
for (int i = 0; i < normalizedNames.length; i++) {
if (normalizedNames[i].contains(lowerFilter)) newList.add(fileList.get(i));
}
if (newList.isEmpty()) {
String checkName = filter.replace("\\", "/").split("/")[0];
if (checkName.length() > 3 && checkName.length() <= 16) {
UUID fromName = Fawe.imp().getUUID(checkName);
if (fromName != null) {
lowerFilter = filter.replaceFirst(checkName, fromName.toString()).toLowerCase();
for (int i = 0; i < normalizedNames.length; i++) {
if (normalizedNames[i].startsWith(lowerFilter)) newList.add(fileList.get(i));
}
}
}
}
}
fileList = newList;
}
return fileList;
}
public static void allFiles(File[] files, boolean recursive, Consumer<File> task) {
if (files == null || files.length == 0) return;
for (File f : files) {
if (f.isDirectory()) {
if (recursive) {
allFiles(f.listFiles(), recursive, task);
} else {
task.accept(f);
}
} else {
task.accept(f);
}
}
}
private static String getPath(File root, File file, UUID uuid) {
File dir;
if (uuid != null) {
dir = new File(root, uuid.toString());
} else {
dir = root;
}
ClipboardFormat format = ClipboardFormats.findByFile(file);
URI relative = dir.toURI().relativize(file.toURI());
StringBuilder name = new StringBuilder();
if (relative.isAbsolute()) {
relative = root.toURI().relativize(file.toURI());
name.append(".." + File.separator);
}
name.append(relative.getPath());
return name.toString();
}
}