@@ -3,12 +3,8 @@
import com.sk89q.craftbook.*;
import com.sk89q.craftbook.bukkit.MechanismsPlugin;
import com.sk89q.worldedit.BlockWorldVector;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.blocks.BlockID;
import com.sk89q.worldedit.bukkit.BukkitUtil;
import com.sk89q.worldedit.bukkit.BukkitWorld;
import com.sk89q.worldedit.bukkit.WorldEditPlugin;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.block.Block;
import org.bukkit.block.BlockState;
@@ -30,219 +26,202 @@

public class Area extends AbstractMechanic {

public static class Factory extends AbstractMechanicFactory<Area> {

public Factory(MechanismsPlugin plugin) {

this.plugin = plugin;
}

private final MechanismsPlugin plugin;

/**
* Detect the mechanic at a placed sign.
*
* @throws ProcessedMechanismException
*/
@Override
public Area detect(BlockWorldVector pt, LocalPlayer player, Sign sign)
throws InvalidMechanismException, ProcessedMechanismException {

if (!plugin.getLocalConfiguration().areaSettings.enable)
return null;
if (sign.getLine(1).equalsIgnoreCase("[Area]") || sign.getLine(1).equalsIgnoreCase("[SaveArea]")) {
if (!player.hasPermission("craftbook.mech.area")) {
throw new InsufficientPermissionsException();
}
if (sign.getLine(0).trim().equalsIgnoreCase("")) sign.setLine(0, "~" + player.getName());
if (sign.getLine(1).equalsIgnoreCase("[Area]")) sign.setLine(1, "[Area]");
else sign.setLine(1, "[SaveArea]");
player.print("Toggle area created.");
} else {
return null;
}

throw new ProcessedMechanismException();
}

/**
* Explore around the trigger to find a Door; throw if things look funny.
*
* @param pt the trigger (should be a signpost)
*
* @return a Area if we could make a valid one, or null if this looked
* nothing like a area.
*
* @throws InvalidMechanismException if the area looked like it was intended to be a area, but
* it failed.
*/
@Override
public Area detect(BlockWorldVector pt) throws InvalidMechanismException {

if (!plugin.getLocalConfiguration().areaSettings.enableRedstone)
return null;

Block block = BukkitUtil.toWorld(pt).getBlockAt(
BukkitUtil.toLocation(pt));
if (block.getTypeId() == BlockID.SIGN_POST || block.getTypeId() == BlockID.WALL_SIGN) {
BlockState state = block.getState();
if (state instanceof Sign) {
Sign sign = (Sign) state;
if (sign.getLine(1).equalsIgnoreCase("[Area]") || sign.getLine(1).equalsIgnoreCase("[SaveArea]")) {
if (!sign.getLine(0).equalsIgnoreCase(""))
sign.setLine(0, "global");
return new Area(pt, plugin);
}
}
}
return null;
}
}

public final MechanismsPlugin plugin;

public final BlockWorldVector pt;

private final WorldEditPlugin worldEdit;
public static class Factory extends AbstractMechanicFactory<Area> {

public Factory(MechanismsPlugin plugin) {

this.plugin = plugin;
}

private final MechanismsPlugin plugin;

/**
* Detect the mechanic at a placed sign.
*
* @throws ProcessedMechanismException
*/
@Override
public Area detect(BlockWorldVector pt, LocalPlayer player, Sign sign)
throws InvalidMechanismException, ProcessedMechanismException {

if (!plugin.getLocalConfiguration().areaSettings.enable)
return null;
if (sign.getLine(1).equalsIgnoreCase("[Area]") || sign.getLine(1).equalsIgnoreCase("[SaveArea]")) {
if (!player.hasPermission("craftbook.mech.area")) {
throw new InsufficientPermissionsException();
}
if (sign.getLine(0).trim().equalsIgnoreCase("")) sign.setLine(0, "~" + player.getName());
if (sign.getLine(1).equalsIgnoreCase("[Area]")) sign.setLine(1, "[Area]");
else sign.setLine(1, "[SaveArea]");
player.print("Toggle area created.");
} else {
return null;
}

throw new ProcessedMechanismException();
}

/**
* Explore around the trigger to find a Door; throw if things look funny.
*
* @param pt the trigger (should be a signpost)
* @return a Area if we could make a valid one, or null if this looked
* nothing like a area.
* @throws InvalidMechanismException if the area looked like it was intended to be a area, but
* it failed.
*/
@Override
public Area detect(BlockWorldVector pt) throws InvalidMechanismException {

if (!plugin.getLocalConfiguration().areaSettings.enableRedstone)
return null;

Block block = BukkitUtil.toWorld(pt).getBlockAt(
BukkitUtil.toLocation(pt));
if (block.getTypeId() == BlockID.SIGN_POST || block.getTypeId() == BlockID.WALL_SIGN) {
BlockState state = block.getState();
if (state instanceof Sign) {
Sign sign = (Sign) state;
if (sign.getLine(1).equalsIgnoreCase("[Area]") || sign.getLine(1).equalsIgnoreCase("[SaveArea]")) {
if (!sign.getLine(0).equalsIgnoreCase(""))
sign.setLine(0, "global");
return new Area(pt, plugin);
}
}
}
return null;
}
}

public final MechanismsPlugin plugin;

public final BlockWorldVector pt;


/**
* Raised when a block is right clicked.
*
* @param event
*/
@Override
public void onRightClick(PlayerInteractEvent event) {

if (!event.getPlayer().hasPermission("craftbook.mech.area.use")) {
event.getPlayer().sendMessage(ChatColor.RED + "You don't have permission to use areas.");
return;
}
try {
Sign s = null;
if (BukkitUtil.toBlock(pt).getState() instanceof Sign)
s = ((Sign) BukkitUtil.toBlock(pt).getState());
if (s == null) return;
boolean save = s.getLine(1).equalsIgnoreCase("[SaveArea]");
String namespace = s.getLine(0);
String id = s.getLine(2);
String inactiveID = s.getLine(3);

if (id == null || id.equalsIgnoreCase("") || id.length() < 1) return;
if (namespace == null || namespace.equalsIgnoreCase("") || namespace.length() < 1) return;
if (event.getPlayer().getWorld() == null) return;

CuboidCopy copy = plugin.copyManager.load(event.getPlayer().getWorld(), namespace, id, plugin);
if (!copy.shouldClear(event.getPlayer().getWorld())) {
if (save)
plugin.copyManager.save(event.getPlayer().getWorld(), namespace, inactiveID, copy, plugin);
copy.paste(event.getPlayer().getWorld());
} else {
if (inactiveID.length() == 0) {
if (save)
plugin.copyManager.save(event.getPlayer().getWorld(), namespace, id, copy, plugin);
copy.clear(event.getPlayer().getWorld());
} else {
if (save)
plugin.copyManager.save(event.getPlayer().getWorld(), namespace, id, copy, plugin);
copy = plugin.copyManager.load(event.getPlayer().getWorld(), namespace, inactiveID, plugin);
copy.paste(event.getPlayer().getWorld());
}
}
} catch (Exception e) {
final Writer result = new StringWriter();
final PrintWriter printWriter = new PrintWriter(result);
e.printStackTrace(printWriter);
plugin.getLogger().log(Level.SEVERE, "Failed to toggle Area: " + result.toString());
}

event.setCancelled(true);
}

/**
* Raised when an input redstone current changes.
*
* @param event
*/
@Override
public void onBlockRedstoneChange(SourcedBlockRedstoneEvent event) {

if (!plugin.getLocalConfiguration().areaSettings.enableRedstone)
return;
try {
Sign s = null;
if (BukkitUtil.toBlock(pt).getState() instanceof Sign)
s = ((Sign) BukkitUtil.toBlock(pt).getState());
if (s == null) return;
String namespace = s.getLine(0);
String id = s.getLine(2);

CuboidCopy copy = plugin.copyManager.load(BukkitUtil.toWorld(pt.getWorld()), namespace, id, plugin);

if (!copy.shouldClear(BukkitUtil.toWorld(pt.getWorld()))) {
copy.paste(BukkitUtil.toWorld(pt.getWorld()));
} else {
String inactiveID = s.getLine(3);

if (inactiveID.length() == 0) {
copy.clear(BukkitUtil.toWorld(pt.getWorld()));
} else {
copy = plugin.copyManager.load(BukkitUtil.toWorld(pt.getWorld()), namespace, inactiveID, plugin);
copy.paste(BukkitUtil.toWorld(pt.getWorld()));
}
}
} catch (Exception e) {
final Writer result = new StringWriter();
final PrintWriter printWriter = new PrintWriter(result);
e.printStackTrace(printWriter);
plugin.getLogger().log(Level.SEVERE, "Failed to toggle Area: " + result.toString());
}
}

/**
* @param pt if you didn't already check if this is a signpost with appropriate
* text, you're going on Santa's naughty list.
* @param plugin
*
* @throws InvalidMechanismException
*/
private Area(BlockWorldVector pt, MechanismsPlugin plugin) throws InvalidMechanismException {

super();
this.plugin = plugin;
this.pt = pt;
this.worldEdit = (WorldEditPlugin) Bukkit.getPluginManager().getPlugin("WorldEdit");
}

@Override
public void unload() {

}

@Override
public boolean isActive() {

return false;
}

@Override
public void onBlockBreak(BlockBreakEvent event) {

}

@Override
public void unloadWithEvent(ChunkUnloadEvent event) {

/**
* Raised when a block is right clicked.
*
* @param event
*/
@Override
public void onRightClick(PlayerInteractEvent event) {

if (!event.getPlayer().hasPermission("craftbook.mech.area.use")) {
event.getPlayer().sendMessage(ChatColor.RED + "You don't have permission to use areas.");
return;
}
try {
EditSession editSession = worldEdit.createEditSession(event.getPlayer());
Sign s = null;
if (BukkitUtil.toBlock(pt).getState() instanceof Sign)
s = ((Sign) BukkitUtil.toBlock(pt).getState());
if (s == null) return;
boolean save = s.getLine(1).equalsIgnoreCase("[SaveArea]");
String namespace = s.getLine(0);
String id = s.getLine(2);
String inactiveID = s.getLine(3);

if (id == null || id.equalsIgnoreCase("") || id.length() < 1) return;
if (namespace == null || namespace.equalsIgnoreCase("") || namespace.length() < 1) return;
if (event.getPlayer().getWorld() == null) return;

CuboidCopy copy = plugin.copyManager.load(event.getPlayer().getWorld(), namespace, id, plugin);
copy.toggle(editSession);
/*
if (!copy.shouldClear()) {
if (save)
plugin.copyManager.save(event.getPlayer().getWorld(), namespace, inactiveID, copy, plugin);
copy.paste(editSession);
} else {
if (inactiveID.length() == 0) {
if (save)
plugin.copyManager.save(event.getPlayer().getWorld(), namespace, id, copy, plugin);
copy.clear(event.getPlayer().getWorld());
} else {
if (save)
plugin.copyManager.save(event.getPlayer().getWorld(), namespace, id, copy, plugin);
copy = plugin.copyManager.load(event.getPlayer().getWorld(), namespace, inactiveID, plugin);
copy.paste(editSession);
}
}
*/
} catch (Exception e) {
final Writer result = new StringWriter();
final PrintWriter printWriter = new PrintWriter(result);
e.printStackTrace(printWriter);
plugin.getLogger().log(Level.SEVERE, "Failed to toggle Area: " + result.toString());
}

event.setCancelled(true);
}

/**
* Raised when an input redstone current changes.
*
* @param event
*/
@Override
public void onBlockRedstoneChange(SourcedBlockRedstoneEvent event) {

if (!plugin.getLocalConfiguration().areaSettings.enableRedstone)
return;
try {


Sign s = null;
if (BukkitUtil.toBlock(pt).getState() instanceof Sign) {
s = ((Sign) BukkitUtil.toBlock(pt).getState());
}
if (s == null) return;
String namespace = s.getLine(0);
String id = s.getLine(2);

EditSession editSession = new EditSession(
new BukkitWorld(s.getWorld()),
plugin.getLocalConfiguration().areaSettings.maxSizePerArea
);
CuboidCopy copy = plugin.copyManager.load(BukkitUtil.toWorld(pt.getWorld()), namespace, id, plugin);

if (!copy.shouldClear()) {
copy.paste(editSession);
} else {
String inactiveID = s.getLine(3);

if (inactiveID.length() == 0) {
copy.clear(BukkitUtil.toWorld(pt.getWorld()));
} else {
copy = plugin.copyManager.load(BukkitUtil.toWorld(pt.getWorld()), namespace, inactiveID, plugin);
copy.paste(editSession);
}
}
} catch (Exception e) {
final Writer result = new StringWriter();
final PrintWriter printWriter = new PrintWriter(result);
e.printStackTrace(printWriter);
plugin.getLogger().log(Level.SEVERE, "Failed to toggle Area: " + result.toString());
}
}

@Override
public void unload() {

}

@Override
public boolean isActive() {

return false;
}

@Override
public void onBlockBreak(BlockBreakEvent event) {

}

@Override
public void unloadWithEvent(ChunkUnloadEvent event) {

}
}
}
}
@@ -20,7 +20,6 @@

