Finish clipboard on disk
This commit is contained in:
parent
f5bbd59602
commit
57696f25f4
27
README.md
27
README.md
@ -1,4 +1,23 @@
|
||||
# Overview
|
||||
Optimize worldedit
|
||||
# Main resource page
|
||||
https://www.spigotmc.org/resources/13932/
|
||||
# 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
|
@ -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();
|
||||
|
@ -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"),
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 <code>BufferedRandomAccessFile</code> is like a
|
||||
* <code>RandomAccessFile</code>, but it uses a private buffer so that most
|
||||
* operations do not require a disk access.
|
||||
* <P>
|
||||
*
|
||||
* Note: The operations on this class are unmonitored. Also, the correct
|
||||
* functioning of the <code>RandomAccessFile</code> 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 <code>BufferedRandomAccessFile</code> on <code>file</code>
|
||||
* in mode <code>mode</code>, 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 <code>BufferedRandomAccessFile</code> on the file named
|
||||
* <code>name</code> in mode <code>mode</code>, 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 <code>this.curr</code> at position <code>pos</code>.
|
||||
* If <code>pos</code> does not fall in the current buffer, it flushes the
|
||||
* current buffer and loads the correct one.<p>
|
||||
*
|
||||
* On exit from this routine <code>this.curr == this.hi</code> iff <code>pos</code>
|
||||
* 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;
|
||||
}
|
||||
}
|
@ -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<T> {
|
||||
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<T> {
|
||||
if (world == null) {
|
||||
return;
|
||||
}
|
||||
TaskManager.IMP.async(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
UUID uuid = getUUID();
|
||||
List<Integer> 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<Integer> 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));
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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<IntegerTrio, CompoundTag> nbtMap;
|
||||
private final HashSet<ClipboardEntity> 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;
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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<ClipboardEntity> 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<ClipboardEntity>();
|
||||
entities = new HashSet<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -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<File> task) {
|
||||
if (directory.exists()) {
|
||||
File[] files = directory.listFiles();
|
||||
|
@ -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<IntegerTrio, CompoundTag> nbtMap;
|
||||
public List<CopiedEntity> entities = new ArrayList<CopiedEntity>();
|
||||
public List<CopiedEntity> 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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,263 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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 = "<y-axis> [<x-axis>] [<z-axis>]",
|
||||
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 = "[<direction>]",
|
||||
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 = "<filename>",
|
||||
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 = "<filename>",
|
||||
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;
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
6
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
6
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@ -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
|
Loading…
Reference in New Issue
Block a user