diff --git a/README.md b/README.md
index d690f08c..43a40a81 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,23 @@
-# Overview
-Optimize worldedit
-# Main resource page
-https://www.spigotmc.org/resources/13932/
\ No newline at end of file
+# About
+FAWE is an addon for WorldEdit that has huge speed and memory improvements as well as a few extra features.
+
+# Spigot page
+https://www.spigotmc.org/resources/13932/
+
+# IRC
+http://webchat.esper.net/?nick=&channels=IntellectualCrafters
+
+# Releases:
+https://github.com/boy0001/FastAsyncWorldedit/releases
+
+# Building
+> FAWE uses gradle to build
+
+gradlew setupDecompWorkspace
+gradlew build
+
+# Contributing
+Have an idea for an optimization, or a cool feature?
+ - I'll accept most PR's
+ - Let me know what you've tested / what may need further testing
+ - If you need any help, create a ticket or discuss on IRC
\ No newline at end of file
diff --git a/core/src/main/java/com/boydti/fawe/Fawe.java b/core/src/main/java/com/boydti/fawe/Fawe.java
index 44b70f8f..7da5ce81 100644
--- a/core/src/main/java/com/boydti/fawe/Fawe.java
+++ b/core/src/main/java/com/boydti/fawe/Fawe.java
@@ -22,6 +22,7 @@ import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.LocalSession;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.WorldEdit;
+import com.sk89q.worldedit.command.ClipboardCommands;
import com.sk89q.worldedit.command.SchematicCommands;
import com.sk89q.worldedit.command.ScriptingCommands;
import com.sk89q.worldedit.extension.platform.CommandManager;
@@ -155,6 +156,7 @@ public class Fawe {
*/
this.setupConfigs();
MainUtil.deleteOlder(new File(IMP.getDirectory(), "history"), TimeUnit.DAYS.toMillis(Settings.DELETE_HISTORY_AFTER_DAYS));
+ MainUtil.deleteOlder(new File(IMP.getDirectory(), "clipboard"), TimeUnit.DAYS.toMillis(Settings.DELETE_CLIPBOARD_AFTER_DAYS));
TaskManager.IMP = this.IMP.getTaskManager();
if (Settings.METRICS) {
@@ -221,6 +223,7 @@ public class Fawe {
private void setupInjector() {
EditSession.inject();
Operations.inject();
+ ClipboardCommands.inject();
SchematicCommands.inject();
ScriptingCommands.inject();
BreadthFirstSearch.inject();
diff --git a/core/src/main/java/com/boydti/fawe/config/BBC.java b/core/src/main/java/com/boydti/fawe/config/BBC.java
index 48e4bce7..afe1eb5d 100644
--- a/core/src/main/java/com/boydti/fawe/config/BBC.java
+++ b/core/src/main/java/com/boydti/fawe/config/BBC.java
@@ -51,6 +51,10 @@ public enum BBC {
WORLDEDIT_CANCEL_REASON_MAX_FAILS("Outside allowed region", "Cancel"),
WORLDEDIT_FAILED_LOAD_CHUNK("&cSkipped loading chunk: &7%s0;%s1&c. Try increasing chunk-wait.", "Cancel"),
+ LOADING_CLIPBOARD("&dLoading clipboard from disk, please wait.", "History"),
+ INDEXING_HISTORY("&dIndexing %s history objects on disk, please wait.", "History"),
+ INDEXING_COMPLETE("&dIndexing complete. Took: %s seconds!", "History"),
+
WORLDEDIT_OOM_ADMIN("&cPossible options:\n&8 - &7//fast\n&8 - &7Do smaller edits\n&8 - &7Allocate more memory\n&8 - &7Disable this safeguard", "Info"),
NOT_PLAYER("&cYou must be a player to perform this action!", "Error"),
COMPRESSED("History compressed. Saved ~ %s0b (%s1x smaller)", "Info"),
diff --git a/core/src/main/java/com/boydti/fawe/config/Settings.java b/core/src/main/java/com/boydti/fawe/config/Settings.java
index 4d5d88cc..0dca8739 100644
--- a/core/src/main/java/com/boydti/fawe/config/Settings.java
+++ b/core/src/main/java/com/boydti/fawe/config/Settings.java
@@ -24,6 +24,7 @@ public class Settings {
public static boolean STORE_CLIPBOARD_ON_DISK = false;
public static int DELETE_HISTORY_AFTER_DAYS = 7;
+ public static int DELETE_CLIPBOARD_AFTER_DAYS = 0;
public static int COMPRESSION_LEVEL = 0;
public static int BUFFER_SIZE = 531441;
public static boolean METRICS = true;
@@ -86,6 +87,7 @@ public class Settings {
options.put("lighting.fix-all", FIX_ALL_LIGHTING);
options.put("lighting.async", ASYNC_LIGHTING);
options.put("clipboard.use-disk", STORE_CLIPBOARD_ON_DISK);
+ options.put("clipboard.delete-after-days", DELETE_CLIPBOARD_AFTER_DAYS);
options.put("history.use-disk", STORE_HISTORY_ON_DISK);
options.put("history.compress", false);
options.put("history.chunk-wait-ms", CHUNK_WAIT);
@@ -136,7 +138,7 @@ public class Settings {
ALLOWED_3RDPARTY_EXTENTS = config.getStringList("extent.allowed-plugins");
EXTENT_DEBUG = config.getBoolean("extent.debug");
STORE_CLIPBOARD_ON_DISK = config.getBoolean("clipboard.use-disk");
-
+ DELETE_CLIPBOARD_AFTER_DAYS = config.getInt("clipboard.delete-after-days");
if (STORE_HISTORY_ON_DISK = config.getBoolean("history.use-disk")) {
LocalSession.MAX_HISTORY_SIZE = Integer.MAX_VALUE;
}
diff --git a/core/src/main/java/com/boydti/fawe/object/BufferedRandomAccessFile.java b/core/src/main/java/com/boydti/fawe/object/BufferedRandomAccessFile.java
new file mode 100644
index 00000000..97414c5a
--- /dev/null
+++ b/core/src/main/java/com/boydti/fawe/object/BufferedRandomAccessFile.java
@@ -0,0 +1,373 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.boydti.fawe.object;
+
+import java.io.*;
+import java.util.Arrays;
+
+
+/**
+ * A BufferedRandomAccessFile
is like a
+ * RandomAccessFile
, but it uses a private buffer so that most
+ * operations do not require a disk access.
+ *
+ *
+ * Note: The operations on this class are unmonitored. Also, the correct
+ * functioning of the RandomAccessFile
methods that are not
+ * overridden here relies on the implementation of those methods in the
+ * superclass.
+ * Author : Avinash Lakshman ( alakshman@facebook.com) & Prashant Malik ( pmalik@facebook.com )
+ */
+
+public final class BufferedRandomAccessFile extends RandomAccessFile
+{
+ static final int LogBuffSz_ = 16; // 64K buffer
+ public static final int BuffSz_ = (1 << LogBuffSz_);
+ static final long BuffMask_ = ~(((long) BuffSz_) - 1L);
+
+ /*
+ * This implementation is based on the buffer implementation in Modula-3's
+ * "Rd", "Wr", "RdClass", and "WrClass" interfaces.
+ */
+ private boolean dirty_; // true iff unflushed bytes exist
+ private boolean closed_; // true iff the file is closed
+ private long curr_; // current position in file
+ private long lo_, hi_; // bounds on characters in "buff"
+ private byte[] buff_; // local buffer
+ private long maxHi_; // this.lo + this.buff.length
+ private boolean hitEOF_; // buffer contains last file block?
+ private long diskPos_; // disk position
+
+ /*
+ * To describe the above fields, we introduce the following abstractions for
+ * the file "f":
+ *
+ * len(f) the length of the file curr(f) the current position in the file
+ * c(f) the abstract contents of the file disk(f) the contents of f's
+ * backing disk file closed(f) true iff the file is closed
+ *
+ * "curr(f)" is an index in the closed interval [0, len(f)]. "c(f)" is a
+ * character sequence of length "len(f)". "c(f)" and "disk(f)" may differ if
+ * "c(f)" contains unflushed writes not reflected in "disk(f)". The flush
+ * operation has the effect of making "disk(f)" identical to "c(f)".
+ *
+ * A file is said to be *valid* if the following conditions hold:
+ *
+ * V1. The "closed" and "curr" fields are correct:
+ *
+ * f.closed == closed(f) f.curr == curr(f)
+ *
+ * V2. The current position is either contained in the buffer, or just past
+ * the buffer:
+ *
+ * f.lo <= f.curr <= f.hi
+ *
+ * V3. Any (possibly) unflushed characters are stored in "f.buff":
+ *
+ * (forall i in [f.lo, f.curr): c(f)[i] == f.buff[i - f.lo])
+ *
+ * V4. For all characters not covered by V3, c(f) and disk(f) agree:
+ *
+ * (forall i in [f.lo, len(f)): i not in [f.lo, f.curr) => c(f)[i] ==
+ * disk(f)[i])
+ *
+ * V5. "f.dirty" is true iff the buffer contains bytes that should be
+ * flushed to the file; by V3 and V4, only part of the buffer can be dirty.
+ *
+ * f.dirty == (exists i in [f.lo, f.curr): c(f)[i] != f.buff[i - f.lo])
+ *
+ * V6. this.maxHi == this.lo + this.buff.length
+ *
+ * Note that "f.buff" can be "null" in a valid file, since the range of
+ * characters in V3 is empty when "f.lo == f.curr".
+ *
+ * A file is said to be *ready* if the buffer contains the current position,
+ * i.e., when:
+ *
+ * R1. !f.closed && f.buff != null && f.lo <= f.curr && f.curr < f.hi
+ *
+ * When a file is ready, reading or writing a single byte can be performed
+ * by reading or writing the in-memory buffer without performing a disk
+ * operation.
+ */
+
+ /**
+ * Open a new BufferedRandomAccessFile
on file
+ * in mode mode
, which should be "r" for reading only, or
+ * "rw" for reading and writing.
+ */
+ public BufferedRandomAccessFile(File file, String mode) throws IOException
+ {
+ super(file, mode);
+ this.init(0);
+ }
+
+ public BufferedRandomAccessFile(File file, String mode, int size) throws IOException
+ {
+ super(file, mode);
+ this.init(size);
+ }
+
+ /**
+ * Open a new BufferedRandomAccessFile
on the file named
+ * name
in mode mode
, which should be "r" for
+ * reading only, or "rw" for reading and writing.
+ */
+ public BufferedRandomAccessFile(String name, String mode) throws IOException
+ {
+ super(name, mode);
+ this.init(0);
+ }
+
+ public BufferedRandomAccessFile(String name, String mode, int size) throws FileNotFoundException
+ {
+ super(name, mode);
+ this.init(size);
+ }
+
+ private void init(int size)
+ {
+ this.dirty_ = this.closed_ = false;
+ this.lo_ = this.curr_ = this.hi_ = 0;
+ this.buff_ = (size > BuffSz_) ? new byte[size] : new byte[BuffSz_];
+ this.maxHi_ = (long) BuffSz_;
+ this.hitEOF_ = false;
+ this.diskPos_ = 0L;
+ }
+
+ public void close() throws IOException
+ {
+ this.flush();
+ this.closed_ = true;
+ super.close();
+ }
+
+ /**
+ * Flush any bytes in the file's buffer that have not yet been written to
+ * disk. If the file was created read-only, this method is a no-op.
+ */
+ public void flush() throws IOException
+ {
+ this.flushBuffer();
+ }
+
+ /* Flush any dirty bytes in the buffer to disk. */
+ private void flushBuffer() throws IOException
+ {
+ if (this.dirty_)
+ {
+ if (this.diskPos_ != this.lo_)
+ super.seek(this.lo_);
+ int len = (int) (this.curr_ - this.lo_);
+ super.write(this.buff_, 0, len);
+ this.diskPos_ = this.curr_;
+ this.dirty_ = false;
+ }
+ }
+
+ /*
+ * Read at most "this.buff.length" bytes into "this.buff", returning the
+ * number of bytes read. If the return result is less than
+ * "this.buff.length", then EOF was read.
+ */
+ private int fillBuffer() throws IOException
+ {
+ int cnt = 0;
+ int rem = this.buff_.length;
+ while (rem > 0)
+ {
+ int n = super.read(this.buff_, cnt, rem);
+ if (n < 0)
+ break;
+ cnt += n;
+ rem -= n;
+ }
+ if ( (cnt < 0) && (this.hitEOF_ = (cnt < this.buff_.length)) )
+ {
+ // make sure buffer that wasn't read is initialized with -1
+ Arrays.fill(this.buff_, cnt, this.buff_.length, (byte) 0xff);
+ }
+ this.diskPos_ += cnt;
+ return cnt;
+ }
+
+ /*
+ * This method positions this.curr
at position pos
.
+ * If pos
does not fall in the current buffer, it flushes the
+ * current buffer and loads the correct one.
+ *
+ * On exit from this routine this.curr == this.hi
iff pos
+ * is at or past the end-of-file, which can only happen if the file was
+ * opened in read-only mode.
+ */
+ public void seek(long pos) throws IOException
+ {
+ if (pos >= this.hi_ || pos < this.lo_)
+ {
+ // seeking outside of current buffer -- flush and read
+ this.flushBuffer();
+ this.lo_ = pos & BuffMask_; // start at BuffSz boundary
+ this.maxHi_ = this.lo_ + (long) this.buff_.length;
+ if (this.diskPos_ != this.lo_)
+ {
+ super.seek(this.lo_);
+ this.diskPos_ = this.lo_;
+ }
+ int n = this.fillBuffer();
+ this.hi_ = this.lo_ + (long) n;
+ }
+ else
+ {
+ // seeking inside current buffer -- no read required
+ if (pos < this.curr_)
+ {
+ // if seeking backwards, we must flush to maintain V4
+ this.flushBuffer();
+ }
+ }
+ this.curr_ = pos;
+ }
+
+ public long getFilePointer()
+ {
+ return this.curr_;
+ }
+
+ public long length() throws IOException
+ {
+ return Math.max(this.curr_, super.length());
+ }
+
+ public int read() throws IOException
+ {
+ if (this.curr_ >= this.hi_)
+ {
+ // test for EOF
+ // if (this.hi < this.maxHi) return -1;
+ if (this.hitEOF_)
+ return -1;
+
+ // slow path -- read another buffer
+ this.seek(this.curr_);
+ if (this.curr_ == this.hi_)
+ return -1;
+ }
+ byte res = this.buff_[(int) (this.curr_ - this.lo_)];
+ this.curr_++;
+ return ((int) res) & 0xFF; // convert byte -> int
+ }
+
+ public int read(byte[] b) throws IOException
+ {
+ return this.read(b, 0, b.length);
+ }
+
+ public int read(byte[] b, int off, int len) throws IOException
+ {
+ if (this.curr_ >= this.hi_)
+ {
+ // test for EOF
+ // if (this.hi < this.maxHi) return -1;
+ if (this.hitEOF_)
+ return -1;
+
+ // slow path -- read another buffer
+ this.seek(this.curr_);
+ if (this.curr_ == this.hi_)
+ return -1;
+ }
+ len = Math.min(len, (int) (this.hi_ - this.curr_));
+ int buffOff = (int) (this.curr_ - this.lo_);
+ System.arraycopy(this.buff_, buffOff, b, off, len);
+ this.curr_ += len;
+ return len;
+ }
+
+ public void write(int b) throws IOException
+ {
+ if (this.curr_ >= this.hi_)
+ {
+ if (this.hitEOF_ && this.hi_ < this.maxHi_)
+ {
+ // at EOF -- bump "hi"
+ this.hi_++;
+ }
+ else
+ {
+ // slow path -- write current buffer; read next one
+ this.seek(this.curr_);
+ if (this.curr_ == this.hi_)
+ {
+ // appending to EOF -- bump "hi"
+ this.hi_++;
+ }
+ }
+ }
+ this.buff_[(int) (this.curr_ - this.lo_)] = (byte) b;
+ this.curr_++;
+ this.dirty_ = true;
+ }
+
+ public void write(byte[] b) throws IOException
+ {
+ this.write(b, 0, b.length);
+ }
+
+ public void write(byte[] b, int off, int len) throws IOException
+ {
+ while (len > 0)
+ {
+ int n = this.writeAtMost(b, off, len);
+ off += n;
+ len -= n;
+ this.dirty_ = true;
+ }
+ }
+
+ /*
+ * Write at most "len" bytes to "b" starting at position "off", and return
+ * the number of bytes written.
+ */
+ private int writeAtMost(byte[] b, int off, int len) throws IOException
+ {
+ if (this.curr_ >= this.hi_)
+ {
+ if (this.hitEOF_ && this.hi_ < this.maxHi_)
+ {
+ // at EOF -- bump "hi"
+ this.hi_ = this.maxHi_;
+ }
+ else
+ {
+ // slow path -- write current buffer; read next one
+ this.seek(this.curr_);
+ if (this.curr_ == this.hi_)
+ {
+ // appending to EOF -- bump "hi"
+ this.hi_ = this.maxHi_;
+ }
+ }
+ }
+ len = Math.min(len, (int) (this.hi_ - this.curr_));
+ int buffOff = (int) (this.curr_ - this.lo_);
+ System.arraycopy(b, off, this.buff_, buffOff, len);
+ this.curr_ += len;
+ return len;
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/com/boydti/fawe/object/FawePlayer.java b/core/src/main/java/com/boydti/fawe/object/FawePlayer.java
index fa678dea..c7d5b31b 100644
--- a/core/src/main/java/com/boydti/fawe/object/FawePlayer.java
+++ b/core/src/main/java/com/boydti/fawe/object/FawePlayer.java
@@ -5,6 +5,7 @@ import com.boydti.fawe.FaweAPI;
import com.boydti.fawe.config.BBC;
import com.boydti.fawe.config.Settings;
import com.boydti.fawe.object.changeset.DiskStorageHistory;
+import com.boydti.fawe.object.clipboard.DiskOptimizedClipboard;
import com.boydti.fawe.util.TaskManager;
import com.boydti.fawe.util.WEManager;
import com.boydti.fawe.wrappers.PlayerWrapper;
@@ -13,10 +14,13 @@ import com.sk89q.worldedit.IncompleteRegionException;
import com.sk89q.worldedit.LocalSession;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.entity.Player;
+import com.sk89q.worldedit.extent.clipboard.Clipboard;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.regions.RegionSelector;
import com.sk89q.worldedit.regions.selector.CuboidRegionSelector;
+import com.sk89q.worldedit.session.ClipboardHolder;
import com.sk89q.worldedit.world.World;
+import com.sk89q.worldedit.world.registry.WorldData;
import java.io.File;
import java.lang.reflect.Field;
import java.util.ArrayList;
@@ -95,12 +99,33 @@ public abstract class FawePlayer {
loadSessionsFromDisk(world);
}
}
+ loadClipboardFromDisk();
} catch (Exception e) {
e.printStackTrace();
Fawe.debug("Failed to load history for: " + getName());
}
}
+ public void loadClipboardFromDisk() {
+ try {
+ File file = new File(Fawe.imp().getDirectory(), "clipboard" + File.separator + getUUID());
+ if (file.exists()) {
+ DiskOptimizedClipboard doc = new DiskOptimizedClipboard(file);
+ Player player = getPlayer();
+ LocalSession session = getSession();
+ if (player != null && session != null) {
+ sendMessage("&d" + BBC.PREFIX.s() + " " + BBC.LOADING_CLIPBOARD.s());
+ WorldData worldData = player.getWorld().getWorldData();
+ Clipboard clip = doc.toClipboard();
+ ClipboardHolder holder = new ClipboardHolder(clip, worldData);
+ getSession().setClipboard(holder);
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
/**
* Get the current World
* @return
@@ -118,35 +143,38 @@ public abstract class FawePlayer {
if (world == null) {
return;
}
- TaskManager.IMP.async(new Runnable() {
- @Override
- public void run() {
- UUID uuid = getUUID();
- List editIds = new ArrayList<>();
- File folder = new File(Fawe.imp().getDirectory(), "history" + File.separator + world.getName() + File.separator + uuid);
- if (folder.isDirectory()) {
- for (File file : folder.listFiles()) {
- if (file.getName().endsWith(".bd")) {
- int index = Integer.parseInt(file.getName().split("\\.")[0]);
- editIds.add(index);
- }
- }
+ final long start = System.currentTimeMillis();
+ final UUID uuid = getUUID();
+ final List editIds = new ArrayList<>();
+ final File folder = new File(Fawe.imp().getDirectory(), "history" + File.separator + world.getName() + File.separator + uuid);
+ if (folder.isDirectory()) {
+ for (File file : folder.listFiles()) {
+ if (file.getName().endsWith(".bd")) {
+ int index = Integer.parseInt(file.getName().split("\\.")[0]);
+ editIds.add(index);
}
- Collections.sort(editIds);
- if (editIds.size() > 0) {
- Fawe.debug(BBC.PREFIX.s() + " Indexing " + editIds.size() + " history objects for " + getName());
- for (int index : editIds) {
+ }
+ }
+ if (editIds.size() > 0) {
+ sendMessage("&d" + BBC.PREFIX.s() + " " + BBC.INDEXING_HISTORY.format(editIds.size()));
+ TaskManager.IMP.async(new Runnable() {
+ @Override
+ public void run() {
+ Collections.sort(editIds);
+ for (int i = editIds.size() - 1; i >= 0; i--) {
+ int index = editIds.get(i);
DiskStorageHistory set = new DiskStorageHistory(world, uuid, index);
EditSession edit = set.toEditSession(getPlayer());
if (world.equals(getWorld())) {
- session.remember(edit);
+ session.remember(edit, 0);
} else {
return;
}
}
+ sendMessage("&d" + BBC.PREFIX.s() + " " + BBC.INDEXING_COMPLETE.format((System.currentTimeMillis() - start) / 1000d));
}
- }
- });
+ });
+ }
}
/**
diff --git a/core/src/main/java/com/boydti/fawe/object/clipboard/DiskOptimizedClipboard.java b/core/src/main/java/com/boydti/fawe/object/clipboard/DiskOptimizedClipboard.java
index 27e93c0a..0d262a4b 100644
--- a/core/src/main/java/com/boydti/fawe/object/clipboard/DiskOptimizedClipboard.java
+++ b/core/src/main/java/com/boydti/fawe/object/clipboard/DiskOptimizedClipboard.java
@@ -2,14 +2,19 @@ package com.boydti.fawe.object.clipboard;
import com.boydti.fawe.Fawe;
import com.boydti.fawe.FaweCache;
+import com.boydti.fawe.config.Settings;
+import com.boydti.fawe.object.BufferedRandomAccessFile;
import com.boydti.fawe.object.IntegerTrio;
import com.boydti.fawe.util.TaskManager;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.worldedit.EditSession;
+import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.entity.BaseEntity;
import com.sk89q.worldedit.entity.Entity;
import com.sk89q.worldedit.extent.Extent;
+import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard;
+import com.sk89q.worldedit.regions.CuboidRegion;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
@@ -28,55 +33,167 @@ import java.util.UUID;
*/
public class DiskOptimizedClipboard extends FaweClipboard {
+ private static int HEADER_SIZE = 10;
+
+ protected int length;
+ protected int height;
+ protected int width;
+ protected int area;
+
private final HashMap nbtMap;
private final HashSet entities;
private final File file;
private final byte[] buffer;
- private volatile RandomAccessFile raf;
+ private volatile BufferedRandomAccessFile raf;
private long lastAccessed;
private int last;
+ public DiskOptimizedClipboard(int width, int height, int length, UUID uuid) {
+ this(width, height, length, new File(Fawe.imp().getDirectory(), "clipboard" + File.separator + uuid));
+ }
+
+ public DiskOptimizedClipboard(File file) {
+ nbtMap = new HashMap<>();
+ entities = new HashSet<>();this.buffer = new byte[2];
+ this.file = file;
+ this.lastAccessed = System.currentTimeMillis();
+ try {
+ this.raf = new BufferedRandomAccessFile(file, "rw", Settings.BUFFER_SIZE);
+ raf.setLength(file.length());
+ long size = (raf.length() - HEADER_SIZE) / 2;
+ raf.seek(0);
+ last = -1;
+ raf.read(buffer);
+ width = (((buffer[1] & 0xFF) << 8) + ((buffer[0] & 0xFF)));
+ raf.read(buffer);
+ length = (((buffer[1] & 0xFF) << 8) + ((buffer[0] & 0xFF)));
+ height = (int) (size / (width * length));
+ area = width * length;
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ autoCloseTask();
+ }
+
+ public BlockArrayClipboard toClipboard() {
+ try {
+ CuboidRegion region = new CuboidRegion(new Vector(0, 0, 0), new Vector(width - 1, height - 1, length - 1)) {
+ @Override
+ public boolean contains(Vector position) {
+ return true;
+ }
+ };
+ if (raf == null) {
+ open();
+ }
+ raf.seek(4);
+ last = -1;
+ int ox = (((byte) raf.read() << 8) | ((byte) raf.read()) & 0xFF);
+ int oy = (((byte) raf.read() << 8) | ((byte) raf.read()) & 0xFF);
+ int oz = (((byte) raf.read() << 8) | ((byte) raf.read()) & 0xFF);
+ BlockArrayClipboard clipboard = new BlockArrayClipboard(region, this);
+ clipboard.setOrigin(new Vector(ox, oy, oz));
+ return clipboard;
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ @Override
+ public void setOrigin(Vector offset) {
+ try {
+ if (raf == null) {
+ open();
+ }
+ raf.seek(4);
+ last = -1;
+ raf.write((byte) (offset.getBlockX() >> 8));
+ raf.write((byte) (offset.getBlockX()));
+
+ raf.write((byte) (offset.getBlockY() >> 8));
+ raf.write((byte) (offset.getBlockY()));
+
+ raf.write((byte) (offset.getBlockZ() >> 8));
+ raf.write((byte) (offset.getBlockZ()));
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
public DiskOptimizedClipboard(int width, int height, int length, File file) {
- super(width, height, length);
nbtMap = new HashMap<>();
entities = new HashSet<>();
this.file = file;
this.buffer = new byte[2];
this.lastAccessed = System.currentTimeMillis();
+ this.width = width;
+ this.height = height;
+ this.length = length;
+ this.area = width * length;
try {
if (!file.exists()) {
file.getParentFile().mkdirs();
- file.createNewFile();
}
+ file.createNewFile();
} catch (Exception e) {
e.printStackTrace();
}
}
+ public void flush() {
+ try {
+ raf.close();
+ raf = null;
+ file.setWritable(true);
+ System.gc();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
public DiskOptimizedClipboard(int width, int height, int length) {
this(width, height, length, new File(Fawe.imp().getDirectory(), "clipboard" + File.separator + UUID.randomUUID()));
}
-
+ public void close() {
+ try {
+ RandomAccessFile tmp = raf;
+ raf = null;
+ tmp.close();
+ tmp = null;
+ System.gc();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
public void open() throws IOException {
- this.raf = new RandomAccessFile(file, "rw");
+ if (raf != null) {
+ close();
+ }
+ this.raf = new BufferedRandomAccessFile(file, "rw", Settings.BUFFER_SIZE);
long size = width * height * length * 2l;
if (raf.length() != size) {
- raf.setLength(size);
+ raf.setLength(size + HEADER_SIZE);
+ // write length etc
+ raf.seek(0);
+ last = 0;
+ raf.write((width) & 0xff);
+ raf.write(((width) >> 8) & 0xff);
+ raf.write((length) & 0xff);
+ raf.write(((length) >> 8) & 0xff);
}
+ autoCloseTask();
+ }
+
+ private void autoCloseTask() {
TaskManager.IMP.laterAsync(new Runnable() {
@Override
public void run() {
if (raf != null && System.currentTimeMillis() - lastAccessed > 10000) {
- try {
- RandomAccessFile tmp = raf;
- raf = null;
- tmp.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
+ close();
} else if (raf == null) {
return;
} else {
@@ -95,7 +212,7 @@ public class DiskOptimizedClipboard extends FaweClipboard {
lastAccessed = System.currentTimeMillis();
int i = x + z * width + y * area;
if (i != last + 1) {
- raf.seek(i << 1);
+ raf.seek((HEADER_SIZE) + (i << 1));
}
raf.read(buffer);
last = i;
@@ -129,8 +246,9 @@ public class DiskOptimizedClipboard extends FaweClipboard {
lastAccessed = System.currentTimeMillis();
int i = x + z * width + y * area;
if (i != last + 1) {
- raf.seek(i << 1);
+ raf.seek((HEADER_SIZE) + (i << 1));
}
+ last = i;
final int id = block.getId();
final int data = block.getData();
int combined = (id << 4) + data;
diff --git a/core/src/main/java/com/boydti/fawe/object/clipboard/FaweClipboard.java b/core/src/main/java/com/boydti/fawe/object/clipboard/FaweClipboard.java
index 0a84fe00..959889e5 100644
--- a/core/src/main/java/com/boydti/fawe/object/clipboard/FaweClipboard.java
+++ b/core/src/main/java/com/boydti/fawe/object/clipboard/FaweClipboard.java
@@ -1,5 +1,6 @@
package com.boydti.fawe.object.clipboard;
+import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.entity.BaseEntity;
import com.sk89q.worldedit.entity.Entity;
@@ -12,18 +13,6 @@ import javax.annotation.Nullable;
import static com.google.common.base.Preconditions.checkNotNull;
public abstract class FaweClipboard {
- public final int length;
- public final int height;
- public final int width;
- public final int area;
-
- public FaweClipboard(int width, int height, int length) {
- this.width = width;
- this.height = height;
- this.length = length;
- this.area = width * length;
- }
-
public abstract BaseBlock getBlock(int x, int y, int z);
public abstract boolean setBlock(int x, int y, int z, BaseBlock block);
@@ -34,6 +23,8 @@ public abstract class FaweClipboard {
public abstract boolean remove(ClipboardEntity clipboardEntity);
+ public void setOrigin(Vector offset) {} // Do nothing
+
/**
* Stores entity data.
*/
diff --git a/core/src/main/java/com/boydti/fawe/object/clipboard/MemoryOptimizedClipboard.java b/core/src/main/java/com/boydti/fawe/object/clipboard/MemoryOptimizedClipboard.java
index 9e8cddb5..6d4d5f62 100644
--- a/core/src/main/java/com/boydti/fawe/object/clipboard/MemoryOptimizedClipboard.java
+++ b/core/src/main/java/com/boydti/fawe/object/clipboard/MemoryOptimizedClipboard.java
@@ -13,6 +13,10 @@ import java.util.HashSet;
import java.util.List;
public class MemoryOptimizedClipboard extends FaweClipboard {
+ protected int length;
+ protected int height;
+ protected int width;
+ protected int area;
// x,z,y+15>>4 | y&15
private final byte[][] ids;
@@ -21,10 +25,13 @@ public class MemoryOptimizedClipboard extends FaweClipboard {
private final HashSet entities;
public MemoryOptimizedClipboard(int width, int height, int length) {
- super(width, height, length);
+ this.width = width;
+ this.height = height;
+ this.length = length;
+ this.area = width * length;
ids = new byte[width * length * ((height + 15) >> 4)][];
nbtMap = new HashMap<>();
- entities = new HashSet();
+ entities = new HashSet<>();
}
@Override
diff --git a/core/src/main/java/com/boydti/fawe/util/MainUtil.java b/core/src/main/java/com/boydti/fawe/util/MainUtil.java
index e3e2a36a..2a047c1c 100644
--- a/core/src/main/java/com/boydti/fawe/util/MainUtil.java
+++ b/core/src/main/java/com/boydti/fawe/util/MainUtil.java
@@ -35,6 +35,24 @@ public class MainUtil {
Fawe.debug(s);
}
+ public static void warnDeprecated(Class... alternatives) {
+ StackTraceElement[] stack = new RuntimeException().getStackTrace();
+ if (stack.length > 1) {
+ try {
+ StackTraceElement creatorElement = stack[stack.length - 2];
+ String className = creatorElement.getClassName();
+ Class clazz = Class.forName(className);
+ String creator = clazz.getSimpleName();
+ String packageName = clazz.getPackage().getName();
+
+ StackTraceElement deprecatedElement = stack[stack.length - 1];
+ String myName = Class.forName(deprecatedElement.getFileName()).getSimpleName();
+ Fawe.debug("@" + creator + " from " + packageName +": " + myName + " is deprecated.");
+ Fawe.debug(" - Alternatives: " + StringMan.getString(alternatives));
+ } catch (Throwable ignore) {}
+ }
+ }
+
public static void iterateFiles(File directory, RunnableVal task) {
if (directory.exists()) {
File[] files = directory.listFiles();
diff --git a/core/src/main/java/com/sk89q/worldedit/CuboidClipboard.java b/core/src/main/java/com/sk89q/worldedit/CuboidClipboard.java
index d60d8a2a..d0713371 100644
--- a/core/src/main/java/com/sk89q/worldedit/CuboidClipboard.java
+++ b/core/src/main/java/com/sk89q/worldedit/CuboidClipboard.java
@@ -21,12 +21,14 @@ package com.sk89q.worldedit;
import com.boydti.fawe.FaweCache;
import com.boydti.fawe.object.IntegerTrio;
+import com.boydti.fawe.util.MainUtil;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.blocks.BlockID;
import com.sk89q.worldedit.command.ClipboardCommands;
import com.sk89q.worldedit.command.SchematicCommands;
import com.sk89q.worldedit.extent.Extent;
+import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard;
import com.sk89q.worldedit.extent.clipboard.Clipboard;
import com.sk89q.worldedit.function.operation.ForwardExtentCopy;
import com.sk89q.worldedit.regions.CuboidRegion;
@@ -76,7 +78,7 @@ public class CuboidClipboard {
public byte[][] ids;
public byte[][] datas;
public HashMap nbtMap;
- public List entities = new ArrayList();
+ public List entities = new ArrayList<>();
public Vector size;
private int dx;
@@ -91,6 +93,7 @@ public class CuboidClipboard {
*/
public CuboidClipboard(Vector size) {
checkNotNull(size);
+ MainUtil.warnDeprecated(BlockArrayClipboard.class);
origin = new Vector();
offset = new Vector();
this.size = size;
diff --git a/core/src/main/java/com/sk89q/worldedit/LocalSession.java b/core/src/main/java/com/sk89q/worldedit/LocalSession.java
index 3904f908..92e19b92 100644
--- a/core/src/main/java/com/sk89q/worldedit/LocalSession.java
+++ b/core/src/main/java/com/sk89q/worldedit/LocalSession.java
@@ -20,6 +20,8 @@
package com.sk89q.worldedit;
import com.boydti.fawe.object.changeset.FaweChangeSet;
+import com.boydti.fawe.object.clipboard.DiskOptimizedClipboard;
+import com.boydti.fawe.util.FaweQueue;
import com.boydti.fawe.util.SetQueue;
import com.sk89q.jchronic.Chronic;
import com.sk89q.jchronic.Options;
@@ -32,6 +34,8 @@ import com.sk89q.worldedit.command.tool.SinglePickaxe;
import com.sk89q.worldedit.command.tool.Tool;
import com.sk89q.worldedit.entity.Player;
import com.sk89q.worldedit.extension.platform.Actor;
+import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard;
+import com.sk89q.worldedit.extent.clipboard.Clipboard;
import com.sk89q.worldedit.extent.inventory.BlockBag;
import com.sk89q.worldedit.function.mask.Mask;
import com.sk89q.worldedit.function.mask.Masks;
@@ -190,10 +194,16 @@ public class LocalSession {
* @param editSession the edit session
*/
public void remember(EditSession editSession) {
- checkNotNull(editSession);
+ remember(editSession, history.size());
+ }
+ public void remember(EditSession editSession, int index) {
+ // Enqueue it
if (editSession.getQueue() != null) {
- SetQueue.IMP.enqueue(editSession.getQueue());
+ FaweQueue queue = editSession.getQueue();
+ if (queue.size() > 0) {
+ SetQueue.IMP.enqueue(editSession.getQueue());
+ }
}
// Don't store anything if no changes were made
@@ -207,7 +217,7 @@ public class LocalSession {
if (set instanceof FaweChangeSet) {
((FaweChangeSet) set).flush();
}
- history.add(editSession);
+ history.add(Math.max(0, Math.min(index, history.size())), editSession);
while (history.size() > MAX_HISTORY_SIZE) {
history.remove(0);
}
@@ -456,6 +466,15 @@ public class LocalSession {
* @param clipboard the clipboard, or null if the clipboard is to be cleared
*/
public void setClipboard(@Nullable ClipboardHolder clipboard) {
+ if (this.clipboard != null && clipboard != null) {
+ Clipboard clip = clipboard.getClipboard();
+ if (clip instanceof BlockArrayClipboard) {
+ BlockArrayClipboard bac = (BlockArrayClipboard) clip;
+ if (bac.IMP instanceof DiskOptimizedClipboard) {
+ ((DiskOptimizedClipboard) bac.IMP).flush();
+ }
+ }
+ }
this.clipboard = clipboard;
}
diff --git a/core/src/main/java/com/sk89q/worldedit/command/ClipboardCommands.java b/core/src/main/java/com/sk89q/worldedit/command/ClipboardCommands.java
new file mode 100644
index 00000000..943546e3
--- /dev/null
+++ b/core/src/main/java/com/sk89q/worldedit/command/ClipboardCommands.java
@@ -0,0 +1,263 @@
+/*
+ * WorldEdit, a Minecraft world manipulation toolkit
+ * Copyright (C) sk89q
+ * Copyright (C) WorldEdit team and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldedit.command;
+
+import com.sk89q.minecraft.util.commands.Command;
+import com.sk89q.minecraft.util.commands.CommandPermissions;
+import com.sk89q.minecraft.util.commands.Logging;
+import com.sk89q.worldedit.EditSession;
+import com.sk89q.worldedit.LocalSession;
+import com.sk89q.worldedit.Vector;
+import com.sk89q.worldedit.WorldEdit;
+import com.sk89q.worldedit.WorldEditException;
+import com.sk89q.worldedit.entity.Player;
+import com.sk89q.worldedit.extension.platform.Actor;
+import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard;
+import com.sk89q.worldedit.extent.clipboard.Clipboard;
+import com.sk89q.worldedit.function.block.BlockReplace;
+import com.sk89q.worldedit.function.mask.Mask;
+import com.sk89q.worldedit.function.operation.ForwardExtentCopy;
+import com.sk89q.worldedit.function.operation.Operation;
+import com.sk89q.worldedit.function.operation.Operations;
+import com.sk89q.worldedit.function.pattern.Pattern;
+import com.sk89q.worldedit.internal.annotation.Direction;
+import com.sk89q.worldedit.internal.annotation.Selection;
+import com.sk89q.worldedit.math.transform.AffineTransform;
+import com.sk89q.worldedit.regions.Region;
+import com.sk89q.worldedit.regions.RegionSelector;
+import com.sk89q.worldedit.regions.selector.CuboidRegionSelector;
+import com.sk89q.worldedit.session.ClipboardHolder;
+import com.sk89q.worldedit.util.command.binding.Switch;
+import com.sk89q.worldedit.util.command.parametric.Optional;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.sk89q.minecraft.util.commands.Logging.LogMode.PLACEMENT;
+import static com.sk89q.minecraft.util.commands.Logging.LogMode.REGION;
+
+/**
+ * Clipboard commands.
+ */
+public class ClipboardCommands {
+
+ private final WorldEdit worldEdit;
+
+ /**
+ * Create a new instance.
+ *
+ * @param worldEdit reference to WorldEdit
+ */
+ public ClipboardCommands(WorldEdit worldEdit) {
+ checkNotNull(worldEdit);
+ this.worldEdit = worldEdit;
+ }
+
+ @Command(
+ aliases = { "/copy" },
+ flags = "em",
+ desc = "Copy the selection to the clipboard",
+ help = "Copy the selection to the clipboard\n" +
+ "Flags:\n" +
+ " -e controls whether entities are copied\n" +
+ " -m sets a source mask so that excluded blocks become air\n" +
+ "WARNING: Pasting entities cannot yet be undone!",
+ min = 0,
+ max = 0
+ )
+ @CommandPermissions("worldedit.clipboard.copy")
+ public void copy(Player player, LocalSession session, EditSession editSession,
+ @Selection Region region, @Switch('e') boolean copyEntities,
+ @Switch('m') Mask mask) throws WorldEditException {
+
+ BlockArrayClipboard clipboard = new BlockArrayClipboard(region, player.getUniqueId());
+
+
+ clipboard.setOrigin(session.getPlacementPosition(player));
+ ForwardExtentCopy copy = new ForwardExtentCopy(editSession, region, clipboard, region.getMinimumPoint());
+ if (mask != null) {
+ copy.setSourceMask(mask);
+ }
+ Operations.completeLegacy(copy);
+ session.setClipboard(new ClipboardHolder(clipboard, editSession.getWorld().getWorldData()));
+
+ player.print(region.getArea() + " block(s) were copied.");
+ }
+
+ @Command(
+ aliases = { "/cut" },
+ flags = "em",
+ usage = "[leave-id]",
+ desc = "Cut the selection to the clipboard",
+ help = "Copy the selection to the clipboard\n" +
+ "Flags:\n" +
+ " -e controls whether entities are copied\n" +
+ " -m sets a source mask so that excluded blocks become air\n" +
+ "WARNING: Cutting and pasting entities cannot yet be undone!",
+ min = 0,
+ max = 1
+ )
+ @CommandPermissions("worldedit.clipboard.cut")
+ @Logging(REGION)
+ public void cut(Player player, LocalSession session, EditSession editSession,
+ @Selection Region region, @Optional("air") Pattern leavePattern, @Switch('e') boolean copyEntities,
+ @Switch('m') Mask mask) throws WorldEditException {
+
+ BlockArrayClipboard clipboard = new BlockArrayClipboard(region, player.getUniqueId());
+ clipboard.setOrigin(session.getPlacementPosition(player));
+ ForwardExtentCopy copy = new ForwardExtentCopy(editSession, region, clipboard, region.getMinimumPoint());
+ copy.setSourceFunction(new BlockReplace(editSession, leavePattern));
+ if (mask != null) {
+ copy.setSourceMask(mask);
+ }
+ Operations.completeLegacy(copy);
+ session.setClipboard(new ClipboardHolder(clipboard, editSession.getWorld().getWorldData()));
+
+ player.print(region.getArea() + " block(s) were copied.");
+ }
+
+ @Command(
+ aliases = { "/paste" },
+ usage = "",
+ flags = "sao",
+ desc = "Paste the clipboard's contents",
+ help =
+ "Pastes the clipboard's contents.\n" +
+ "Flags:\n" +
+ " -a skips air blocks\n" +
+ " -o pastes at the original position\n" +
+ " -s selects the region after pasting",
+ min = 0,
+ max = 0
+ )
+ @CommandPermissions("worldedit.clipboard.paste")
+ @Logging(PLACEMENT)
+ public void paste(Player player, LocalSession session, EditSession editSession,
+ @Switch('a') boolean ignoreAirBlocks, @Switch('o') boolean atOrigin,
+ @Switch('s') boolean selectPasted) throws WorldEditException {
+
+ ClipboardHolder holder = session.getClipboard();
+ Clipboard clipboard = holder.getClipboard();
+ Region region = clipboard.getRegion();
+
+ Vector to = atOrigin ? clipboard.getOrigin() : session.getPlacementPosition(player);
+ Operation operation = holder
+ .createPaste(editSession, editSession.getWorld().getWorldData())
+ .to(to)
+ .ignoreAirBlocks(ignoreAirBlocks)
+ .build();
+ Operations.completeLegacy(operation);
+
+ if (selectPasted) {
+ Vector max = to.add(region.getMaximumPoint().subtract(region.getMinimumPoint()));
+ RegionSelector selector = new CuboidRegionSelector(player.getWorld(), to, max);
+ session.setRegionSelector(player.getWorld(), selector);
+ selector.learnChanges();
+ selector.explainRegionAdjust(player, session);
+ }
+
+ player.print("The clipboard has been pasted at " + to);
+ }
+
+ @Command(
+ aliases = { "/rotate" },
+ usage = " [] []",
+ desc = "Rotate the contents of the clipboard",
+ help = "Non-destructively rotate the contents of the clipboard.\n" +
+ "Angles are provided in degrees and a positive angle will result in a clockwise rotation. " +
+ "Multiple rotations can be stacked. Interpolation is not performed so angles should be a multiple of 90 degrees.\n"
+ )
+ @CommandPermissions("worldedit.clipboard.rotate")
+ public void rotate(Player player, LocalSession session, Double yRotate, @Optional Double xRotate, @Optional Double zRotate) throws WorldEditException {
+ if ((yRotate != null && Math.abs(yRotate % 90) > 0.001) ||
+ xRotate != null && Math.abs(xRotate % 90) > 0.001 ||
+ zRotate != null && Math.abs(zRotate % 90) > 0.001) {
+ player.printDebug("Note: Interpolation is not yet supported, so angles that are multiples of 90 is recommended.");
+ }
+
+ ClipboardHolder holder = session.getClipboard();
+ AffineTransform transform = new AffineTransform();
+ transform = transform.rotateY(-(yRotate != null ? yRotate : 0));
+ transform = transform.rotateX(-(xRotate != null ? xRotate : 0));
+ transform = transform.rotateZ(-(zRotate != null ? zRotate : 0));
+ holder.setTransform(holder.getTransform().combine(transform));
+ player.print("The clipboard copy has been rotated.");
+ }
+
+ @Command(
+ aliases = { "/flip" },
+ usage = "[]",
+ desc = "Flip the contents of the clipboard",
+ help =
+ "Flips the contents of the clipboard across the point from which the copy was made.\n",
+ min = 0,
+ max = 1
+ )
+ @CommandPermissions("worldedit.clipboard.flip")
+ public void flip(Player player, LocalSession session, EditSession editSession,
+ @Optional(Direction.AIM) @Direction Vector direction) throws WorldEditException {
+ ClipboardHolder holder = session.getClipboard();
+ Clipboard clipboard = holder.getClipboard();
+ AffineTransform transform = new AffineTransform();
+ transform = transform.scale(direction.positive().multiply(-2).add(1, 1, 1));
+ holder.setTransform(holder.getTransform().combine(transform));
+ player.print("The clipboard copy has been flipped.");
+ }
+
+ @Command(
+ aliases = { "/load" },
+ usage = "",
+ desc = "Load a schematic into your clipboard",
+ min = 0,
+ max = 1
+ )
+ @Deprecated
+ @CommandPermissions("worldedit.clipboard.load")
+ public void load(Actor actor) {
+ actor.printError("This command is no longer used. See //schematic load.");
+ }
+
+ @Command(
+ aliases = { "/save" },
+ usage = "",
+ desc = "Save a schematic into your clipboard",
+ min = 0,
+ max = 1
+ )
+ @Deprecated
+ @CommandPermissions("worldedit.clipboard.save")
+ public void save(Actor actor) {
+ actor.printError("This command is no longer used. See //schematic save.");
+ }
+
+ @Command(
+ aliases = { "clearclipboard" },
+ usage = "",
+ desc = "Clear your clipboard",
+ min = 0,
+ max = 0
+ )
+ @CommandPermissions("worldedit.clipboard.clear")
+ public void clearClipboard(Player player, LocalSession session, EditSession editSession) throws WorldEditException {
+ session.setClipboard(null);
+ player.print("Clipboard cleared.");
+ }
+ public static Class> inject() {
+ return ClipboardCommands.class;
+ }
+}
diff --git a/core/src/main/java/com/sk89q/worldedit/command/SchematicCommands.java b/core/src/main/java/com/sk89q/worldedit/command/SchematicCommands.java
index 96f206d7..0ddde6e1 100644
--- a/core/src/main/java/com/sk89q/worldedit/command/SchematicCommands.java
+++ b/core/src/main/java/com/sk89q/worldedit/command/SchematicCommands.java
@@ -37,6 +37,7 @@ import com.sk89q.worldedit.extent.clipboard.Clipboard;
import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormat;
import com.sk89q.worldedit.extent.clipboard.io.ClipboardReader;
import com.sk89q.worldedit.extent.clipboard.io.ClipboardWriter;
+import com.sk89q.worldedit.extent.clipboard.io.SchematicReader;
import com.sk89q.worldedit.function.operation.Operations;
import com.sk89q.worldedit.math.transform.Transform;
import com.sk89q.worldedit.session.ClipboardHolder;
@@ -117,9 +118,13 @@ public class SchematicCommands {
final ClipboardReader reader = format.getReader(bis);
final WorldData worldData = player.getWorld().getWorldData();
- final Clipboard clipboard = reader.read(player.getWorld().getWorldData());
+ final Clipboard clipboard;
+ if (reader instanceof SchematicReader) {
+ clipboard = ((SchematicReader) reader).read(player.getWorld().getWorldData(), player.getUniqueId());
+ } else {
+ clipboard = reader.read(player.getWorld().getWorldData());
+ }
session.setClipboard(new ClipboardHolder(clipboard, worldData));
-
log.info(player.getName() + " loaded " + filePath);
player.print(filename + " loaded. Paste it with //paste");
}
@@ -160,7 +165,7 @@ public class SchematicCommands {
// If we have a transform, bake it into the copy
if (!transform.isIdentity()) {
final FlattenedClipboardTransform result = FlattenedClipboardTransform.transform(clipboard, transform, holder.getWorldData());
- target = new BlockArrayClipboard(result.getTransformedRegion());
+ target = new BlockArrayClipboard(result.getTransformedRegion(), player.getUniqueId());
target.setOrigin(clipboard.getOrigin());
Operations.completeLegacy(result.copyTo(target));
} else {
diff --git a/core/src/main/java/com/sk89q/worldedit/extent/clipboard/BlockArrayClipboard.java b/core/src/main/java/com/sk89q/worldedit/extent/clipboard/BlockArrayClipboard.java
index 147fad17..84f8a1f8 100644
--- a/core/src/main/java/com/sk89q/worldedit/extent/clipboard/BlockArrayClipboard.java
+++ b/core/src/main/java/com/sk89q/worldedit/extent/clipboard/BlockArrayClipboard.java
@@ -37,6 +37,7 @@ import com.sk89q.worldedit.world.biome.BaseBiome;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.UUID;
import javax.annotation.Nullable;
@@ -64,6 +65,17 @@ public class BlockArrayClipboard implements Clipboard {
private Vector origin;
+ public BlockArrayClipboard(Region region) {
+ checkNotNull(region);
+ this.region = region.clone();
+ this.size = getDimensions();
+ this.IMP = Settings.STORE_CLIPBOARD_ON_DISK ? new DiskOptimizedClipboard(size.getBlockX(), size.getBlockY(), size.getBlockZ()) : new MemoryOptimizedClipboard(size.getBlockX(), size.getBlockY(), size.getBlockZ());
+ this.origin = region.getMinimumPoint();
+ this.mx = origin.getBlockX();
+ this.my = origin.getBlockY();
+ this.mz = origin.getBlockZ();
+ }
+
/**
* Create a new instance.
*
@@ -71,11 +83,22 @@ public class BlockArrayClipboard implements Clipboard {
*
* @param region the bounding region
*/
- public BlockArrayClipboard(Region region) {
+ public BlockArrayClipboard(Region region, UUID clipboardId) {
checkNotNull(region);
this.region = region.clone();
this.size = getDimensions();
- this.IMP = Settings.STORE_CLIPBOARD_ON_DISK ? new DiskOptimizedClipboard(size.getBlockX(), size.getBlockY(), size.getBlockZ()) : new MemoryOptimizedClipboard(size.getBlockX(), size.getBlockY(), size.getBlockZ());
+ this.IMP = Settings.STORE_CLIPBOARD_ON_DISK ? new DiskOptimizedClipboard(size.getBlockX(), size.getBlockY(), size.getBlockZ(), clipboardId) : new MemoryOptimizedClipboard(size.getBlockX(), size.getBlockY(), size.getBlockZ());
+ this.origin = region.getMinimumPoint();
+ this.mx = origin.getBlockX();
+ this.my = origin.getBlockY();
+ this.mz = origin.getBlockZ();
+ }
+
+ public BlockArrayClipboard(Region region, DiskOptimizedClipboard clipboard) {
+ checkNotNull(region);
+ this.region = region.clone();
+ this.size = getDimensions();
+ this.IMP = clipboard;
this.origin = region.getMinimumPoint();
this.mx = origin.getBlockX();
this.my = origin.getBlockY();
@@ -95,6 +118,7 @@ public class BlockArrayClipboard implements Clipboard {
@Override
public void setOrigin(Vector origin) {
this.origin = origin;
+ IMP.setOrigin(origin.subtract(region.getMinimumPoint()));
}
@Override
diff --git a/core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/SchematicReader.java b/core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/SchematicReader.java
index ed4a7014..0a8130d6 100644
--- a/core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/SchematicReader.java
+++ b/core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/SchematicReader.java
@@ -44,6 +44,7 @@ import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nullable;
@@ -71,6 +72,10 @@ public class SchematicReader implements ClipboardReader {
@Override
public Clipboard read(WorldData data) throws IOException {
+ return read(data, UUID.randomUUID());
+ }
+
+ public Clipboard read(WorldData data, UUID clipboardId) throws IOException {
// Schematic tag
NamedTag rootTag = inputStream.readNamedTag();
if (!rootTag.getName().equals("Schematic")) {
@@ -171,7 +176,7 @@ public class SchematicReader implements ClipboardReader {
tileEntitiesMap.put(vec, values);
}
- BlockArrayClipboard clipboard = new BlockArrayClipboard(region);
+ BlockArrayClipboard clipboard = new BlockArrayClipboard(region, clipboardId);
clipboard.setOrigin(origin);
// Don't log a torrent of errors
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 00000000..5ccda13e
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 00000000..b2c6e7a0
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Mon Feb 22 17:40:44 PST 2016
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.11-bin.zip