import com.sk89q.craftbook.bukkit.MechanismsPlugin;
import com.sk89q.craftbook.util.HistoryHashMap;
import com.sk89q.worldedit.data.DataException;
import org.bukkit.World;

import java.io.File;
@@ -35,177 +34,167 @@
*/
public class CopyManager {

/**
* Cache.
*/
private final HashMap<String, HistoryHashMap<String, CuboidCopy>> cache =
new HashMap<String, HistoryHashMap<String, CuboidCopy>>();

/**
* Remembers missing copies so as to not look for them on disk.
*/
private final HashMap<String, HistoryHashMap<String, Long>> missing =
new HashMap<String, HistoryHashMap<String, Long>>();

/**
* Checks to see whether a name is a valid copy name.
*
* @param name
*
* @return
*/
public static boolean isValidName(String name) {

return name.length() > 0 && name.length() <= 30
&& name.matches("^[A-Za-z0-9_\\- ]+$");
}

/**
* Checks to see whether a name is a valid namespace.
*
* @param name
*
* @return
*/
public static boolean isValidNamespace(String name) {

return name.length() > 0 && name.length() <= 15
&& name.matches("^[A-Za-z0-9_]+$");
}

/**
* Load a copy from disk. This may return a cached copy. If the copy is not
* cached, the file will be loaded from disk if possible. If the copy does
* not exist, an exception will be raised. An exception may be raised if the file
* exists but cannot be read for whatever reason.
*
* @param namespace
* @param id
*
* @return
*
* @throws IOException
* @throws MissingCuboidCopyException
* @throws CuboidCopyException
*/
public CuboidCopy load(World world, String namespace, String id, MechanismsPlugin plugin)
throws IOException, CuboidCopyException {

id = id.toLowerCase();
String cacheKey = namespace + "/" + id;

HistoryHashMap<String, Long> missing = getMissing(world.getUID().toString());

if (missing.containsKey(cacheKey)) {
long lastCheck = missing.get(cacheKey);
if (lastCheck > System.currentTimeMillis()) {
throw new MissingCuboidCopyException(id);
}
}

HistoryHashMap<String, CuboidCopy> cache = getCache(world.getUID().toString());

CuboidCopy copy = cache.get(id);

if (copy == null) {
try {
File folder = new File(new File(plugin.getDataFolder(), "areas"), namespace);
copy = CuboidCopy.load(new File(folder, id + ".schematic"));
missing.remove(cacheKey);
cache.put(cacheKey, copy);
return copy;
} catch (FileNotFoundException e) {
missing.put(cacheKey, System.currentTimeMillis() + 10000);
throw new MissingCuboidCopyException(id);
} catch (IOException e) {
missing.put(cacheKey, System.currentTimeMillis() + 10000);
throw e;
} catch (DataException e) {
missing.put(cacheKey, System.currentTimeMillis() + 10000);
throw new MissingCuboidCopyException(id);
}
}

return copy;
}

/**
* Save a copy to disk. The copy will be cached.
*
* @param id
* @param copy
*
* @throws IOException
*/
public void save(World world, String namespace, String id, CuboidCopy copy, MechanismsPlugin plugin)
throws IOException, DataException {

HistoryHashMap<String, CuboidCopy> cache = getCache(world.getUID().toString());

File folder = new File(new File(plugin.getDataFolder(), "areas"), namespace);

if (!folder.exists()) folder.mkdirs();

id = id.toLowerCase();

String cacheKey = namespace + "/" + id;

copy.save(new File(folder, id + ".schematic"));
missing.remove(cacheKey);
cache.put(id, copy);
}

/**
* Gets whether a copy can be made.
*
* @param namespace
* @param ignore
*
* @return -1 if the copy can be made, some other number for the count
*/
public int meetsQuota(World world, String namespace, String ignore, int quota, MechanismsPlugin plugin) {

String ignoreFilename = ignore + ".schematic";

String[] files = new File(new File(plugin.getDataFolder(), "areas"), namespace).list();

if (files == null) {
return quota > 0 ? -1 : 0;
} else if (ignore == null) {
return files.length < quota ? -1 : files.length;
} else {
int count = 0;

for (String f : files) {
if (f.equals(ignoreFilename)) {
return -1;
}

count++;
}

return count < quota ? -1 : count;
}
}

private HistoryHashMap<String, CuboidCopy> getCache(String world) {

if (cache.containsKey(world)) return cache.get(world);
else {
HistoryHashMap<String, CuboidCopy> h = new HistoryHashMap<String, CuboidCopy>(10);
cache.put(world, h);
return h;
}
}

private HistoryHashMap<String, Long> getMissing(String world) {

if (missing.containsKey(world)) {
return missing.get(world);
} else {
HistoryHashMap<String, Long> h = new HistoryHashMap<String, Long>(10);
missing.put(world, h);
return h;
}
}
/**
* Cache.
*/
private final HashMap<String, HistoryHashMap<String, CuboidCopy>> cache =
new HashMap<String, HistoryHashMap<String, CuboidCopy>>();

/**
* Remembers missing copies so as to not look for them on disk.
*/
private final HashMap<String, HistoryHashMap<String, Long>> missing =
new HashMap<String, HistoryHashMap<String, Long>>();

/**
* Checks to see whether a name is a valid copy name.
*
* @param name
* @return
*/
public static boolean isValidName(String name) {

return name.length() > 0 && name.length() <= 30
&& name.matches("^[A-Za-z0-9_\\- ]+$");
}

/**
* Checks to see whether a name is a valid namespace.
*
* @param name
* @return
*/
public static boolean isValidNamespace(String name) {

return name.length() > 0 && name.length() <= 15
&& name.matches("^[A-Za-z0-9_]+$");
}

/**
* Load a copy from disk. This may return a cached copy. If the copy is not
* cached, the file will be loaded from disk if possible. If the copy does
* not exist, an exception will be raised. An exception may be raised if the file
* exists but cannot be read for whatever reason.
*
* @param namespace
* @param id
* @return
* @throws IOException
* @throws MissingCuboidCopyException
* @throws CuboidCopyException
*/
public CuboidCopy load(World world, String namespace, String id, MechanismsPlugin plugin)
throws IOException, CuboidCopyException {

id = id.toLowerCase();
String cacheKey = namespace + "/" + id;

HistoryHashMap<String, Long> missing = getMissing(world.getUID().toString());

if (missing.containsKey(cacheKey)) {
long lastCheck = missing.get(cacheKey);
if (lastCheck > System.currentTimeMillis()) {
throw new MissingCuboidCopyException(id);
}
}

HistoryHashMap<String, CuboidCopy> cache = getCache(world.getUID().toString());

CuboidCopy copy = cache.get(id);

if (copy == null) {
try {
File folder = new File(new File(plugin.getDataFolder(), "areas"), namespace);
copy = CuboidCopy.load(new File(folder, id + ".cbcopy"));
missing.remove(cacheKey);
cache.put(cacheKey, copy);
return copy;
} catch (FileNotFoundException e) {
missing.put(cacheKey, System.currentTimeMillis() + 10000);
throw new MissingCuboidCopyException(id);
} catch (IOException e) {
missing.put(cacheKey, System.currentTimeMillis() + 10000);
throw e;
}
}

return copy;
}

/**
* Save a copy to disk. The copy will be cached.
*
* @param id
* @param copy
* @throws IOException
*/
public void save(World world, String namespace, String id, CuboidCopy copy, MechanismsPlugin plugin)
throws IOException {

HistoryHashMap<String, CuboidCopy> cache = getCache(world.getUID().toString());

File folder = new File(new File(plugin.getDataFolder(), "areas"), namespace);

if (!folder.exists()) folder.mkdirs();

id = id.toLowerCase();

String cacheKey = namespace + "/" + id;

copy.save(new File(folder, id + ".cbcopy"));
missing.remove(cacheKey);
cache.put(id, copy);
}

/**
* Gets whether a copy can be made.
*
* @param namespace
* @param ignore
* @return -1 if the copy can be made, some other number for the count
*/
public int meetsQuota(World world, String namespace, String ignore, int quota, MechanismsPlugin plugin) {

String ignoreFilename = ignore + ".cbcopy";

String[] files = new File(new File(plugin.getDataFolder(), "areas"), namespace).list();

if (files == null) {
return quota > 0 ? -1 : 0;
} else if (ignore == null) {
return files.length < quota ? -1 : files.length;
} else {
int count = 0;

for (String f : files) {
if (f.equals(ignoreFilename)) {
return -1;
}

count++;
}

return count < quota ? -1 : count;
}
}

private HistoryHashMap<String, CuboidCopy> getCache(String world) {

if (cache.containsKey(world)) return cache.get(world);
else {
HistoryHashMap<String, CuboidCopy> h = new HistoryHashMap<String, CuboidCopy>(10);
cache.put(world, h);
return h;
}
}

private HistoryHashMap<String, Long> getMissing(String world) {

if (cache.containsKey(world)) return missing.get(world);
else {
HistoryHashMap<String, Long> h = new HistoryHashMap<String, Long>(10);
missing.put(world, h);
return h;
}
}
}
@@ -18,23 +18,15 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import com.sk89q.worldedit.CuboidClipboard;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.MaxChangedBlocksException;
import com.sk89q.craftbook.util.Tuple2;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.blocks.BlockType;
import com.sk89q.worldedit.bukkit.BukkitUtil;
import com.sk89q.worldedit.data.DataException;
import com.sk89q.worldedit.schematic.SchematicFormat;
import org.bukkit.Bukkit;
import org.bukkit.World;

