// $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 . */ import java.util.Map; 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.*; import com.sk89q.worldedit.blocks.*; import com.sk89q.worldedit.data.*; import com.sk89q.worldedit.snapshots.*; import com.sk89q.worldedit.regions.*; /** * Plugin base. * * @author sk89q */ public class WorldEditListener extends PluginListener { /** * Logger. */ private static final Logger logger = Logger.getLogger("Minecraft"); /** * 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 }; /** * WorldEditLibrary's properties file. */ private PropertiesFile properties; /** * 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(); /** * List of allowed blocks. */ private HashSet allowedBlocks; /** * Default maximum number of blocks that can be changed in one operation. */ private int defaultChangeLimit = -1; /** * Shell script save type. */ private String shellSaveType; /** * Snapshot repository used for restoring from backups. This may be null * if snapshot restoration is not configured. */ private SnapshotRepository snapshotRepo; /** * Construct an instance of the plugin. */ public WorldEditListener() { // 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("/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("//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("//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("//drain", "[Radius] - Drain nearby water/lava pools"); commands.put("//limit", "[Num] - See documentation"); commands.put("//expand", " [Num] - Expands the selection"); commands.put("//contract", " [Num] - Contracts the selection"); commands.put("//rotate", "[Angle] - Rotate 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("/forestgen", " - Make an ugly pine tree forest"); 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("/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("//use", "[SnapshotID] - Use a particular snapshot"); commands.put("//restore", "Restore a particular snapshot"); } /** * Gets the WorldEditLibrary session for a player. * * @param player * @return */ public WorldEditSession getSession(WorldEditPlayer player) { if (sessions.containsKey(player)) { return sessions.get(player); } else { WorldEditSession session = new WorldEditSession(); session.setBlockChangeLimit(defaultChangeLimit); sessions.put(player, session); return session; } } /** * 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("_", " "); 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) { throw new UnknownItemException(); } // 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); } return new BaseBlock(blockType.getID(), data); } throw new DisallowedItemException(); } /** * Get a block. * * @param id * @return * @throws UnknownItemException * @throws DisallowedItemException */ public BaseBlock getBlock(String id) throws UnknownItemException, DisallowedItemException { return getBlock(id, false); } /** * 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"); } } } /** * 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(WorldEditPlayer player, WorldEditSession session, EditSession editSession, String[] split) throws WorldEditException { // 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]); Vector pos = player.getBlockTrace(300); if (pos != null) { player.findFreePosition(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; // Set edit position #1 } else if (split[0].equalsIgnoreCase("//pos1")) { checkArgs(split, 0, 0, split[0]); session.setPos1(player.getBlockIn()); player.print("First edit position set."); return true; // Set edit position #2 } else if (split[0].equalsIgnoreCase("//pos2")) { checkArgs(split, 0, 0, split[0]); session.setPos2(player.getBlockIn()); player.print("Second edit position set."); 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); player.print("First edit position set."); } 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); player.print("Second edit position set."); } else { player.printError("No block in sight!"); } return true; // Edit wand } else if (split[0].equalsIgnoreCase("//wand")) { checkArgs(split, 0, 0, split[0]); player.giveItem(271, 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])); session.setBlockChangeLimit(limit); player.print("Block change limit set to " + limit + "."); return true; // Undo } else if (split[0].equalsIgnoreCase("//undo")) { checkArgs(split, 0, 0, split[0]); if (session.undo()) { player.print("Undo successful."); } else { player.printError("Nothing to undo."); } return true; // Redo } else if (split[0].equalsIgnoreCase("//redo")) { checkArgs(split, 0, 0, split[0]); if (session.redo()) { player.print("Redo successful."); } 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")) { Vector pos = session.getPlacementPosition(player); session.getClipboard().paste(editSession, pos, split[0].equalsIgnoreCase("//paste")); player.findFreePosition(); player.print("Pasted. 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")) { checkArgs(split, 2, 3, split[0]); BaseBlock block = getBlock(split[1]); int radius = Math.max(1, Integer.parseInt(split[2])); int depth = split.length > 3 ? Math.max(1, Integer.parseInt(split[3])) : 1; Vector pos = session.getPlacementPosition(player); int affected = editSession.fillXZ((int)pos.getX(), (int)pos.getZ(), pos, block, radius, depth); 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; 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; 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])); int affected = editSession.removeNear( session.getPlacementPosition(player), block.getID(), 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); 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."); } } 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"; 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")) { player.print("# of blocks: " + session.getRegion().getSize()); return true; // Replace all blocks in the region } else if(split[0].equalsIgnoreCase("//set")) { checkArgs(split, 1, 1, split[0]); BaseBlock block = getBlock(split[1]); int affected = editSession.setBlocks(session.getRegion(), block); player.print(affected + " block(s) have been 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])); 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])); int affected = editSession.fixWater( session.getPlacementPosition(player), radius); 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]); int from; BaseBlock to; if (split.length == 2) { from = -1; to = getBlock(split[1], true); } else { from = getBlock(split[1]).getID(); to = getBlock(split[2]); } int affected = editSession.replaceBlocks(session.getRegion(), 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 pine tree forest } else if (split[0].equalsIgnoreCase("/forestgen")) { checkArgs(split, 0, 1, split[0]); int size = split.length > 1 ? Math.max(1, Integer.parseInt(split[1])) : 10; int affected = editSession.makePineTreeForest(player.getPosition(), size); player.print(affected + " pine trees created."); 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; // 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; // 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, 0, split[0]); if (snapshotRepo != null) { Snapshot[] snapshots = snapshotRepo.getSnapshots(); if (snapshots.length > 0) { for (byte i = 0; i < Math.min(5, 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, 0, split[0]); if (snapshotRepo == null) { player.printError("Snapshot/backup restore is not configured."); return true; } Region region = session.getRegion(); Snapshot 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 (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()) { player.printError("No blocks could be restored. (Bad backup?)"); } 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(WorldEditPlayer player, String dir) throws UnknownDirectionException { int xm = 0; int ym = 0; int zm = 0; if (dir.equals("me")) { dir = player.getCardinalDirection(); } if (dir.charAt(0) == 'w') { zm += 1; } else if (dir.charAt(0) == 'e') { zm -= 1; } else if (dir.charAt(0) == 's') { xm += 1; } else if (dir.charAt(0) == 'n') { xm -= 1; } else if (dir.charAt(0) == 'u') { ym += 1; } else if (dir.charAt(0) == 'd') { ym -= 1; } else { throw new UnknownDirectionException(dir); } return new Vector(xm, ym, zm); } /** * Remove a session. * * @param player */ public void removeSession(WorldEditPlayer 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 */ @Override public void onDisconnect(Player player) { removeSession(new WorldEditPlayer(player)); } /** * Called on right click. * * @param modPlayer * @param blockPlaced * @param blockClicked * @param itemInHand * @return false if you want the action to go through */ @Override public boolean onBlockCreate(Player modPlayer, Block blockPlaced, Block blockClicked, int itemInHand) { WorldEditPlayer player = new WorldEditPlayer(modPlayer); if (itemInHand != 271) { return false; } if (!canUseCommand(modPlayer, "//pos2")) { return false; } WorldEditSession session = getSession(player); if (session.isToolControlEnabled()) { Vector cur = Vector.toBlockPoint(blockClicked.getX(), blockClicked.getY(), blockClicked.getZ()); session.setPos2(cur); player.print("Second edit position set."); return true; } return false; } /** * Called on left click. * * @param modPlayer * @param blockClicked * @param itemInHand * @return false if you want the action to go through */ @Override public boolean onBlockDestroy(Player modPlayer, Block blockClicked) { if (!canUseCommand(modPlayer, "//pos1") && !canUseCommand(modPlayer, "//")) { return false; } WorldEditPlayer player = new WorldEditPlayer(modPlayer); WorldEditSession session = getSession(player); if (player.getItemInHand() == 271) { if (session.isToolControlEnabled()) { Vector cur = Vector.toBlockPoint(blockClicked.getX(), blockClicked.getY(), blockClicked.getZ()); // Bug workaround if (cur.getBlockX() == 0 && cur.getBlockY() == 0 && cur.getBlockZ() == 0) { return false; } try { if (session.getPos1().equals(cur)) { return false; } } catch (IncompleteRegionException e) { } session.setPos1(cur); player.print("First edit position set."); return true; } } else if (player.isHoldingPickAxe()) { if (session.hasSuperPickAxe()) { Vector pos = new Vector(blockClicked.getX(), blockClicked.getY(), blockClicked.getZ()); if (ServerInterface.getBlockType(pos) == 7 && !canUseCommand(modPlayer, "/worldeditbedrock")) { return true; } else if (ServerInterface.getBlockType(pos) == 46) { return false; } ServerInterface.setBlockType(pos, 0); return true; } } return false; } /** * * @param ply * @param split * @return whether the command was processed */ @Override public boolean onCommand(Player ply, String[] split) { try { // Legacy /, command if (split[0].equals("/,")) { split[0] = "//"; } if (commands.containsKey(split[0].toLowerCase())) { if (canUseCommand(ply, split[0])) { WorldEditPlayer player = new WorldEditPlayer(ply); WorldEditSession session = getSession(player); EditSession editSession = new EditSession(session.getBlockChangeLimit()); editSession.enableQueue(); try { return performCommand(player, session, editSession, split); } finally { session.remember(editSession); editSession.flushQueue(); } } } return false; } catch (NumberFormatException e) { ply.sendMessage(Colors.Rose + "Number expected; string given."); } catch (IncompleteRegionException e2) { ply.sendMessage(Colors.Rose + "The edit region has not been fully defined."); } catch (UnknownItemException e3) { ply.sendMessage(Colors.Rose + "Block name was not recognized."); } catch (DisallowedItemException e4) { ply.sendMessage(Colors.Rose + "Block not allowed (see WorldEdit configuration)."); } catch (MaxChangedBlocksException e5) { ply.sendMessage(Colors.Rose + "The maximum number of blocks changed (" + e5.getBlockLimit() + ") in an instance was reached."); } catch (UnknownDirectionException ue) { ply.sendMessage(Colors.Rose + "Unknown direction: " + ue.getDirection()); } catch (InsufficientArgumentsException e6) { ply.sendMessage(Colors.Rose + e6.getMessage()); } catch (EmptyClipboardException ec) { ply.sendMessage(Colors.Rose + "Your clipboard is empty."); } catch (WorldEditException e7) { ply.sendMessage(Colors.Rose + e7.getMessage()); } catch (Throwable excp) { ply.sendMessage(Colors.Rose + "Please report this error: [See console]"); ply.sendMessage(excp.getClass().getName() + ": " + excp.getMessage()); excp.printStackTrace(); } return true; } /** * Checks to see if the player can use a command or /worldedit. * * @param player * @param command * @return */ private boolean canUseCommand(Player player, String command) { return player.canUseCommand(command.replace("air", "")) || player.canUseCommand("/worldedit"); } /** * Loads the configuration. */ public void loadConfiguration() { if (properties == null) { properties = new PropertiesFile("worldedit.properties"); } else { properties.load(); } // Get allowed blocks allowedBlocks = new HashSet(); for (String b : properties.getString("allowed-blocks", WorldEditListener.getDefaultAllowedBlocks()).split(",")) { try { allowedBlocks.add(Integer.parseInt(b)); } catch (NumberFormatException e) { } } defaultChangeLimit = Math.max(-1, properties.getInt("max-blocks-changed", -1)); String snapshotsDir = properties.getString("snapshots-dir", ""); if (!snapshotsDir.trim().equals("")) { snapshotRepo = new SnapshotRepository(snapshotsDir); } else { snapshotRepo = null; } String type = properties.getString("shell-save-type", "").trim(); shellSaveType = type.equals("") ? null : type; } /** * Register commands with help. */ public void registerCommands() { for (Map.Entry entry : commands.entrySet()) { etc.getInstance().addCommand(entry.getKey(), entry.getValue()); } } /** * De-register commands. */ public void deregisterCommands() { for (String key : commands.keySet()) { etc.getInstance().removeCommand(key); } } /** * Gets the WorldEditLibrary session for a player. Used for the bridge. * * @param player * @return */ public WorldEditSession _bridgeSession(Player pl) { WorldEditPlayer player = new WorldEditPlayer(pl); if (sessions.containsKey(player)) { return sessions.get(player); } else { WorldEditSession session = new WorldEditSession(); session.setBlockChangeLimit(defaultChangeLimit); sessions.put(player, session); return session; } } }