// $Id$ /* * WorldEdit * Copyright (C) 2010 sk89q * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.sk89q.worldedit; import java.util.List; import java.util.ArrayList; import java.util.HashMap; import java.util.Set; import java.util.HashSet; import java.util.logging.Level; import java.util.logging.Logger; import java.io.*; import com.sk89q.worldedit.bags.BlockBag; import com.sk89q.worldedit.blocks.*; import com.sk89q.worldedit.data.*; import com.sk89q.worldedit.filters.*; import com.sk89q.worldedit.snapshots.*; import com.sk89q.worldedit.regions.*; import com.sk89q.worldedit.patterns.*; /** * Plugin base. * * @author sk89q */ public class WorldEditController { /** * Logger. */ private static final Logger logger = Logger.getLogger("Minecraft.WorldEdit"); /** * Default list of allowed block types. */ private final static Integer[] DEFAULT_ALLOWED_BLOCKS = { 0, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 35, 41, 42, 43, 44, 45, 47, 48, 49, 52, 53, 54, 56, 57, 58, 60, 61, 62, 67, 73, 78, 79, 80, 82, 85, 86, 87, 88, 89, 91 }; /** * Server interface. */ private ServerInterface server; /** * Stores a list of WorldEdit sessions, keyed by players' names. Sessions * persist only for the user's session. On disconnect, the session will be * removed. Sessions are created only when they are needed and those * without any WorldEdit abilities or never use WorldEdit in a session will * not have a session object generated for them. */ private HashMap sessions = new HashMap(); /** * List of commands. These are checked when onCommand() is called, so * the list must know about every command. On plugin load, the commands * will be loaded into help. On unload, they will be removed. */ private HashMap commands = new HashMap(); public boolean profile; public HashSet allowedBlocks; public int defaultChangeLimit = -1; public int maxChangeLimit = -1; public String shellSaveType; public SnapshotRepository snapshotRepo; public int maxRadius = -1; public int maxSuperPickaxeSize = 5; public boolean logComands = false; public boolean registerHelp = true; public int wandItem = 271; public boolean superPickaxeDrop = true; public boolean superPickaxeManyDrop = true; public boolean noDoubleSlash = false; public boolean useInventory = false; public boolean useInventoryOverride = false; /** * Construct an instance of the plugin * * @param server */ public WorldEditController(ServerInterface server) { this.server = server; // Note: Commands should only have the phrase 'air' at the end // for now (see SMWorldEditListener.canUseCommand) commands.put("//pos1", "Set editing position #1"); commands.put("//pos2", "Set editing position #2"); commands.put("//hpos1", "Trace editing position #1"); commands.put("//hpos2", "Trace editing position #2"); commands.put("//chunk", "Select the chunk that you are in"); commands.put("/toggleplace", "Toggle placing at pos #1"); commands.put("//wand", "Gives you the \"edit wand\""); commands.put("/toggleeditwand", "Toggles edit wand selection"); commands.put("//", "Toggles super pick axe."); commands.put("//undo", "Undo"); commands.put("//redo", "Redo"); commands.put("/clearhistory", "Clear history"); commands.put("/clearclipboard", "Clear clipboard"); commands.put("//size", "Get size of selected region"); commands.put("//count", "[BlockIDs] - Count the number of blocks in the region"); commands.put("//distr", "Get the top block distribution"); commands.put("//set", "[ID] - Set all blocks inside region"); commands.put("//outline", "[ID] - Outline the region with blocks"); commands.put("//walls", "[ID] - Build walls"); commands.put("//replace", " [ToID] - Replace all existing blocks inside region"); commands.put("/replacenear", " [ToID] - Replace all existing blocks nearby"); commands.put("//overlay", "[ID] - Overlay the area one layer"); commands.put("/removeabove", " - Remove blocks above head"); commands.put("/removebelow", " - Remove blocks below position"); commands.put("/removenear", " - Remove blocks near you"); commands.put("//copy", "Copies the currently selected region"); commands.put("//cut", "Cuts the currently selected region"); commands.put("//paste", " - Pastes the clipboard"); commands.put("//pasteair", " - Pastes the clipboard (with air)"); commands.put("//move", " - Move the selection"); commands.put("//moveair", " - Move the selection (with air)"); commands.put("//stack", " - Stacks the selection"); commands.put("//stackair", " - Stacks the selection (with air)"); commands.put("//load", "[Filename] - Load .schematic into clipboard"); commands.put("//save", "[Filename] - Save clipboard to .schematic"); commands.put("//fill", "[ID] [Radius] - Fill a hole"); commands.put("//fillr", "[ID] [Radius] - Fill a hole fully recursively"); commands.put("//drain", "[Radius] - Drain nearby water/lava pools"); commands.put("//limit", "[Num] - See documentation"); commands.put("//mode", "[Mode] - Set super pickaxe mode (single/recursive/area)"); commands.put("//tool", "[Tool] - Set pickaxe tool (none/tree/info)"); commands.put("//expand", "[Num] - Expands the selection"); commands.put("//contract", "[Num] - Contracts the selection"); commands.put("//shift", "[Num] - Shift the selection"); commands.put("//rotate", "[Angle] - Rotate the clipboard"); commands.put("//flip", " - Flip the clipboard"); commands.put("//hcyl", "[ID] [Radius] - Create a vertical hollow cylinder"); commands.put("//cyl", "[ID] [Radius] - Create a vertical cylinder"); commands.put("//sphere", "[ID] [Radius] - Create a sphere"); commands.put("//hsphere", "[ID] [Radius] - Create a hollow sphere"); commands.put("/fixwater", "[Radius] - Level nearby pools of water"); commands.put("/fixlava", "[Radius] - Level nearby pools of lava"); commands.put("/ex", "[Size] - Extinguish fires"); commands.put("/forestgen", " - Make Notch tree forest"); commands.put("/pinegen", " - Make an ugly pine tree forest"); commands.put("/snow", " - Simulate snow cover"); commands.put("/pumpkins", " - Make a pumpkin forest"); commands.put("/unstuck", "Go up to the first free spot"); commands.put("/ascend", "Go up one level"); commands.put("/descend", "Go down one level"); commands.put("/jumpto", "Jump to the block that you are looking at"); commands.put("/thru", "Go through the wall that you are looking at"); commands.put("/ceil", " - Get to the ceiling"); commands.put("/up", " - Go up some distance"); commands.put("/chunkinfo", "Get the filename of the chunk that you are in"); commands.put("/listchunks", "Print a list of used chunks"); commands.put("/delchunks", "Generate a shell script to delete chunks"); commands.put("/listsnapshots", " - List the 5 newest snapshots"); commands.put("/butcher", " - Kill nearby mobs"); commands.put("//use", "[SnapshotID] - Use a particular snapshot"); commands.put("//restore", " - Restore a particular snapshot"); commands.put("//smooth", " - Smooth an area's heightmap"); } /** * Gets the WorldEditLibrary session for a player. * * @param player * @return */ public LocalSession getSession(LocalPlayer player) { if (sessions.containsKey(player)) { return sessions.get(player); } else { LocalSession session = new LocalSession(); if (!player.hasPermission("/worldeditnomax") && maxChangeLimit > -1) { if (defaultChangeLimit < 0) { // No infinite! session.setBlockChangeLimit(maxChangeLimit); } else { // Bound session.setBlockChangeLimit( Math.min(defaultChangeLimit, maxChangeLimit)); } } else { session.setBlockChangeLimit(defaultChangeLimit); } session.setUseInventory(useInventory && (!useInventoryOverride || !player.hasPermission("/worldeditunlimited"))); sessions.put(player, session); return session; } } /** * Returns true if the player has a session. * * @param player * @return */ public boolean hasSession(LocalPlayer player) { return sessions.containsKey(player); } /** * Get an item ID from an item name or an item ID number. * * @param arg * @return * @throws UnknownItemException * @throws DisallowedItemException */ public BaseBlock getBlock(String arg, boolean allAllowed) throws UnknownItemException, DisallowedItemException { BlockType blockType; arg = arg.replace("_", " "); arg = arg.replace(";", "|"); String[] args0 = arg.split("\\|"); String[] args1 = args0[0].split(":", 2); String testID = args1[0]; int data; try { data = args1.length > 1 ? Integer.parseInt(args1[1]) : 0; if (data > 15 || data < 0) { data = 0; } } catch (NumberFormatException e) { data = 0; } try { blockType = BlockType.fromID(Integer.parseInt(testID)); } catch (NumberFormatException e) { blockType = BlockType.lookup(testID); if (blockType == null) { int t = server.resolveItem(testID); if (t > 0 && t < 256) { blockType = BlockType.fromID(t); } } } if (blockType == null) { throw new UnknownItemException(arg); } // Check if the item is allowed if (allAllowed || allowedBlocks.isEmpty() || allowedBlocks.contains(blockType.getID())) { if (blockType == BlockType.SIGN_POST || blockType == BlockType.WALL_SIGN) { String[] text = new String[4]; text[0] = args0.length > 1 ? args0[1] : ""; text[1] = args0.length > 2 ? args0[2] : ""; text[2] = args0.length > 3 ? args0[3] : ""; text[3] = args0.length > 4 ? args0[4] : ""; return new SignBlock(blockType.getID(), data, text); } else if (blockType == BlockType.MOB_SPAWNER) { if (args0.length > 1) { if (!server.isValidMobType(args0[1])) { throw new InvalidItemException(arg, "Unknown mob type '" + args0[1] + "'"); } return new MobSpawnerBlock(data, args0[1]); } else { return new MobSpawnerBlock(data, "Pig"); } } return new BaseBlock(blockType.getID(), data); } throw new DisallowedItemException(arg); } /** * Get a block. * * @param id * @return * @throws UnknownItemException * @throws DisallowedItemException */ public BaseBlock getBlock(String id) throws UnknownItemException, DisallowedItemException { return getBlock(id, false); } /** * Get a list of blocks as a set. This returns a Pattern. * * @param list * @return pattern */ public Pattern getBlockPattern(String list) throws UnknownItemException, DisallowedItemException { String[] items = list.split(","); if (items.length == 1) { return new SingleBlockPattern(getBlock(items[0])); } List blockChances = new ArrayList(); for (String s : items) { BaseBlock block; double chance; if (s.matches("[0-9]+(?:\\.(?:[0-9]+)?)?%.*")) { String[] p = s.split("%"); chance = Double.parseDouble(p[0]); block = getBlock(p[1]); } else { chance = 1; block = getBlock(s); } blockChances.add(new BlockChance(block, chance)); } return new RandomFillPattern(blockChances); } /** * Get a list of blocks as a set. * * @param list * @params allBlocksAllowed * @return set */ public Set getBlockIDs(String list, boolean allBlocksAllowed) throws UnknownItemException, DisallowedItemException { String[] items = list.split(","); Set blocks = new HashSet(); for (String s : items) { blocks.add(getBlock(s, allBlocksAllowed).getID()); } return blocks; } /** * Checks to make sure that there are enough but not too many arguments. * * @param args * @param min * @param max -1 for no maximum * @param cmd command name * @throws InsufficientArgumentsException */ private void checkArgs(String[] args, int min, int max, String cmd) throws InsufficientArgumentsException { if (args.length <= min || (max != -1 && args.length - 1 > max)) { if (commands.containsKey(cmd)) { throw new InsufficientArgumentsException(cmd + " usage: " + commands.get(cmd)); } else { throw new InsufficientArgumentsException("Invalid number of arguments"); } } } /** * Checks to see if the specified radius is within bounds. * * @param radius * @throws MaxRadiusException */ private void checkMaxRadius(int radius) throws MaxRadiusException { if (maxRadius > 0 && radius > maxRadius) { throw new MaxRadiusException(); } } /** * The main meat of command processing. * * @param player * @param editPlayer * @param session * @param editSession * @param split * @return * @throws UnknownItemException * @throws IncompleteRegionException * @throws InsufficientArgumentsException * @throws DisallowedItemException */ public boolean performCommand(LocalPlayer player, LocalSession session, EditSession editSession, String[] split) throws WorldEditException { if (logComands) { logger.log(Level.INFO, "WorldEdit: " + player.getName() + ": " + joinString(split, " ")); } // Jump to the first free position if (split[0].equalsIgnoreCase("/unstuck")) { checkArgs(split, 0, 0, split[0]); player.print("There you go!"); player.findFreePosition(); return true; // Ascend a level } else if(split[0].equalsIgnoreCase("/ascend")) { checkArgs(split, 0, 0, split[0]); if (player.ascendLevel()) { player.print("Ascended a level."); } else { player.printError("No free spot above you found."); } return true; // Descend a level } else if(split[0].equalsIgnoreCase("/descend")) { checkArgs(split, 0, 0, split[0]); if (player.descendLevel()) { player.print("Descended a level."); } else { player.printError("No free spot below you found."); } return true; // Jump to the block in sight } else if (split[0].equalsIgnoreCase("/jumpto")) { checkArgs(split, 0, 0, split[0]); WorldVector pos = player.getSolidBlockTrace(300); if (pos != null) { player.findFreePosition(pos.getWorld(), pos); player.print("Poof!"); } else { player.printError("No block in sight!"); } return true; // Go through a wall } else if (split[0].equalsIgnoreCase("/thru")) { checkArgs(split, 0, 0, split[0]); if (player.passThroughForwardWall(6)) { player.print("Whoosh!"); } else { player.printError("No free spot ahead of you found."); } return true; // Go to the ceiling } else if (split[0].equalsIgnoreCase("/ceil")) { checkArgs(split, 0, 1, split[0]); int clearence = split.length > 1 ? Math.max(0, Integer.parseInt(split[1])) : 0; if (player.ascendToCeiling(clearence)) { player.print("Whoosh!"); } else { player.printError("No free spot above you found."); } return true; // Go up } else if (split[0].equalsIgnoreCase("/up")) { checkArgs(split, 1, 1, split[0]); int distance = Integer.parseInt(split[1]); if (player.ascendUpwards(distance)) { player.print("Whoosh!"); } else { player.printError("You would hit something above you."); } return true; // Set edit position #1 } else if (split[0].equalsIgnoreCase("//pos1")) { checkArgs(split, 0, 0, split[0]); session.setPos1(player.getBlockIn()); if (session.isRegionDefined()) { player.print("First position set to " + player.getBlockIn() + " (" + session.getRegion().getSize() + ")."); } else { player.print("First position set to " + player.getBlockIn() + "."); } return true; // Set edit position #2 } else if (split[0].equalsIgnoreCase("//pos2")) { checkArgs(split, 0, 0, split[0]); session.setPos2(player.getBlockIn()); if (session.isRegionDefined()) { player.print("Second position set to " + player.getBlockIn() + " (" + session.getRegion().getSize() + ")."); } else { player.print("Second position set to " + player.getBlockIn() + "."); } return true; // Trace edit position #1 } else if (split[0].equalsIgnoreCase("//hpos1")) { checkArgs(split, 0, 0, split[0]); Vector pos = player.getBlockTrace(300); if (pos != null) { session.setPos1(pos); if (session.isRegionDefined()) { player.print("First position set to " + pos + " (" + session.getRegion().getSize() + ")."); } else { player.print("First position set to " + pos.toString() + " ."); } } else { player.printError("No block in sight!"); } return true; // Trace edit position #2 } else if (split[0].equalsIgnoreCase("//hpos2")) { checkArgs(split, 0, 0, split[0]); Vector pos = player.getBlockTrace(300); if (pos != null) { session.setPos2(pos); if (session.isRegionDefined()) { player.print("Second position set to " + pos + " (" + session.getRegion().getSize() + ")."); } else { player.print("Second position set to " + pos.toString() + " ."); } } else { player.printError("No block in sight!"); } return true; // Select the chunk } else if(split[0].equalsIgnoreCase("//chunk")) { checkArgs(split, 0, 0, split[0]); Vector2D min2D = ChunkStore.toChunk(player.getBlockIn()); Vector min = new Vector(min2D.getBlockX() * 16, 0, min2D.getBlockZ() * 16); Vector max = min.add(15, 127, 15); session.setPos1(min); session.setPos2(max); player.print("Chunk selected: " + min2D.getBlockX() + ", " + min2D.getBlockZ()); return true; // Edit wand } else if (split[0].equalsIgnoreCase("//wand")) { checkArgs(split, 0, 0, split[0]); player.giveItem(wandItem, 1); player.print("Left click: select pos #1; Right click: select pos #2"); return true; // Toggle placing at pos #1 } else if (split[0].equalsIgnoreCase("/toggleplace")) { checkArgs(split, 0, 0, split[0]); if (session.togglePlacementPosition()) { player.print("Now placing at pos #1."); } else { player.print("Now placing at the block you stand in."); } return true; // Toggle edit wand } else if (split[0].equalsIgnoreCase("/toggleeditwand")) { checkArgs(split, 0, 0, split[0]); session.setToolControl(!session.isToolControlEnabled()); if (session.isToolControlEnabled()) { player.print("Edit wand enabled."); } else { player.print("Edit wand disabled."); } return true; // Toggle super pick axe } else if (split[0].equalsIgnoreCase("//")) { checkArgs(split, 0, 0, split[0]); if (session.toggleSuperPickAxe()) { player.print("Super pick axe enabled."); } else { player.print("Super pick axe disabled."); } return true; // Set max number of blocks to change at a time } else if (split[0].equalsIgnoreCase("//limit")) { checkArgs(split, 1, 1, split[0]); int limit = Math.max(-1, Integer.parseInt(split[1])); if (!player.hasPermission("/worldeditnomax") && maxChangeLimit > -1) { if (limit > maxChangeLimit) { player.printError("Your maximum allowable limit is " + maxChangeLimit + "."); return true; } } session.setBlockChangeLimit(limit); player.print("Block change limit set to " + limit + "."); return true; // Set super pick axe mode } else if (split[0].equalsIgnoreCase("//mode")) { checkArgs(split, 1, 2, split[0]); if (split[1].equalsIgnoreCase("single")) { session.setSuperPickaxeMode(LocalSession.SuperPickaxeMode.SINGLE); player.print("Mode set to single block."); } else if (split[1].equalsIgnoreCase("recursive") || split[1].equalsIgnoreCase("area")) { if (split.length == 3) { int size = Math.max(1, Integer.parseInt(split[2])); if (size <= maxSuperPickaxeSize) { LocalSession.SuperPickaxeMode mode = split[1].equalsIgnoreCase("recursive") ? LocalSession.SuperPickaxeMode.SAME_TYPE_RECURSIVE : LocalSession.SuperPickaxeMode.SAME_TYPE_AREA; session.setSuperPickaxeMode(mode); session.setSuperPickaxeRange(size); player.print("Mode set to " + split[1].toLowerCase() + "."); } else { player.printError("Max size is " + maxSuperPickaxeSize + "."); } } else { player.printError("Size argument required for mode " + split[1].toLowerCase() + "."); } } else { player.printError("Unknown super pick axe mode."); } return true; // Set tool } else if (split[0].equalsIgnoreCase("//tool")) { checkArgs(split, 1, 1, split[0]); if (split[1].equalsIgnoreCase("none")) { session.setTool(LocalSession.Tool.NONE); player.print("No tool equipped. -3 XP, +10 Manliness"); } else if (split[1].equalsIgnoreCase("tree")) { if (!canUseCommand(player, "/treetool")) { player.printError("You do not have the /treetool permission."); return true; } session.setTool(LocalSession.Tool.TREE); player.print("Tree planting tool equipped. +5 XP"); } else if (split[1].equalsIgnoreCase("info")) { if (!canUseCommand(player, "/infotool")) { player.printError("You do not have the /infotool permission."); return true; } session.setTool(LocalSession.Tool.INFO); player.print("Block information tool equipped."); } else { player.printError("Unknown tool."); } return true; // Undo } else if (split[0].equalsIgnoreCase("//undo")) { checkArgs(split, 0, 0, split[0]); EditSession undone = session.undo(session.getBlockBag(player)); if (undone != null) { player.print("Undo successful."); flushBlockBag(player, undone); } else { player.printError("Nothing to undo."); } return true; // Redo } else if (split[0].equalsIgnoreCase("//redo")) { checkArgs(split, 0, 0, split[0]); EditSession redone = session.redo(session.getBlockBag(player)); if (redone != null) { player.print("Redo successful."); flushBlockBag(player, redone); } else { player.printError("Nothing to redo."); } return true; // Clear undo history } else if (split[0].equalsIgnoreCase("/clearhistory")) { checkArgs(split, 0, 0, split[0]); session.clearHistory(); player.print("History cleared."); return true; // Clear clipboard } else if (split[0].equalsIgnoreCase("/clearclipboard")) { checkArgs(split, 0, 0, split[0]); session.setClipboard(null); player.print("Clipboard cleared."); return true; // Paste } else if (split[0].equalsIgnoreCase("//pasteair") || split[0].equalsIgnoreCase("//paste")) { checkArgs(split, 0, 1, split[0]); boolean atOrigin = split.length > 1 ? (split[1].equalsIgnoreCase("true") || split[1].equalsIgnoreCase("yes")) : false; if (atOrigin) { Vector pos = session.getClipboard().getOrigin(); session.getClipboard().place(editSession, pos, split[0].equalsIgnoreCase("//paste")); player.findFreePosition(); player.print("Pasted to copy origin. Undo with //undo"); } else { Vector pos = session.getPlacementPosition(player); session.getClipboard().paste(editSession, pos, split[0].equalsIgnoreCase("//paste")); player.findFreePosition(); player.print("Pasted relative to you. Undo with //undo"); } return true; // Draw a hollow cylinder } else if (split[0].equalsIgnoreCase("//hcyl") || split[0].equalsIgnoreCase("//cyl")) { checkArgs(split, 2, 3, split[0]); BaseBlock block = getBlock(split[1]); int radius = Math.max(1, Integer.parseInt(split[2])); int height = split.length > 3 ? Integer.parseInt(split[3]) : 1; boolean filled = split[0].equalsIgnoreCase("//cyl"); Vector pos = session.getPlacementPosition(player); int affected; if (filled) { affected = editSession.makeCylinder(pos, block, radius, height); } else { affected = editSession.makeHollowCylinder(pos, block, radius, height); } player.print(affected + " block(s) have been created."); return true; // Draw a sphere } else if (split[0].equalsIgnoreCase("//sphere") || split[0].equalsIgnoreCase("//hsphere")) { checkArgs(split, 2, 3, split[0]); BaseBlock block = getBlock(split[1]); int radius = Math.max(1, Integer.parseInt(split[2])); boolean raised = split.length > 3 ? (split[3].equalsIgnoreCase("true") || split[3].equalsIgnoreCase("yes")) : false; boolean filled = split[0].equalsIgnoreCase("//sphere"); Vector pos = session.getPlacementPosition(player); if (raised) { pos = pos.add(0, radius, 0); } int affected = editSession.makeSphere(pos, block, radius, filled); player.findFreePosition(); player.print(affected + " block(s) have been created."); return true; // Fill a hole } else if (split[0].equalsIgnoreCase("//fill") || split[0].equalsIgnoreCase("//fillr")) { boolean recursive = split[0].equalsIgnoreCase("//fillr"); checkArgs(split, 2, recursive ? 2 : 3, split[0]); Pattern pattern = getBlockPattern(split[1]); int radius = Math.max(1, Integer.parseInt(split[2])); checkMaxRadius(radius); int depth = split.length > 3 ? Math.max(1, Integer.parseInt(split[3])) : 1; Vector pos = session.getPlacementPosition(player); int affected = 0; if (pattern instanceof SingleBlockPattern) { affected = editSession.fillXZ(pos, ((SingleBlockPattern)pattern).getBlock(), radius, depth, recursive); } else { affected = editSession.fillXZ(pos, pattern, radius, depth, recursive); } player.print(affected + " block(s) have been created."); return true; // Remove blocks above current position } else if (split[0].equalsIgnoreCase("/removeabove")) { checkArgs(split, 0, 2, split[0]); int size = split.length > 1 ? Math.max(1, Integer.parseInt(split[1])) : 1; checkMaxRadius(size); int height = split.length > 2 ? Math.min(128, Integer.parseInt(split[2]) + 2) : 128; int affected = editSession.removeAbove( session.getPlacementPosition(player), size, height); player.print(affected + " block(s) have been removed."); return true; // Remove blocks below current position } else if (split[0].equalsIgnoreCase("/removebelow")) { checkArgs(split, 0, 2, split[0]); int size = split.length > 1 ? Math.max(1, Integer.parseInt(split[1])) : 1; checkMaxRadius(size); int height = split.length > 2 ? Math.max(1, Integer.parseInt(split[2])) : 128; int affected = editSession.removeBelow( session.getPlacementPosition(player), size, height); player.print(affected + " block(s) have been removed."); return true; // Remove blocks near } else if (split[0].equalsIgnoreCase("/removenear")) { checkArgs(split, 2, 2, split[0]); BaseBlock block = getBlock(split[1], true); int size = Math.max(1, Integer.parseInt(split[2])); checkMaxRadius(size); int affected = editSession.removeNear( session.getPlacementPosition(player), block.getID(), size); player.print(affected + " block(s) have been removed."); return true; // Extinguish } else if (split[0].equalsIgnoreCase("/ex")) { checkArgs(split, 0, 1, split[0]); int defaultRadius = maxRadius != -1 ? Math.min(40, maxRadius) : 40; int size = split.length > 1 ? Math.max(1, Integer.parseInt(split[1])) : defaultRadius; checkMaxRadius(size); int affected = editSession.removeNear( session.getPlacementPosition(player), 51, size); player.print(affected + " block(s) have been removed."); return true; // Load .schematic to clipboard } else if (split[0].equalsIgnoreCase("//load")) { checkArgs(split, 1, 1, split[0]); String filename = split[1].replace("\0", "") + ".schematic"; File dir = new File("schematics"); File f = new File("schematics", filename); if (!filename.matches("^[A-Za-z0-9_\\- \\./\\\\'\\$@~!%\\^\\*\\(\\)\\[\\]\\+\\{\\},\\?]+$")) { player.printError("Valid characters: A-Z, a-z, 0-9, spaces, " + "./\'$@~!%^*()[]+{},?"); return true; } try { String filePath = f.getCanonicalPath(); String dirPath = dir.getCanonicalPath(); if (!filePath.substring(0, dirPath.length()).equals(dirPath)) { player.printError("Schematic could not read or it does not exist."); } else { session.setClipboard(CuboidClipboard.loadSchematic(filePath)); logger.log(Level.INFO, player.getName() + " loaded " + filePath); player.print(filename + " loaded. Paste it with //paste"); } } catch (DataException e) { player.printError("Load error: " + e.getMessage()); } catch (IOException e) { player.printError("Schematic could not read or it does not exist: " + e.getMessage()); } return true; // Save clipboard to .schematic } else if (split[0].equalsIgnoreCase("//save")) { checkArgs(split, 1, 1, split[0]); String filename = split[1].replace("\0", "") + ".schematic"; if (!filename.matches("^[A-Za-z0-9_\\- \\./\\\\'\\$@~!%\\^\\*\\(\\)\\[\\]\\+\\{\\},\\?]+$")) { player.printError("Valid characters: A-Z, a-z, 0-9, spaces, " + "./\'$@~!%^*()[]+{},?"); return true; } File dir = new File("schematics"); File f = new File("schematics", filename); if (!dir.exists()) { if (!dir.mkdir()) { player.printError("A schematics/ folder could not be created."); return true; } } try { String filePath = f.getCanonicalPath(); String dirPath = dir.getCanonicalPath(); if (!filePath.substring(0, dirPath.length()).equals(dirPath)) { player.printError("Invalid path for Schematic."); } else { // Create parent directories File parent = f.getParentFile(); if (parent != null && !parent.exists()) { parent.mkdirs(); } session.getClipboard().saveSchematic(filePath); logger.log(Level.INFO, player.getName() + " saved " + filePath); player.print(filename + " saved."); } } catch (DataException se) { player.printError("Save error: " + se.getMessage()); } catch (IOException e) { player.printError("Schematic could not written: " + e.getMessage()); } return true; // Get size } else if (split[0].equalsIgnoreCase("//size")) { Region region = session.getRegion(); Vector size = region.getMaximumPoint() .subtract(region.getMinimumPoint()) .add(1, 1, 1); player.print("First position: " + session.getPos1()); player.print("Second position: " + session.getPos2()); player.print("Size: " + size); player.print("# of blocks: " + region.getSize()); return true; // Get count } else if (split[0].equalsIgnoreCase("//count")) { checkArgs(split, 1, 1, split[0]); Set searchIDs = getBlockIDs(split[1], true); player.print("Counted: " + editSession.countBlocks(session.getRegion(), searchIDs)); return true; // Get block distribution } else if (split[0].equalsIgnoreCase("//distr")) { checkArgs(split, 0, 0, split[0]); List> distribution = editSession.getBlockDistribution(session.getRegion()); if (distribution.size() > 0) { // *Should* always be true int size = session.getRegion().getSize(); player.print("# total blocks: " + size); for (Countable c : distribution) { player.print(String.format("%-7s (%.3f%%) %s #%d", String.valueOf(c.getAmount()), c.getAmount() / (double)size * 100, BlockType.fromID(c.getID()).getName(), c.getID())); } } else { player.printError("No blocks counted."); } return true; // Replace all blocks in the region } else if(split[0].equalsIgnoreCase("//set")) { checkArgs(split, 1, 1, split[0]); Pattern pattern = getBlockPattern(split[1]); int affected; if (pattern instanceof SingleBlockPattern) { affected = editSession.setBlocks(session.getRegion(), ((SingleBlockPattern)pattern).getBlock()); } else { affected = editSession.setBlocks(session.getRegion(), pattern); } player.print(affected + " block(s) have been changed."); return true; // Smooth the heightmap of a region } else if (split[0].equalsIgnoreCase("//smooth")) { checkArgs(split, 0, 1, split[0]); int iterations = 1; if (split.length >= 2) iterations = Integer.parseInt(split[1]); HeightMap heightMap = new HeightMap(editSession, session.getRegion()); HeightMapFilter filter = new HeightMapFilter(new GaussianKernel(5, 1.0)); int affected = heightMap.applyFilter(filter, iterations); player.print("Terrain's height map smoothed. " + affected + " block(s) changed."); return true; // Set the outline of a region } else if(split[0].equalsIgnoreCase("//outline")) { checkArgs(split, 1, 1, split[0]); BaseBlock block = getBlock(split[1]); int affected = editSession.makeCuboidFaces(session.getRegion(), block); player.print(affected + " block(s) have been changed."); return true; // Set the walls of a region } else if(split[0].equalsIgnoreCase("//walls")) { checkArgs(split, 1, 1, split[0]); BaseBlock block = getBlock(split[1]); int affected = editSession.makeCuboidWalls(session.getRegion(), block); player.print(affected + " block(s) have been changed."); return true; // Drain pools } else if(split[0].equalsIgnoreCase("//drain")) { checkArgs(split, 1, 1, split[0]); int radius = Math.max(0, Integer.parseInt(split[1])); checkMaxRadius(radius); int affected = editSession.drainArea( session.getPlacementPosition(player), radius); player.print(affected + " block(s) have been changed."); return true; // Fix water } else if(split[0].equalsIgnoreCase("/fixwater")) { checkArgs(split, 1, 1, split[0]); int radius = Math.max(0, Integer.parseInt(split[1])); checkMaxRadius(radius); int affected = editSession.fixLiquid( session.getPlacementPosition(player), radius, 8, 9); player.print(affected + " block(s) have been changed."); return true; // Fix lava } else if(split[0].equalsIgnoreCase("/fixlava")) { checkArgs(split, 1, 1, split[0]); int radius = Math.max(0, Integer.parseInt(split[1])); checkMaxRadius(radius); int affected = editSession.fixLiquid( session.getPlacementPosition(player), radius, 10, 11); player.print(affected + " block(s) have been changed."); return true; // Replace all blocks in the region } else if(split[0].equalsIgnoreCase("//replace")) { checkArgs(split, 1, 2, split[0]); Set from; Pattern to; if (split.length == 2) { from = null; to = getBlockPattern(split[1]); } else { from = getBlockIDs(split[1], true); to = getBlockPattern(split[2]); } int affected = 0; if (to instanceof SingleBlockPattern) { affected = editSession.replaceBlocks(session.getRegion(), from, ((SingleBlockPattern)to).getBlock()); } else { affected = editSession.replaceBlocks(session.getRegion(), from, to); } player.print(affected + " block(s) have been replaced."); return true; // Replace all blocks in the region } else if(split[0].equalsIgnoreCase("/replacenear")) { checkArgs(split, 2, 3, split[0]); int size = Math.max(1, Integer.parseInt(split[1])); Set from; BaseBlock to; if (split.length == 3) { from = null; to = getBlock(split[2]); } else { from = getBlockIDs(split[2], true); to = getBlock(split[3]); } Vector min = player.getBlockIn().subtract(size, size, size); Vector max = player.getBlockIn().add(size, size, size); Region region = new CuboidRegion(min, max); int affected = editSession.replaceBlocks(region, from, to); player.print(affected + " block(s) have been replaced."); return true; // Lay blocks over an area } else if (split[0].equalsIgnoreCase("//overlay")) { checkArgs(split, 1, 1, split[0]); BaseBlock block = getBlock(split[1]); Region region = session.getRegion(); int affected = editSession.overlayCuboidBlocks(region, block); player.print(affected + " block(s) have been overlayed."); return true; // Copy } else if (split[0].equalsIgnoreCase("//copy") || split[0].equalsIgnoreCase("//cut")) { boolean cut = split[0].equalsIgnoreCase("//cut"); BaseBlock block = new BaseBlock(0); if (cut) { checkArgs(split, 0, 1, split[0]); if (split.length > 1) { getBlock(split[1]); } } else { checkArgs(split, 0, 0, split[0]); } Region region = session.getRegion(); Vector min = region.getMinimumPoint(); Vector max = region.getMaximumPoint(); Vector pos = player.getBlockIn(); CuboidClipboard clipboard = new CuboidClipboard( max.subtract(min).add(new Vector(1, 1, 1)), min, min.subtract(pos)); clipboard.copy(editSession); session.setClipboard(clipboard); if (cut) { editSession.setBlocks(session.getRegion(), block); player.print("Block(s) cut."); } else { player.print("Block(s) copied."); } return true; // Make tree forest } else if (split[0].equalsIgnoreCase("/forestgen")) { checkArgs(split, 0, 2, split[0]); int size = split.length > 1 ? Math.max(1, Integer.parseInt(split[1])) : 10; double density = split.length > 2 ? Double.parseDouble(split[2]) / 100 : 0.05; int affected = editSession.makeForest(player.getPosition(), size, density, false); player.print(affected + " trees created."); return true; // Make pine tree forest } else if (split[0].equalsIgnoreCase("/pinegen")) { checkArgs(split, 0, 1, split[0]); int size = split.length > 1 ? Math.max(1, Integer.parseInt(split[1])) : 10; double density = split.length > 2 ? Double.parseDouble(split[2]) / 100 : 0.05; int affected = editSession.makeForest(player.getPosition(), size, density, true); player.print(affected + " pine trees created."); return true; // Let it snow~ } else if (split[0].equalsIgnoreCase("/snow")) { checkArgs(split, 0, 1, split[0]); int size = split.length > 1 ? Math.max(1, Integer.parseInt(split[1])) : 10; int affected = editSession.simulateSnow(player.getBlockIn(), size); player.print(affected + " surfaces covered. Let it snow~"); return true; // Make pumpkin patches } else if (split[0].equalsIgnoreCase("/pumpkins")) { checkArgs(split, 0, 1, split[0]); int size = split.length > 1 ? Math.max(1, Integer.parseInt(split[1])) : 10; int affected = editSession.makePumpkinPatches(player.getPosition(), size); player.print(affected + " pumpkin patches created."); return true; // Move } else if (split[0].equalsIgnoreCase("//moveair") || split[0].equalsIgnoreCase("//move")) { checkArgs(split, 0, 3, split[0]); int count = split.length > 1 ? Math.max(1, Integer.parseInt(split[1])) : 1; Vector dir = getDirection(player, split.length > 2 ? split[2].toLowerCase() : "me"); BaseBlock replace; // Replacement block argument if (split.length > 3) { replace = getBlock(split[3]); } else { replace = new BaseBlock(0); } boolean copyAir = split[0].equalsIgnoreCase("//moveair"); int affected = editSession.moveCuboidRegion(session.getRegion(), dir, count, copyAir, replace); player.print(affected + " blocks moved."); return true; // Stack } else if (split[0].equalsIgnoreCase("//stackair") || split[0].equalsIgnoreCase("//stack")) { checkArgs(split, 0, 2, split[0]); int count = split.length > 1 ? Math.max(1, Integer.parseInt(split[1])) : 1; Vector dir = getDirection(player, split.length > 2 ? split[2].toLowerCase() : "me"); boolean copyAir = split[0].equalsIgnoreCase("//stackair"); int affected = editSession.stackCuboidRegion(session.getRegion(), dir, count, copyAir); player.print(affected + " blocks changed. Undo with //undo"); return true; // Expand } else if (split[0].equalsIgnoreCase("//expand")) { checkArgs(split, 1, 2, split[0]); Vector dir; int change = Integer.parseInt(split[1]); if (split.length == 3) { dir = getDirection(player, split[2].toLowerCase()); } else { dir = getDirection(player, "me"); } Region region = session.getRegion(); int oldSize = region.getSize(); region.expand(dir.multiply(change)); session.learnRegionChanges(); int newSize = region.getSize(); player.print("Region expanded " + (newSize - oldSize) + " blocks."); return true; // Contract } else if (split[0].equalsIgnoreCase("//contract")) { checkArgs(split, 1, 2, split[0]); Vector dir; int change = Integer.parseInt(split[1]); if (split.length == 3) { dir = getDirection(player, split[2].toLowerCase()); } else { dir = getDirection(player, "me"); } Region region = session.getRegion(); int oldSize = region.getSize(); region.contract(dir.multiply(change)); session.learnRegionChanges(); int newSize = region.getSize(); player.print("Region contracted " + (oldSize - newSize) + " blocks."); return true; // Shift } else if (split[0].equalsIgnoreCase("//shift")) { checkArgs(split, 1, 2, split[0]); Vector dir; int change = Integer.parseInt(split[1]); if (split.length == 3) { dir = getDirection(player, split[2].toLowerCase()); } else { dir = getDirection(player, "me"); } Region region = session.getRegion(); region.expand(dir.multiply(change)); region.contract(dir.multiply(change)); session.learnRegionChanges(); player.print("Region shifted."); return true; // Rotate } else if (split[0].equalsIgnoreCase("//rotate")) { checkArgs(split, 1, 1, split[0]); int angle = Integer.parseInt(split[1]); if (angle % 90 == 0) { CuboidClipboard clipboard = session.getClipboard(); clipboard.rotate2D(angle); player.print("Clipboard rotated by " + angle + " degrees."); } else { player.printError("Angles must be divisible by 90 degrees."); } return true; // Flip } else if (split[0].equalsIgnoreCase("//flip")) { checkArgs(split, 0, 1, split[0]); CuboidClipboard.FlipDirection dir = getFlipDirection(player, split.length > 1 ? split[1].toLowerCase() : "me"); CuboidClipboard clipboard = session.getClipboard(); clipboard.flip(dir); player.print("Clipboard flipped."); return true; // Kill mobs } else if (split[0].equalsIgnoreCase("/butcher")) { checkArgs(split, 0, 1, split[0]); int radius = split.length > 1 ? Math.max(1, Integer.parseInt(split[1])) : -1; Vector origin = session.getPlacementPosition(player); int killed = server.killMobs(player.getWorld(), origin, radius); player.print("Killed " + killed + " mobs."); return true; // Get chunk filename } else if (split[0].equalsIgnoreCase("/chunkinfo")) { checkArgs(split, 0, 0, split[0]); Vector pos = player.getBlockIn(); int chunkX = (int)Math.floor(pos.getBlockX() / 16.0); int chunkZ = (int)Math.floor(pos.getBlockZ() / 16.0); String folder1 = Integer.toString(divisorMod(chunkX, 64), 36); String folder2 = Integer.toString(divisorMod(chunkZ, 64), 36); String filename = "c." + Integer.toString(chunkX, 36) + "." + Integer.toString(chunkZ, 36) + ".dat"; player.print("Chunk: " + chunkX + ", " + chunkZ); player.print(folder1 + "/" + folder2 + "/" + filename); return true; // Dump a list of involved chunks } else if (split[0].equalsIgnoreCase("/listchunks")) { checkArgs(split, 0, 0, split[0]); Set chunks = session.getRegion().getChunks(); for (Vector2D chunk : chunks) { player.print(NestedFileChunkStore.getFilename(chunk)); } return true; // Dump a list of involved chunks } else if (split[0].equalsIgnoreCase("/delchunks")) { checkArgs(split, 0, 0, split[0]); Set chunks = session.getRegion().getChunks(); FileOutputStream out = null; if (shellSaveType == null) { player.printError("shell-save-type has to be configured in worldedit.properties"); } else if (shellSaveType.equalsIgnoreCase("bat")) { try { out = new FileOutputStream("worldedit-delchunks.bat"); OutputStreamWriter writer = new OutputStreamWriter(out, "UTF-8"); writer.write("@ECHO off\r\n"); writer.write("ECHO This batch file was generated by WorldEdit.\r\n"); writer.write("ECHO It contains a list of chunks that were in the selected region\r\n"); writer.write("ECHO at the time that the /delchunks command was used. Run this file\r\n"); writer.write("ECHO in order to delete the chunk files listed in this file.\r\n"); writer.write("ECHO.\r\n"); writer.write("PAUSE\r\n"); for (Vector2D chunk : chunks) { String filename = NestedFileChunkStore.getFilename(chunk); writer.write("ECHO " + filename + "\r\n"); writer.write("DEL \"world/" + filename + "\"\r\n"); } writer.write("ECHO Complete.\r\n"); writer.write("PAUSE\r\n"); writer.close(); player.print("worldedit-delchunks.bat written. Run it when no one is near the region."); } catch (IOException e) { player.printError("Error occurred: " + e.getMessage()); } finally { if (out != null) { try { out.close(); } catch (IOException ie) {} } } } else if (shellSaveType.equalsIgnoreCase("bash")) { try { out = new FileOutputStream("worldedit-delchunks.sh"); OutputStreamWriter writer = new OutputStreamWriter(out, "UTF-8"); writer.write("#!/bin/bash\n"); writer.write("echo This shell file was generated by WorldEdit.\n"); writer.write("echo It contains a list of chunks that were in the selected region\n"); writer.write("echo at the time that the /delchunks command was used. Run this file\n"); writer.write("echo in order to delete the chunk files listed in this file.\n"); writer.write("echo\n"); writer.write("read -p \"Press any key to continue...\"\n"); for (Vector2D chunk : chunks) { String filename = NestedFileChunkStore.getFilename(chunk); writer.write("echo " + filename + "\n"); writer.write("rm \"world/" + filename + "\"\n"); } writer.write("echo Complete.\n"); writer.write("read -p \"Press any key to continue...\"\n"); writer.close(); player.print("worldedit-delchunks.sh written. Run it when no one is near the region."); player.print("You will have to chmod it to be executable."); } catch (IOException e) { player.printError("Error occurred: " + e.getMessage()); } finally { if (out != null) { try { out.close(); } catch (IOException ie) {} } } } else { player.printError("Unknown shell script save type. 'bat' or 'bash' expected."); } return true; // List snapshots } else if (split[0].equalsIgnoreCase("/listsnapshots")) { checkArgs(split, 0, 1, split[0]); int num = split.length > 1 ? Math.min(40, Math.max(5, Integer.parseInt(split[1]))) : 5; if (snapshotRepo != null) { Snapshot[] snapshots = snapshotRepo.getSnapshots(); if (snapshots.length > 0) { for (byte i = 0; i < Math.min(num, snapshots.length); i++) { player.print((i + 1) + ". " + snapshots[i].getName()); } player.print("Use //use [snapshot] or //use latest to set the snapshot."); } else { player.printError("No snapshots are available."); } } else { player.printError("Snapshot/backup restore is not configured."); } return true; // Use a certain snapshot } else if (split[0].equalsIgnoreCase("//use")) { checkArgs(split, 1, 1, split[0]); if (snapshotRepo == null) { player.printError("Snapshot/backup restore is not configured."); return true; } String name = split[1]; // Want the latest snapshot? if (name.equalsIgnoreCase("latest")) { Snapshot snapshot = snapshotRepo.getDefaultSnapshot(); if (snapshot != null) { session.setSnapshot(null); player.print("Now using newest snapshot."); } else { player.printError("No snapshots were found."); } } else { try { session.setSnapshot(snapshotRepo.getSnapshot(name)); player.print("Snapshot set to: " + name); } catch (InvalidSnapshotException e) { player.printError("That snapshot does not exist or is not available."); } } return true; // Restore } else if (split[0].equalsIgnoreCase("//restore")) { checkArgs(split, 0, 1, split[0]); if (snapshotRepo == null) { player.printError("Snapshot/backup restore is not configured."); return true; } Region region = session.getRegion(); Snapshot snapshot; if (split.length > 1) { try { snapshot = snapshotRepo.getSnapshot(split[1]); } catch (InvalidSnapshotException e) { player.printError("That snapshot does not exist or is not available."); return true; } } else { snapshot = session.getSnapshot(); } ChunkStore chunkStore = null; // No snapshot set? if (snapshot == null) { snapshot = snapshotRepo.getDefaultSnapshot(); if (snapshot == null) { player.printError("No snapshots were found."); return true; } } // Load chunk store try { chunkStore = snapshot.getChunkStore(); player.print("Snapshot '" + snapshot.getName() + "' loaded; now restoring..."); } catch (DataException e) { player.printError("Failed to load snapshot: " + e.getMessage()); return true; } catch (IOException e) { player.printError("Failed to load snapshot: " + e.getMessage()); return true; } try { // Restore snapshot SnapshotRestore restore = new SnapshotRestore(chunkStore, region); //player.print(restore.getChunksAffected() + " chunk(s) will be loaded."); restore.restore(editSession); if (restore.hadTotalFailure()) { String error = restore.getLastErrorMessage(); if (error != null) { player.printError("Errors prevented any blocks from being restored."); player.printError("Last error: " + error); } else { player.printError("No chunks could be loaded. (Bad archive?)"); } } else { player.print(String.format("Restored; %d " + "missing chunks and %d other errors.", restore.getMissingChunks().size(), restore.getErrorChunks().size())); } } finally { try { chunkStore.close(); } catch (IOException e) { } } return true; } return false; } /** * Modulus, divisor-style. * * @param a * @param n * @return */ private static int divisorMod(int a, int n) { return (int)(a - n * Math.floor(Math.floor(a) / (double)n)); } /** * Get the direction vector for a player's direction. May return * null if a direction could not be found. * * @param player * @param dir * @return */ public Vector getDirection(LocalPlayer player, String dirStr) throws UnknownDirectionException { int xm = 0; int ym = 0; int zm = 0; LocalPlayer.DIRECTION dir = null; if (dirStr.equals("me")) { dir = player.getCardinalDirection(); } if (dirStr.charAt(0) == 'u' || dir == LocalPlayer.DIRECTION.WEST) { zm += 1; } else if (dirStr.charAt(0) == 'e' || dir == LocalPlayer.DIRECTION.EAST) { zm -= 1; } else if (dirStr.charAt(0) == 's' || dir == LocalPlayer.DIRECTION.SOUTH) { xm += 1; } else if (dirStr.charAt(0) == 'n' || dir == LocalPlayer.DIRECTION.NORTH) { xm -= 1; } else if (dirStr.charAt(0) == 'u') { ym += 1; } else if (dirStr.charAt(0) == 'd') { ym -= 1; } else { throw new UnknownDirectionException(dirStr); } return new Vector(xm, ym, zm); } /** * Get the flip direction for a player's direction. May return * null if a direction could not be found. * * @param player * @param dir * @return */ public CuboidClipboard.FlipDirection getFlipDirection( LocalPlayer player, String dirStr) throws UnknownDirectionException { LocalPlayer.DIRECTION dir = null; if (dirStr.equals("me")) { dir = player.getCardinalDirection(); } if (dirStr.charAt(0) == 'w' || dir == LocalPlayer.DIRECTION.EAST) { return CuboidClipboard.FlipDirection.WEST_EAST; } else if (dirStr.charAt(0) == 'e' || dir == LocalPlayer.DIRECTION.EAST) { return CuboidClipboard.FlipDirection.WEST_EAST; } else if (dirStr.charAt(0) == 's' || dir == LocalPlayer.DIRECTION.SOUTH) { return CuboidClipboard.FlipDirection.NORTH_SOUTH; } else if (dirStr.charAt(0) == 'n' || dir == LocalPlayer.DIRECTION.SOUTH) { return CuboidClipboard.FlipDirection.NORTH_SOUTH; } else if (dirStr.charAt(0) == 'u') { return CuboidClipboard.FlipDirection.UP_DOWN; } else if (dirStr.charAt(0) == 'd') { return CuboidClipboard.FlipDirection.UP_DOWN; } else { throw new UnknownDirectionException(dirStr); } } /** * Remove a session. * * @param player */ public void removeSession(LocalPlayer player) { sessions.remove(player); } /** * Remove all sessions. */ public void clearSessions() { sessions.clear(); } /** * Get a comma-delimited list of the default allowed blocks. * * @return comma-delimited list */ public static String getDefaultAllowedBlocks() { StringBuilder b = new StringBuilder(); for (Integer id : DEFAULT_ALLOWED_BLOCKS) { b.append(id).append(","); } return b.substring(0, b.length() - 1); } /** * * @param player */ public void handleDisconnect(LocalPlayer player) { removeSession(player); } /** * Called on arm swing. * * @param player */ public void handleArmSwing(LocalPlayer player) { if (!canUseCommand(player, "//")) return; } /** * Called on right click. * * @param player * @param clicked * @return false if you want the action to go through */ @SuppressWarnings("deprecation") public boolean handleBlockRightClick(LocalPlayer player, Vector clicked) { int itemInHand = player.getItemInHand(); // This prevents needless sessions from being created if (!hasSession(player) && !(itemInHand == wandItem && canUseCommand(player, "//pos2"))) { return false; } LocalSession session = getSession(player); if (itemInHand == wandItem && session.isToolControlEnabled() && canUseCommand(player, "//pos2")) { session.setPos2(clicked); try { player.print("Second position set to " + clicked + " (" + session.getRegion().getSize() + ")."); } catch (IncompleteRegionException e) { player.print("Second position set to " + clicked + "."); } return true; } else if (player.isHoldingPickAxe() && session.getTool() == LocalSession.Tool.TREE) { EditSession editSession = new EditSession(server, player.getWorld(), session.getBlockChangeLimit()); try { if (!server.generateTree(editSession, player.getWorld(), clicked)) { player.printError("Notch won't let you put a tree there."); } } finally { session.remember(editSession); } return true; } else if (player.isHoldingPickAxe() && session.getTool() == LocalSession.Tool.INFO) { BaseBlock block = (new EditSession(server, player.getWorld(), 0)).rawGetBlock(clicked); player.print("\u00A79@" + clicked + ": " + "\u00A7e" + "Type: " + block.getID() + "\u00A77" + " (" + BlockType.fromID(block.getID()).getName() + ") " + "\u00A7f" + "[" + block.getData() + "]"); if (block instanceof MobSpawnerBlock) { player.printRaw("\u00A7e" + "Mob Type: " + ((MobSpawnerBlock)block).getMobType()); } return true; } return false; } /** * Called on left click. * * @param player * @param clicked * @return false if you want the action to go through */ public boolean handleBlockLeftClick(LocalPlayer player, Vector clicked) { if (!canUseCommand(player, "//pos1") && !canUseCommand(player, "//")) { return false; } LocalSession session = getSession(player); if (player.getItemInHand() == wandItem) { if (session.isToolControlEnabled()) { // Bug workaround if (clicked.getBlockX() == 0 && clicked.getBlockY() == 0 && clicked.getBlockZ() == 0) { return false; } try { if (session.getPos1().equals(clicked)) { return false; } } catch (IncompleteRegionException e) { } session.setPos1(clicked); try { player.print("First position set to " + clicked + " (" + session.getRegion().getSize() + ")."); } catch (IncompleteRegionException e) { player.print("First position set to " + clicked + "."); } return true; } } else if (player.isHoldingPickAxe()) { if (session.hasSuperPickAxe()) { boolean canBedrock = canUseCommand(player, "/worldeditbedrock"); LocalWorld world = player.getWorld(); // Single block super pickaxe if (session.getSuperPickaxeMode() == LocalSession.SuperPickaxeMode.SINGLE) { if (server.getBlockType(world, clicked) == 7 && !canBedrock) { return true; } else if (server.getBlockType(world, clicked) == 46) { return false; } if (superPickaxeDrop) { server.simulateBlockMine(world, clicked); } else { server.setBlockType(world, clicked, 0); } // Area super pickaxe } else if (session.getSuperPickaxeMode() == LocalSession.SuperPickaxeMode.SAME_TYPE_AREA) { int ox = clicked.getBlockX(); int oy = clicked.getBlockY(); int oz = clicked.getBlockZ(); int size = session.getSuperPickaxeRange(); int initialType = server.getBlockType(world, clicked); if (initialType == 7 && !canBedrock) { return true; } for (int x = ox - size; x <= ox + size; x++) { for (int y = oy - size; y <= oy + size; y++) { for (int z = oz - size; z <= oz + size; z++) { Vector pos = new Vector(x, y, z); if (server.getBlockType(world, pos) == initialType) { if (superPickaxeManyDrop) { server.simulateBlockMine(world, pos); } else { server.setBlockType(world, pos, 0); } } } } } return true; // Area super pickaxe } else if (session.getSuperPickaxeMode() == LocalSession.SuperPickaxeMode.SAME_TYPE_RECURSIVE) { int size = session.getSuperPickaxeRange(); int initialType = server.getBlockType(world, clicked); if (initialType == 7 && !canBedrock) { return true; } recursiveSuperPickaxe(world, clicked.toBlockVector(), clicked, size, initialType, new HashSet()); return true; } return true; } } return false; } /** * Helper method for the recursive super pickaxe. * * @param pos * @param canBedrock * @return */ private void recursiveSuperPickaxe(LocalWorld world, BlockVector pos, Vector origin, int size, int initialType, Set visited) { if (origin.distance(pos) > size || visited.contains(pos)) { return; } visited.add(pos); if (server.getBlockType(world, pos) == initialType) { if (superPickaxeManyDrop) { server.simulateBlockMine(world, pos); } else { server.setBlockType(world, pos, 0); } } else { return; } recursiveSuperPickaxe(world, pos.add(1, 0, 0).toBlockVector(), origin, size, initialType, visited); recursiveSuperPickaxe(world, pos.add(-1, 0, 0).toBlockVector(), origin, size, initialType, visited); recursiveSuperPickaxe(world, pos.add(0, 0, 1).toBlockVector(), origin, size, initialType, visited); recursiveSuperPickaxe(world, pos.add(0, 0, -1).toBlockVector(), origin, size, initialType, visited); recursiveSuperPickaxe(world, pos.add(0, 1, 0).toBlockVector(), origin, size, initialType, visited); recursiveSuperPickaxe(world, pos.add(0, -1, 0).toBlockVector(), origin, size, initialType, visited); } /** * * @param player * @param split * @return whether the command was processed */ public boolean handleCommand(LocalPlayer player, String[] split) { try { // Legacy /, command if (split[0].equals("/,")) { split[0] = "//"; } String searchCmd = split[0].toLowerCase(); if (commands.containsKey(searchCmd) || (noDoubleSlash && commands.containsKey("/" + searchCmd)) || ((searchCmd.length() < 3 || searchCmd.charAt(2) != '/') && commands.containsKey(searchCmd.substring(1)))) { if (noDoubleSlash && commands.containsKey("/" + searchCmd)) { split[0] = "/" + split[0]; } else if (commands.containsKey(searchCmd.substring(1))) { split[0] = split[0].substring(1); } if (canUseCommand(player, split[0])) { LocalSession session = getSession(player); BlockBag blockBag = session.getBlockBag(player); EditSession editSession = new EditSession(server, player.getWorld(), session.getBlockChangeLimit(), blockBag); editSession.enableQueue(); long start = System.currentTimeMillis(); try { return performCommand(player, session, editSession, split); } finally { session.remember(editSession); editSession.flushQueue(); if (profile) { long time = System.currentTimeMillis() - start; player.print(( time / 1000.0) + "s elapsed"); } flushBlockBag(player, editSession); } } } return false; } catch (NumberFormatException e) { player.printError("Number expected; string given."); } catch (IncompleteRegionException e2) { player.printError("The edit region has not been fully defined."); } catch (UnknownItemException e3) { player.printError("Block name '" + e3.getID() + "' was not recognized."); } catch (InvalidItemException e4) { player.printError(e4.getMessage()); } catch (DisallowedItemException e4) { player.printError("Block '" + e4.getID() + "' not allowed (see WorldEdit configuration)."); } catch (MaxChangedBlocksException e5) { player.printError("Max blocks changed in an operation reached (" + e5.getBlockLimit() + ")."); } catch (MaxRadiusException e) { player.printError("Maximum radius: " + maxRadius); } catch (UnknownDirectionException ue) { player.printError("Unknown direction: " + ue.getDirection()); } catch (InsufficientArgumentsException e6) { player.printError(e6.getMessage()); } catch (EmptyClipboardException ec) { player.printError("Your clipboard is empty."); } catch (WorldEditException e7) { player.printError(e7.getMessage()); } catch (Throwable excp) { player.printError("Please report this error: [See console]"); player.printRaw(excp.getClass().getName() + ": " + excp.getMessage()); excp.printStackTrace(); } return true; } /** * Flush a block bag's changes to a player. * * @param player * @param blockBag * @param editSession */ private static void flushBlockBag(LocalPlayer player, EditSession editSession) { BlockBag blockBag = editSession.getBlockBag(); if (blockBag != null) { blockBag.flushChanges(); } Set missingBlocks = editSession.popMissingBlocks(); if (missingBlocks.size() > 0) { StringBuilder str = new StringBuilder(); str.append("Missing these blocks: "); int size = missingBlocks.size(); int i = 0; for (Integer id : missingBlocks) { BlockType type = BlockType.fromID(id); str.append(type != null ? type.getName() + " (" + id + ")" : id.toString()); i++; if (i != size) { str.append(", "); } } player.printError(str.toString()); } } /** * Checks to see if the player can use a command or /worldedit. * * @param player * @param command * @return */ private boolean canUseCommand(LocalPlayer player, String command) { // Allow the /worldeditselect permission if (command.equalsIgnoreCase("//pos1") || command.equalsIgnoreCase("//pos2") || command.equalsIgnoreCase("//hpos1") || command.equalsIgnoreCase("//hpos2")) { return player.hasPermission(command) || player.hasPermission("/worldeditselect") || player.hasPermission("/worldedit"); } return player.hasPermission(command.replace("air", "")) || player.hasPermission("/worldedit"); } /** * Joins a string from an array of strings. * * @param str * @param delimiter * @return */ private static String joinString(String[] str, String delimiter) { if (str.length == 0) { return ""; } StringBuilder buffer = new StringBuilder(str[0]); for (int i = 1; i < str.length; i++) { buffer.append(delimiter).append(str[i]); } return buffer.toString(); } /** * @return the commands */ public HashMap getCommands() { return commands; } /** * Gets the WorldEditLibrary session for a player. Used for the bridge. * * @param player * @return */ public LocalSession getBridgeSession(LocalPlayer player) { if (sessions.containsKey(player)) { return sessions.get(player); } else { LocalSession session = new LocalSession(); session.setBlockChangeLimit(defaultChangeLimit); sessions.put(player, session); return session; } } }