import java.io.File;
import java.io.IOException;
import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* Stores a copy of a cuboid.
@@ -43,196 +35,289 @@
*/
public class CuboidCopy {

private static Map<Vector, Boolean> toggledOn = new HashMap<Vector, Boolean>();

private Vector origin;
private int width;
private int height;
private int length;
private CuboidClipboard clipboard;

/**
* Construct the object. This is to create a new copy at a certain
* location.
*
* @param origin
* @param size
*/
public CuboidCopy(Vector origin, Vector size) {

this.origin = origin;
this.clipboard = new CuboidClipboard(size, origin);
width = size.getBlockX();
height = size.getBlockY();
length = size.getBlockZ();
trackToggleState();
}

/**
* Used to create a copy when loaded from file.
*/
private CuboidCopy() {
trackToggleState();
}

/**
* Save the copy to file.
*
* @param file
* @throws IOException
*/
public void save(File file) throws IOException, DataException {

SchematicFormat.MCEDIT.save(clipboard, file);
}

/**
* Save the copy to a file.
*
* @param path
*
* @throws IOException
*/
public void save(String path) throws IOException, DataException {

save(new File(path));
}

/**
* Load a copy.
*
* @param file
*
* @return
*
* @throws IOException
* @throws CuboidCopyException
*/
public static CuboidCopy load(File file) throws IOException, CuboidCopyException, DataException {

CuboidClipboard clipboard = SchematicFormat.MCEDIT.load(file);

CuboidCopy copy = new CuboidCopy();
copy.clipboard = clipboard;
copy.origin = clipboard.getOrigin();
copy.width = clipboard.getWidth();
copy.height = clipboard.getHeight();
copy.length = clipboard.getLength();

return copy;
}

/**
* Load a copy from a file.
*
* @param path
*
* @return
*
* @throws IOException
* @throws CuboidCopyException
*/
public static CuboidCopy load(String path) throws IOException, CuboidCopyException, DataException {

return load(new File(path));
}

/**
* Make the copy from world.
*/
public void copy(EditSession session) {
// make a real copy with all data
clipboard.copy(session);
}

/**
* Paste to world.
*/
public void paste(EditSession session) throws MaxChangedBlocksException {

clipboard.paste(session, origin, false);
}

/**
* Clear the area.
*/
public void clear(World w) {

List<Vector> queued = new ArrayList<Vector>();

for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
for (int z = 0; z < length; z++) {
Vector pt = origin.add(x, y, z);
if (BlockType.shouldPlaceLast(w.getBlockTypeIdAt(BukkitUtil.toLocation(w, pt)))) {
w.getBlockAt(BukkitUtil.toLocation(w, pt)).setTypeId(0);
} else {
// Can't destroy these blocks yet
queued.add(pt);
}
}
}
}

for (Vector pt : queued) {
w.getBlockAt(BukkitUtil.toLocation(w, pt)).setTypeId(0);
}
}

/**
* Toggles the area.
*
* @return
*/
public void toggle(EditSession session) throws MaxChangedBlocksException {

World w = Bukkit.getWorld(session.getWorld().getName());
if (shouldClear()) {
clear(w);
toggledOn.put(origin, false);
} else {
paste(session);
toggledOn.put(origin, true);
}
}

/**
* Returns true if the bridge should be turned 'off'.
*
* @return
*/
public boolean shouldClear() {
return false;
/*
if (!toggledOn.containsKey(origin)) {
return true;
}
return toggledOn.get(origin);
*/
}

private void trackToggleState() {
if (!toggledOn.containsKey(origin)) {
toggledOn.put(origin, true);
private Vector origin;
private int width;
private int height;
private int length;
private byte[] blocks;
private byte[] data;
private Vector testOffset;

/**
* Construct the object. This is to create a new copy at a certain
* location.
*
* @param origin
* @param size
*/
public CuboidCopy(Vector origin, Vector size) {

this.origin = origin;
width = size.getBlockX();
height = size.getBlockY();
length = size.getBlockZ();
blocks = new byte[width * height * length];
data = new byte[width * height * length];
}

/**
* Used to create a copy when loaded from file.
*/
private CuboidCopy() {

}

/**
* Save the copy to file.
*
* @param dest
* @throws IOException
*/
public void save(File dest) throws IOException {

FileOutputStream out = new FileOutputStream(dest);
DataOutputStream writer = new DataOutputStream(out);
writer.writeByte(1);
writer.writeInt(origin.getBlockX());
writer.writeInt(origin.getBlockY());
writer.writeInt(origin.getBlockZ());
writer.writeInt(width);
writer.writeInt(height);
writer.writeInt(length);
writer.write(blocks, 0, blocks.length);
writer.write(data, 0, data.length);
writer.close();
out.close();
}

/**
* Save the copy to a file.
*
* @param path
* @throws IOException
*/
public void save(String path) throws IOException {

save(new File(path));
}

/**
* Load a copy.
*
* @param file
* @return
* @throws IOException
* @throws CuboidCopyException
*/
public static CuboidCopy load(File file) throws IOException, CuboidCopyException {

FileInputStream in = new FileInputStream(file);
DataInputStream reader = new DataInputStream(in);

int x, y, z;
int width, height, length;
byte[] blocks;
byte[] data;

try {
@SuppressWarnings("unused")
byte version = reader.readByte();
x = reader.readInt();
y = reader.readInt();
z = reader.readInt();
width = reader.readInt();
height = reader.readInt();
length = reader.readInt();
int size = width * height * length;
blocks = new byte[size];
data = new byte[size];
if (reader.read(blocks, 0, size) != size) {
throw new CuboidCopyException("File error: Bad size");
}
data = new byte[size];
if (reader.read(data, 0, size) != size) {
throw new CuboidCopyException("File error: Bad size");
}
} finally {
try {
in.close();
} catch (IOException ignored) {
}
}

CuboidCopy copy = new CuboidCopy();
copy.origin = new Vector(x, y, z);
copy.width = width;
copy.height = height;
copy.length = length;
copy.blocks = blocks;
copy.data = data;
copy.findTestOffset();

return copy;
}

/**
* Load a copy from a file.
*
* @param path
* @return
* @throws IOException
* @throws CuboidCopyException
*/
public static CuboidCopy load(String path) throws IOException, CuboidCopyException {

return load(new File(path));
}

/**
* Make the copy from world.
*/
public void copy(World w) {

for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
for (int z = 0; z < length; z++) {
int index = y * width * length + z * width + x;
blocks[index] = (byte) w.getBlockTypeIdAt(BukkitUtil.toLocation(w, origin.add(x, y, z)));
data[index] = w.getBlockAt(BukkitUtil.toLocation(w, origin.add(x, y, z))).getData();
}
}
}

findTestOffset();
}

/**
* Paste to world.
*/
public void paste(World w) {

ArrayList<Tuple2<Vector, byte[]>> queueAfter =
new ArrayList<Tuple2<Vector, byte[]>>();
ArrayList<Tuple2<Vector, byte[]>> queueLast =
new ArrayList<Tuple2<Vector, byte[]>>();

for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
for (int z = 0; z < length; z++) {
int index = y * width * length + z * width + x;
Vector pt = origin.add(x, y, z);

if (BlockType.shouldPlaceLast(w.getBlockTypeIdAt(BukkitUtil.toLocation(w, pt)))) {
w.getBlockAt(BukkitUtil.toLocation(w, pt)).setTypeId(0);
}

if (BlockType.shouldPlaceLast(blocks[index])) {
queueLast.add(new Tuple2<Vector, byte[]>(pt, new byte[]{blocks[index], data[index]}));
} else {
queueAfter.add(new Tuple2<Vector, byte[]>(pt, new byte[]{blocks[index], data[index]}));
}
}
}
}

for (Tuple2<Vector, byte[]> entry : queueAfter) {
byte[] v = entry.b;
w.getBlockAt(BukkitUtil.toLocation(w, entry.a)).setTypeId(v[0]);
if (BlockType.usesData(v[0])) {
w.getBlockAt(BukkitUtil.toLocation(w, entry.a)).setData(v[1]);
}
}

for (Tuple2<Vector, byte[]> entry : queueLast) {
byte[] v = entry.b;
w.getBlockAt(BukkitUtil.toLocation(w, entry.a)).setTypeId(v[0]);
if (BlockType.usesData(v[0])) {
w.getBlockAt(BukkitUtil.toLocation(w, entry.a)).setData(v[1]);
}
}

}

/**
* Get the distance between a point and this cuboid.
*
* @param pos
*
* @return
*/
public double distance(Vector pos) {

Vector max = origin.add(new Vector(width, height, length));
int closestX = Math.max(origin.getBlockX(),
Math.min(max.getBlockX(), pos.getBlockX()));
int closestY = Math.max(origin.getBlockY(),
Math.min(max.getBlockY(), pos.getBlockY()));
int closestZ = Math.max(origin.getBlockZ(),
Math.min(max.getBlockZ(), pos.getBlockZ()));
return pos.distance(new Vector(closestX, closestY, closestZ));
}
/**
* Clear the area.
*/
public void clear(World w) {

List<Vector> queued = new ArrayList<Vector>();

for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
for (int z = 0; z < length; z++) {
Vector pt = origin.add(x, y, z);
if (BlockType.shouldPlaceLast(w.getBlockTypeIdAt(BukkitUtil.toLocation(w, pt)))) {
w.getBlockAt(BukkitUtil.toLocation(w, pt)).setTypeId(0);
} else {
// Can't destroy these blocks yet
queued.add(pt);
}
}
}
}

for (Vector pt : queued) {
w.getBlockAt(BukkitUtil.toLocation(w, pt)).setTypeId(0);
}
}

/**
* Toggles the area.
*
* @return
*/
public void toggle(World w) {

if (shouldClear(w)) {
clear(w);
} else {
paste(w);
}
}

/**
* Returns true if the bridge should be turned 'off'.
*
* @return
*/
public boolean shouldClear(World w) {

Vector v = origin.add(testOffset);
return w.getBlockTypeIdAt(BukkitUtil.toLocation(w, v)) != 0;
}

/**
* Find a good position to test if an area is active.
*/
private void findTestOffset() {

for (int y = height - 1; y >= 0; y--) {
for (int x = 0; x < width; x++) {
for (int z = 0; z < length; z++) {
int index = y * width * length + z * width + x;
if (blocks[index] != 0) {
testOffset = new Vector(x, y, z);
}
}
}
}
}

/**
* Get the distance between a point and this cuboid.
*
* @param pos
* @return
*/
public double distance(Vector pos) {

Vector max = origin.add(new Vector(width, height, length));
int closestX = Math.max(origin.getBlockX(),
Math.min(max.getBlockX(), pos.getBlockX()));
int closestY = Math.max(origin.getBlockY(),
Math.min(max.getBlockY(), pos.getBlockY()));
int closestZ = Math.max(origin.getBlockZ(),
Math.min(max.getBlockZ(), pos.getBlockZ()));
return pos.distance(new Vector(closestX, closestY, closestZ));
}
}