mirror of
https://github.com/boy0001/FastAsyncWorldedit.git
synced 2024-11-28 21:56:33 +01:00
API and brush improvements.
This commit is contained in:
parent
70362d348f
commit
14dd048662
@ -23,11 +23,14 @@ import com.boydti.fawe.object.FaweCommand;
|
|||||||
import com.boydti.fawe.object.FawePlayer;
|
import com.boydti.fawe.object.FawePlayer;
|
||||||
import com.boydti.fawe.regions.FaweMaskManager;
|
import com.boydti.fawe.regions.FaweMaskManager;
|
||||||
import com.boydti.fawe.util.FaweQueue;
|
import com.boydti.fawe.util.FaweQueue;
|
||||||
|
import com.boydti.fawe.util.ReflectionUtils;
|
||||||
import com.boydti.fawe.util.StringMan;
|
import com.boydti.fawe.util.StringMan;
|
||||||
import com.boydti.fawe.util.TaskManager;
|
import com.boydti.fawe.util.TaskManager;
|
||||||
import com.sk89q.worldedit.EditSession;
|
import com.sk89q.worldedit.EditSession;
|
||||||
import com.sk89q.worldedit.bukkit.WorldEditPlugin;
|
import com.sk89q.worldedit.bukkit.WorldEditPlugin;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
@ -64,6 +67,14 @@ public class FaweBukkit extends JavaPlugin implements IFawe, Listener {
|
|||||||
try {
|
try {
|
||||||
Bukkit.getPluginManager().registerEvents(this, this);
|
Bukkit.getPluginManager().registerEvents(this, this);
|
||||||
Fawe.set(this);
|
Fawe.set(this);
|
||||||
|
if (Bukkit.getVersion().contains("git-Spigot") && FaweAPI.checkVersion(this.getVersion(), 1, 7, 10)) {
|
||||||
|
debug("====== USE PAPER SPIGOT ======");
|
||||||
|
debug("DOWNLOAD: https://tcpr.ca/downloads/paperspigot");
|
||||||
|
debug("GUIDE: https://www.spigotmc.org/threads/21726/");
|
||||||
|
debug(" - This is only a recommendation");
|
||||||
|
debug("==============================");
|
||||||
|
}
|
||||||
|
|
||||||
} catch (final Throwable e) {
|
} catch (final Throwable e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
this.getServer().shutdown();
|
this.getServer().shutdown();
|
||||||
@ -204,6 +215,19 @@ public class FaweBukkit extends JavaPlugin implements IFawe, Listener {
|
|||||||
return new BukkitQueue_1_8(world);
|
return new BukkitQueue_1_8(world);
|
||||||
} catch (Throwable ignore) {}
|
} catch (Throwable ignore) {}
|
||||||
if (hasNMS) {
|
if (hasNMS) {
|
||||||
|
try {
|
||||||
|
ReflectionUtils.init();
|
||||||
|
Field fieldDirtyCount = ReflectionUtils.getRefClass("{nms}.PlayerChunk").getField("dirtyCount").getRealField();
|
||||||
|
fieldDirtyCount.setAccessible(true);
|
||||||
|
int mod = fieldDirtyCount.getModifiers();
|
||||||
|
if ((mod & Modifier.VOLATILE) == 0) {
|
||||||
|
Field modifiersField = Field.class.getDeclaredField("modifiers");
|
||||||
|
modifiersField.setAccessible(true);
|
||||||
|
modifiersField.setInt(fieldDirtyCount, mod + Modifier.VOLATILE);
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
debug("====== NO NMS BLOCK PLACER FOUND ======");
|
debug("====== NO NMS BLOCK PLACER FOUND ======");
|
||||||
debug("FAWE couldn't find a fast block placer");
|
debug("FAWE couldn't find a fast block placer");
|
||||||
debug("Bukkit version: " + Bukkit.getVersion());
|
debug("Bukkit version: " + Bukkit.getVersion());
|
||||||
|
@ -6,6 +6,7 @@ import com.boydti.fawe.bukkit.v1_8.BukkitChunk_1_8;
|
|||||||
import com.boydti.fawe.config.Settings;
|
import com.boydti.fawe.config.Settings;
|
||||||
import com.boydti.fawe.object.FaweChunk;
|
import com.boydti.fawe.object.FaweChunk;
|
||||||
import com.boydti.fawe.object.IntegerPair;
|
import com.boydti.fawe.object.IntegerPair;
|
||||||
|
import com.boydti.fawe.object.RunnableVal;
|
||||||
import com.boydti.fawe.object.exception.FaweException;
|
import com.boydti.fawe.object.exception.FaweException;
|
||||||
import com.boydti.fawe.util.TaskManager;
|
import com.boydti.fawe.util.TaskManager;
|
||||||
import com.sk89q.worldedit.LocalWorld;
|
import com.sk89q.worldedit.LocalWorld;
|
||||||
@ -14,7 +15,6 @@ import com.sk89q.worldedit.bukkit.BukkitUtil;
|
|||||||
import com.sk89q.worldedit.world.biome.BaseBiome;
|
import com.sk89q.worldedit.world.biome.BaseBiome;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.concurrent.LinkedBlockingDeque;
|
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.Chunk;
|
import org.bukkit.Chunk;
|
||||||
import org.bukkit.World;
|
import org.bukkit.World;
|
||||||
@ -26,27 +26,9 @@ import org.bukkit.plugin.Plugin;
|
|||||||
import org.spigotmc.AsyncCatcher;
|
import org.spigotmc.AsyncCatcher;
|
||||||
|
|
||||||
public class BukkitQueue_All extends BukkitQueue_0 {
|
public class BukkitQueue_All extends BukkitQueue_0 {
|
||||||
public final LinkedBlockingDeque<IntegerPair> loadQueue = new LinkedBlockingDeque<>();
|
|
||||||
|
|
||||||
public BukkitQueue_All(final String world) {
|
public BukkitQueue_All(final String world) {
|
||||||
super(world);
|
super(world);
|
||||||
TaskManager.IMP.repeat(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
synchronized (loadQueue) {
|
|
||||||
while (loadQueue.size() > 0) {
|
|
||||||
IntegerPair loc = loadQueue.poll();
|
|
||||||
if (bukkitWorld == null) {
|
|
||||||
bukkitWorld = Bukkit.getServer().getWorld(world);
|
|
||||||
}
|
|
||||||
if (!bukkitWorld.isChunkLoaded(loc.x, loc.z)) {
|
|
||||||
bukkitWorld.loadChunk(loc.x, loc.z, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
loadQueue.notifyAll();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, 1);
|
|
||||||
if (getClass() == BukkitQueue_All.class) {
|
if (getClass() == BukkitQueue_All.class) {
|
||||||
TaskManager.IMP.task(new Runnable() {
|
TaskManager.IMP.task(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
@ -213,10 +195,6 @@ public class BukkitQueue_All extends BukkitQueue_0 {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void loadChunk(IntegerPair chunk) {
|
|
||||||
loadQueue.add(chunk);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int lastChunkX = Integer.MIN_VALUE;
|
public int lastChunkX = Integer.MIN_VALUE;
|
||||||
public int lastChunkZ = Integer.MIN_VALUE;
|
public int lastChunkZ = Integer.MIN_VALUE;
|
||||||
public int lastChunkY = Integer.MIN_VALUE;
|
public int lastChunkY = Integer.MIN_VALUE;
|
||||||
@ -241,6 +219,15 @@ public class BukkitQueue_All extends BukkitQueue_0 {
|
|||||||
return combined;
|
return combined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final RunnableVal<IntegerPair> loadChunk = new RunnableVal<IntegerPair>() {
|
||||||
|
@Override
|
||||||
|
public void run(IntegerPair coord) {
|
||||||
|
bukkitWorld.loadChunk(coord.x, coord.z, true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
long average = 0;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getCombinedId4Data(int x, int y, int z) throws FaweException.FaweChunkLoadException {
|
public int getCombinedId4Data(int x, int y, int z) throws FaweException.FaweChunkLoadException {
|
||||||
if (y < 0 || y > 255) {
|
if (y < 0 || y > 255) {
|
||||||
@ -256,24 +243,26 @@ public class BukkitQueue_All extends BukkitQueue_0 {
|
|||||||
lastChunkX = cx;
|
lastChunkX = cx;
|
||||||
lastChunkZ = cz;
|
lastChunkZ = cz;
|
||||||
if (!bukkitWorld.isChunkLoaded(cx, cz)) {
|
if (!bukkitWorld.isChunkLoaded(cx, cz)) {
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
boolean sync = Thread.currentThread() == Fawe.get().getMainThread();
|
boolean sync = Thread.currentThread() == Fawe.get().getMainThread();
|
||||||
if (sync) {
|
if (sync) {
|
||||||
bukkitWorld.loadChunk(cx, cz, true);
|
bukkitWorld.loadChunk(cx, cz, true);
|
||||||
} else if (Settings.CHUNK_WAIT > 0) {
|
} else if (Settings.CHUNK_WAIT > 0) {
|
||||||
synchronized (loadQueue) {
|
loadChunk.value = new IntegerPair(cx, cz);
|
||||||
loadQueue.add(new IntegerPair(cx, cz));
|
TaskManager.IMP.sync(loadChunk, Settings.CHUNK_WAIT);
|
||||||
try {
|
|
||||||
loadQueue.wait(Settings.CHUNK_WAIT);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!bukkitWorld.isChunkLoaded(cx, cz)) {
|
if (!bukkitWorld.isChunkLoaded(cx, cz)) {
|
||||||
throw new FaweException.FaweChunkLoadException();
|
throw new FaweException.FaweChunkLoadException();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
long diff = System.currentTimeMillis() - start;
|
||||||
|
if (average == 0) {
|
||||||
|
average = diff;
|
||||||
|
} else {
|
||||||
|
average = ((average * 15) + diff) / 16;
|
||||||
|
}
|
||||||
|
System.out.println(average);
|
||||||
}
|
}
|
||||||
lastChunk = getCachedChunk(cx, cz);
|
lastChunk = getCachedChunk(cx, cz);
|
||||||
lastSection = getCachedSection(lastChunk, cy);
|
lastSection = getCachedSection(lastChunk, cy);
|
||||||
|
@ -24,6 +24,7 @@ import com.sk89q.worldedit.WorldEdit;
|
|||||||
import com.sk89q.worldedit.command.SchematicCommands;
|
import com.sk89q.worldedit.command.SchematicCommands;
|
||||||
import com.sk89q.worldedit.command.ScriptingCommands;
|
import com.sk89q.worldedit.command.ScriptingCommands;
|
||||||
import com.sk89q.worldedit.extension.platform.CommandManager;
|
import com.sk89q.worldedit.extension.platform.CommandManager;
|
||||||
|
import com.sk89q.worldedit.extension.platform.PlatformManager;
|
||||||
import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard;
|
import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard;
|
||||||
import com.sk89q.worldedit.extent.transform.BlockTransformExtent;
|
import com.sk89q.worldedit.extent.transform.BlockTransformExtent;
|
||||||
import com.sk89q.worldedit.function.operation.Operations;
|
import com.sk89q.worldedit.function.operation.Operations;
|
||||||
@ -240,6 +241,7 @@ public class Fawe {
|
|||||||
Vector.inject();
|
Vector.inject();
|
||||||
try {
|
try {
|
||||||
CommandManager.inject();
|
CommandManager.inject();
|
||||||
|
PlatformManager.inject();
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
debug("====== UPDATE WORLDEDIT TO 6.1.1 ======");
|
debug("====== UPDATE WORLDEDIT TO 6.1.1 ======");
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
@ -255,16 +257,18 @@ public class Fawe {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
Native.load();
|
Native.load();
|
||||||
String arch = System.getenv("PROCESSOR_ARCHITECTURE");
|
try {
|
||||||
String wow64Arch = System.getenv("PROCESSOR_ARCHITEW6432");
|
String arch = System.getenv("PROCESSOR_ARCHITECTURE");
|
||||||
boolean x86OS = arch.endsWith("64") || wow64Arch != null && wow64Arch.endsWith("64") ? false : true;
|
String wow64Arch = System.getenv("PROCESSOR_ARCHITEW6432");
|
||||||
boolean x86JVM = System.getProperty("sun.arch.data.model").equals("32");
|
boolean x86OS = arch.endsWith("64") || wow64Arch != null && wow64Arch.endsWith("64") ? false : true;
|
||||||
if (x86OS != x86JVM) {
|
boolean x86JVM = System.getProperty("sun.arch.data.model").equals("32");
|
||||||
debug("====== UPGRADE TO 64-BIT JAVA ======");
|
if (x86OS != x86JVM) {
|
||||||
debug("You are running 32-bit Java on a 64-bit machine");
|
debug("====== UPGRADE TO 64-BIT JAVA ======");
|
||||||
debug(" - This is a recommendation");
|
debug("You are running 32-bit Java on a 64-bit machine");
|
||||||
debug("====================================");
|
debug(" - This is only a recommendation");
|
||||||
}
|
debug("====================================");
|
||||||
|
}
|
||||||
|
} catch (Throwable ignore) {}
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
debug("====== LZ4 COMPRESSION BINDING NOT FOUND ======");
|
debug("====== LZ4 COMPRESSION BINDING NOT FOUND ======");
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
@ -277,7 +281,7 @@ public class Fawe {
|
|||||||
if (getJavaVersion() < 1.8) {
|
if (getJavaVersion() < 1.8) {
|
||||||
debug("====== UPGRADE TO JAVA 8 ======");
|
debug("====== UPGRADE TO JAVA 8 ======");
|
||||||
debug("You are running " + System.getProperty("java.version"));
|
debug("You are running " + System.getProperty("java.version"));
|
||||||
debug(" - This is a recommendation");
|
debug(" - This is only a recommendation");
|
||||||
debug("====================================");
|
debug("====================================");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,27 @@
|
|||||||
package com.boydti.fawe;
|
package com.boydti.fawe;
|
||||||
|
|
||||||
|
import com.boydti.fawe.config.BBC;
|
||||||
|
import com.boydti.fawe.config.Settings;
|
||||||
import com.boydti.fawe.object.FaweLocation;
|
import com.boydti.fawe.object.FaweLocation;
|
||||||
|
import com.boydti.fawe.object.FawePlayer;
|
||||||
|
import com.boydti.fawe.object.RegionWrapper;
|
||||||
|
import com.boydti.fawe.object.changeset.DiskStorageHistory;
|
||||||
|
import com.boydti.fawe.regions.FaweMaskManager;
|
||||||
import com.boydti.fawe.util.FaweQueue;
|
import com.boydti.fawe.util.FaweQueue;
|
||||||
|
import com.boydti.fawe.util.MemUtil;
|
||||||
import com.boydti.fawe.util.SetQueue;
|
import com.boydti.fawe.util.SetQueue;
|
||||||
import com.boydti.fawe.util.TaskManager;
|
import com.boydti.fawe.util.TaskManager;
|
||||||
|
import com.boydti.fawe.util.WEManager;
|
||||||
|
import com.intellectualcrafters.plot.object.PseudoRandom;
|
||||||
import com.sk89q.jnbt.ByteArrayTag;
|
import com.sk89q.jnbt.ByteArrayTag;
|
||||||
import com.sk89q.jnbt.IntTag;
|
import com.sk89q.jnbt.IntTag;
|
||||||
import com.sk89q.jnbt.NBTInputStream;
|
import com.sk89q.jnbt.NBTInputStream;
|
||||||
import com.sk89q.jnbt.ShortTag;
|
import com.sk89q.jnbt.ShortTag;
|
||||||
import com.sk89q.jnbt.Tag;
|
import com.sk89q.jnbt.Tag;
|
||||||
|
import com.sk89q.worldedit.WorldEdit;
|
||||||
|
import com.sk89q.worldedit.WorldEditException;
|
||||||
|
import com.sk89q.worldedit.extent.Extent;
|
||||||
|
import com.sk89q.worldedit.world.World;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -16,7 +29,15 @@ import java.io.InputStream;
|
|||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.nio.channels.Channels;
|
import java.nio.channels.Channels;
|
||||||
import java.nio.channels.ReadableByteChannel;
|
import java.nio.channels.ReadableByteChannel;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
import java.util.zip.GZIPInputStream;
|
import java.util.zip.GZIPInputStream;
|
||||||
import org.bukkit.Chunk;
|
import org.bukkit.Chunk;
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
@ -24,11 +45,229 @@ import org.bukkit.Location;
|
|||||||
/**
|
/**
|
||||||
* The FaweAPI class offers a few useful functions.<br>
|
* The FaweAPI class offers a few useful functions.<br>
|
||||||
* - This class is not intended to replace the WorldEdit API<br>
|
* - This class is not intended to replace the WorldEdit API<br>
|
||||||
|
* - With FAWE installed, you can use the EditSession and other WorldEdit classes from an async thread.<br>
|
||||||
* <br>
|
* <br>
|
||||||
* FaweAPI.[some method]
|
* FaweAPI.[some method]
|
||||||
*/
|
*/
|
||||||
public class FaweAPI {
|
public class FaweAPI {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The TaskManager has some useful methods for doing things asynchronously
|
||||||
|
* @return TaskManager
|
||||||
|
*/
|
||||||
|
public TaskManager getTaskManager() {
|
||||||
|
return TaskManager.IMP;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap some object into a FawePlayer<br>
|
||||||
|
* - org.bukkit.entity.Player
|
||||||
|
* - org.spongepowered.api.entity.living.player
|
||||||
|
* - com.sk89q.worldedit.entity.Player
|
||||||
|
* - String (name)
|
||||||
|
* - UUID (player UUID)
|
||||||
|
* @param obj
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public FawePlayer wrapPlayer(Object obj) {
|
||||||
|
return FawePlayer.wrap(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* You can either use a FaweQueue or an EditSession to change blocks<br>
|
||||||
|
* - The FaweQueue skips a bit of overhead so it's faster<br>
|
||||||
|
* - The WorldEdit EditSession can do a lot more<br>
|
||||||
|
* Remember to enqueue it when you're done!<br>
|
||||||
|
* @see com.boydti.fawe.util.FaweQueue#enqueue()
|
||||||
|
* @param worldName The name of the world
|
||||||
|
* @param autoqueue If it should start dispatching before you enqueue it.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public FaweQueue createQueue(String worldName, boolean autoqueue) {
|
||||||
|
return SetQueue.IMP.getNewQueue(worldName, autoqueue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a list of supported protection plugin masks.
|
||||||
|
* @return Set of FaweMaskManager
|
||||||
|
*/
|
||||||
|
public Set<FaweMaskManager> getMaskManagers() {
|
||||||
|
return new HashSet<>(WEManager.IMP.managers);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the server has more than the configured low memory threshold
|
||||||
|
* @return True if the server has limited memory
|
||||||
|
*/
|
||||||
|
public boolean isMemoryLimited() {
|
||||||
|
return MemUtil.isMemoryLimited();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If you just need things to look random, use this faster alternative
|
||||||
|
* @return PseudoRandom
|
||||||
|
*/
|
||||||
|
public PseudoRandom getFastRandom() {
|
||||||
|
return new PseudoRandom();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a player's allowed WorldEdit region
|
||||||
|
* @param player
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Set<RegionWrapper> getRegions(FawePlayer player) {
|
||||||
|
return WEManager.IMP.getMask(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel the edit with the following extent<br>
|
||||||
|
* - The extent must be the one being used by an EditSession, otherwise an error may be thrown <br>
|
||||||
|
* - Insert an extent into the EditSession using the EditSessionEvent: http://wiki.sk89q.com/wiki/WorldEdit/API/Hooking_EditSession <br>
|
||||||
|
* @see com.sk89q.worldedit.EditSession#getFaweExtent() To get the FaweExtent for an EditSession
|
||||||
|
* @param extent
|
||||||
|
* @param reason
|
||||||
|
*/
|
||||||
|
public void cancelEdit(Extent extent, BBC reason) {
|
||||||
|
try {
|
||||||
|
WEManager.IMP.cancelEdit(extent, reason);
|
||||||
|
} catch (WorldEditException ignore) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the DiskStorageHistory object representing a File
|
||||||
|
* @param file
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public DiskStorageHistory getChangeSetFromFile(File file) {
|
||||||
|
if (!file.exists() || file.isDirectory()) {
|
||||||
|
throw new IllegalArgumentException("Not a file!");
|
||||||
|
}
|
||||||
|
if (!file.getName().toLowerCase().endsWith(".bd")) {
|
||||||
|
throw new IllegalArgumentException("Not a BD file!");
|
||||||
|
}
|
||||||
|
if (Settings.STORE_HISTORY_ON_DISK) {
|
||||||
|
throw new IllegalArgumentException("History on disk not enabled!");
|
||||||
|
}
|
||||||
|
String[] path = file.getPath().split(File.separator);
|
||||||
|
if (path.length < 3) {
|
||||||
|
throw new IllegalArgumentException("Not in history directory!");
|
||||||
|
}
|
||||||
|
String worldName = path[path.length - 3];
|
||||||
|
String uuidString = path[path.length - 2];
|
||||||
|
World world = null;
|
||||||
|
for (World current : WorldEdit.getInstance().getServer().getWorlds()) {
|
||||||
|
if (current.getName().equals(worldName)) {
|
||||||
|
world = current;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (world == null) {
|
||||||
|
throw new IllegalArgumentException("Corresponding world does not exist: " + worldName);
|
||||||
|
}
|
||||||
|
UUID uuid;
|
||||||
|
try {
|
||||||
|
uuid = UUID.fromString(uuidString);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
throw new IllegalArgumentException("Invalid UUID from file path: " + uuidString);
|
||||||
|
}
|
||||||
|
DiskStorageHistory history = new DiskStorageHistory(world, uuid, Integer.parseInt(file.getName().split("\\.")[0]));
|
||||||
|
return history;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used in the RollBack to generate a list of DiskStorageHistory objects<br>
|
||||||
|
* - Note: An edit outside the radius may be included if it overlaps with an edit inside that depends on it.
|
||||||
|
* @param origin - The origin location
|
||||||
|
* @param user - The uuid (may be null)
|
||||||
|
* @param radius - The radius from the origin of the edit
|
||||||
|
* @param timediff - The max age of the file in milliseconds
|
||||||
|
* @param shallow - If shallow is true, FAWE will only read the first Settings.BUFFER_SIZE bytes to obtain history info<br>
|
||||||
|
* Reading only part of the file will result in unreliable bounds info for large edits
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static List<DiskStorageHistory> getBDFiles(FaweLocation origin, UUID user, int radius, long timediff, boolean shallow) {
|
||||||
|
File history = new File(Fawe.imp().getDirectory(), "history" + File.separator + origin.world);
|
||||||
|
if (!history.exists()) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
ArrayList<File> files = new ArrayList<>();
|
||||||
|
for (File userFile : history.listFiles()) {
|
||||||
|
if (!userFile.isDirectory()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
UUID userUUID;
|
||||||
|
try {
|
||||||
|
userUUID = UUID.fromString(userFile.getName());
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (user != null && !userUUID.equals(user)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ArrayList<Integer> ids = new ArrayList<>();
|
||||||
|
for (File file : userFile.listFiles()) {
|
||||||
|
if (file.getName().endsWith(".bd")) {
|
||||||
|
if (timediff >= Integer.MAX_VALUE || now - file.lastModified() <= timediff) {
|
||||||
|
files.add(file);
|
||||||
|
if (files.size() > 2048) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
World world = origin.getWorld();
|
||||||
|
Collections.sort(files, new Comparator<File>() {
|
||||||
|
@Override
|
||||||
|
public int compare(File a, File b) {
|
||||||
|
long value = a.lastModified() - b.lastModified();
|
||||||
|
return value == 0 ? 0 : value < 0 ? -1 : 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
RegionWrapper bounds = new RegionWrapper(origin.x - radius, origin.x + radius, origin.z - radius, origin.z + radius);
|
||||||
|
RegionWrapper boundsPlus = new RegionWrapper(bounds.minX - 64, bounds.maxX + 512, bounds.minZ - 64, bounds.maxZ + 512);
|
||||||
|
HashSet<RegionWrapper> regionSet = new HashSet<RegionWrapper>(Arrays.asList(bounds));
|
||||||
|
ArrayList<DiskStorageHistory> result = new ArrayList<>();
|
||||||
|
for (File file : files) {
|
||||||
|
UUID uuid = UUID.fromString(file.getParentFile().getName());
|
||||||
|
DiskStorageHistory dsh = new DiskStorageHistory(world, uuid, Integer.parseInt(file.getName().split("\\.")[0]));
|
||||||
|
DiskStorageHistory.DiskStorageSummary summary = dsh.summarize(boundsPlus, shallow);
|
||||||
|
RegionWrapper region = new RegionWrapper(summary.minX, summary.maxX, summary.minZ, summary.maxZ);
|
||||||
|
boolean encompassed = false;
|
||||||
|
boolean isIn = false;
|
||||||
|
for (RegionWrapper allowed : regionSet) {
|
||||||
|
isIn = isIn || allowed.intersects(region);
|
||||||
|
if (encompassed = allowed.isIn(region.minX, region.maxX) && allowed.isIn(region.minZ, region.maxZ)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isIn) {
|
||||||
|
result.add(0, dsh);
|
||||||
|
if (!encompassed) {
|
||||||
|
regionSet.add(region);
|
||||||
|
}
|
||||||
|
if (shallow && result.size() > 64) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The DiskStorageHistory class is what FAWE uses to represent the undo on disk.
|
||||||
|
* @see com.boydti.fawe.object.changeset.DiskStorageHistory#toEditSession(com.sk89q.worldedit.entity.Player)
|
||||||
|
* @param world
|
||||||
|
* @param uuid
|
||||||
|
* @param index
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public DiskStorageHistory getChangeSetFromDisk(World world, UUID uuid, int index) {
|
||||||
|
return new DiskStorageHistory(world, uuid, index);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compare two versions
|
* Compare two versions
|
||||||
* @param version
|
* @param version
|
||||||
@ -41,12 +280,23 @@ public class FaweAPI {
|
|||||||
return (version[0] > major) || ((version[0] == major) && (version[1] > minor)) || ((version[0] == major) && (version[1] == minor) && (version[2] >= minor2));
|
return (version[0] > major) || ((version[0] == major) && (version[1] > minor)) || ((version[0] == major) && (version[1] == minor) && (version[2] >= minor2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fix the lighting in a chunk
|
||||||
|
* @param world
|
||||||
|
* @param x
|
||||||
|
* @param z
|
||||||
|
* @param fixAll
|
||||||
|
*/
|
||||||
public static void fixLighting(String world, int x, int z, final boolean fixAll) {
|
public static void fixLighting(String world, int x, int z, final boolean fixAll) {
|
||||||
FaweQueue queue = SetQueue.IMP.getNewQueue(world, false);
|
FaweQueue queue = SetQueue.IMP.getNewQueue(world, false);
|
||||||
queue.fixLighting(queue.getChunk(x, z), fixAll);
|
queue.fixLighting(queue.getChunk(x, z), fixAll);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fix the lighting in a chunk
|
||||||
|
* @param chunk
|
||||||
|
* @param fixAll
|
||||||
|
*/
|
||||||
public static void fixLighting(final Chunk chunk, final boolean fixAll) {
|
public static void fixLighting(final Chunk chunk, final boolean fixAll) {
|
||||||
FaweQueue queue = SetQueue.IMP.getNewQueue(chunk.getWorld().getName(), false);
|
FaweQueue queue = SetQueue.IMP.getNewQueue(chunk.getWorld().getName(), false);
|
||||||
queue.fixLighting(queue.getChunk(chunk.getX(), chunk.getZ()), fixAll);
|
queue.fixLighting(queue.getChunk(chunk.getX(), chunk.getZ()), fixAll);
|
||||||
@ -55,59 +305,49 @@ public class FaweAPI {
|
|||||||
/**
|
/**
|
||||||
* If a schematic is too large to be pasted normally<br>
|
* If a schematic is too large to be pasted normally<br>
|
||||||
* - Skips any block history
|
* - Skips any block history
|
||||||
* - Ignores some block data
|
* - Ignores nbt
|
||||||
* - No, it's not streaming it from disk, but it is a lot faster
|
* - No, technically I haven't added proper streaming yet (WIP)
|
||||||
* @param file
|
* @param file
|
||||||
* @param loc
|
* @param loc
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public static void streamSchematicAsync(final File file, final Location loc) {
|
public static void streamSchematic(final File file, final Location loc) {
|
||||||
final FaweLocation fl = new FaweLocation(loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ());
|
final FaweLocation fl = new FaweLocation(loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ());
|
||||||
streamSchematicAsync(file, fl);
|
streamSchematic(file, fl);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If a schematic is too large to be pasted normally<br>
|
* If a schematic is too large to be pasted normally<br>
|
||||||
* - Skips any block history
|
* - Skips any block history
|
||||||
* - Ignores some block data
|
* - Ignores nbt
|
||||||
* @param file
|
* @param file
|
||||||
* @param loc
|
* @param loc
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public static void streamSchematicAsync(final File file, final FaweLocation loc) {
|
public static void streamSchematic(final File file, final FaweLocation loc) {
|
||||||
TaskManager.IMP.async(new Runnable() {
|
try {
|
||||||
@Override
|
final FileInputStream is = new FileInputStream(file);
|
||||||
public void run() {
|
streamSchematic(is, loc);
|
||||||
try {
|
} catch (final IOException e) {
|
||||||
final FileInputStream is = new FileInputStream(file);
|
e.printStackTrace();
|
||||||
streamSchematic(is, loc);
|
}
|
||||||
} catch (final IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If a schematic is too large to be pasted normally<br>
|
* If a schematic is too large to be pasted normally<br>
|
||||||
* - Skips any block history
|
* - Skips any block history
|
||||||
* - Ignores some block data
|
* - Ignores nbt
|
||||||
* @param url
|
* @param url
|
||||||
* @param loc
|
* @param loc
|
||||||
*/
|
*/
|
||||||
public static void streamSchematicAsync(final URL url, final FaweLocation loc) {
|
public static void streamSchematic(final URL url, final FaweLocation loc) {
|
||||||
TaskManager.IMP.async(new Runnable() {
|
try {
|
||||||
@Override
|
final ReadableByteChannel rbc = Channels.newChannel(url.openStream());
|
||||||
public void run() {
|
final InputStream is = Channels.newInputStream(rbc);
|
||||||
try {
|
streamSchematic(is, loc);
|
||||||
final ReadableByteChannel rbc = Channels.newChannel(url.openStream());
|
} catch (final IOException e) {
|
||||||
final InputStream is = Channels.newInputStream(rbc);
|
e.printStackTrace();
|
||||||
streamSchematic(is, loc);
|
}
|
||||||
} catch (final IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -241,10 +481,34 @@ public class FaweAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set a task to run when the async queue is empty
|
* Set a task to run when the global queue (SetQueue class) is empty
|
||||||
* @param whenDone
|
* @param whenDone
|
||||||
*/
|
*/
|
||||||
public static void addTask(final Runnable whenDone) {
|
public static void addTask(final Runnable whenDone) {
|
||||||
SetQueue.IMP.addTask(whenDone);
|
SetQueue.IMP.addTask(whenDone);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Have a task run when the server is low on memory (configured threshold)
|
||||||
|
* @param run
|
||||||
|
*/
|
||||||
|
public static void addMemoryLimitedTask(Runnable run) {
|
||||||
|
MemUtil.addMemoryLimitedTask(run);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Have a task run when the server is no longer low on memory (configured threshold)
|
||||||
|
* @param run
|
||||||
|
*/
|
||||||
|
public static void addMemoryPlentifulTask(Runnable run) {
|
||||||
|
MemUtil.addMemoryPlentifulTask(run);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see BBC
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public BBC[] getTranslations() {
|
||||||
|
return BBC.values();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ import com.sk89q.worldedit.regions.RegionSelector;
|
|||||||
import com.sk89q.worldedit.regions.selector.CuboidRegionSelector;
|
import com.sk89q.worldedit.regions.selector.CuboidRegionSelector;
|
||||||
import com.sk89q.worldedit.world.World;
|
import com.sk89q.worldedit.world.World;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
@ -32,7 +33,35 @@ public abstract class FawePlayer<T> {
|
|||||||
*/
|
*/
|
||||||
private volatile ConcurrentHashMap<String, Object> meta;
|
private volatile ConcurrentHashMap<String, Object> meta;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap some object into a FawePlayer<br>
|
||||||
|
* - org.bukkit.entity.Player
|
||||||
|
* - org.spongepowered.api.entity.living.player
|
||||||
|
* - com.sk89q.worldedit.entity.Player
|
||||||
|
* - String (name)
|
||||||
|
* - UUID (player UUID)
|
||||||
|
* @param obj
|
||||||
|
* @param <V>
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
public static <V> FawePlayer<V> wrap(final Object obj) {
|
public static <V> FawePlayer<V> wrap(final Object obj) {
|
||||||
|
if (obj == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (obj instanceof Player) {
|
||||||
|
Player actor = (Player) obj;
|
||||||
|
try {
|
||||||
|
Field fieldBasePlayer = actor.getClass().getDeclaredField("basePlayer");
|
||||||
|
fieldBasePlayer.setAccessible(true);
|
||||||
|
Player player = (Player) fieldBasePlayer.get(actor);
|
||||||
|
Field fieldPlayer = player.getClass().getDeclaredField("player");
|
||||||
|
fieldPlayer.setAccessible(true);
|
||||||
|
return Fawe.imp().wrap(fieldPlayer.get(player));
|
||||||
|
} catch (Throwable e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return Fawe.imp().wrap(actor.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
return Fawe.imp().wrap(obj);
|
return Fawe.imp().wrap(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,7 +78,7 @@ public abstract class FawePlayer<T> {
|
|||||||
if (world != null) {
|
if (world != null) {
|
||||||
if (world.getName().equals(currentWorldName)) {
|
if (world.getName().equals(currentWorldName)) {
|
||||||
getSession().clearHistory();
|
getSession().clearHistory();
|
||||||
loadSessionFromDisk(world);
|
loadSessionsFromDisk(world);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@ -58,6 +87,10 @@ public abstract class FawePlayer<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current World
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
public World getWorld() {
|
public World getWorld() {
|
||||||
String currentWorldName = getLocation().world;
|
String currentWorldName = getLocation().world;
|
||||||
for (World world : WorldEdit.getInstance().getServer().getWorlds()) {
|
for (World world : WorldEdit.getInstance().getServer().getWorlds()) {
|
||||||
@ -68,7 +101,12 @@ public abstract class FawePlayer<T> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void loadSessionFromDisk(World world) {
|
/**
|
||||||
|
* Load all the undo EditSession's from disk for a world <br>
|
||||||
|
* - Usually already called when a player joins or changes world
|
||||||
|
* @param world
|
||||||
|
*/
|
||||||
|
public void loadSessionsFromDisk(World world) {
|
||||||
if (world == null) {
|
if (world == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -94,26 +132,68 @@ public abstract class FawePlayer<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the player's limit
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
public FaweLimit getLimit() {
|
public FaweLimit getLimit() {
|
||||||
return Settings.getLimit(this);
|
return Settings.getLimit(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the player's name
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
public abstract String getName();
|
public abstract String getName();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the player's UUID
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
public abstract UUID getUUID();
|
public abstract UUID getUUID();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check the player's permission
|
||||||
|
* @param perm
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
public abstract boolean hasPermission(final String perm);
|
public abstract boolean hasPermission(final String perm);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a permission (requires Vault)
|
||||||
|
* @param perm
|
||||||
|
* @param flag
|
||||||
|
*/
|
||||||
public abstract void setPermission(final String perm, final boolean flag);
|
public abstract void setPermission(final String perm, final boolean flag);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a message to the player
|
||||||
|
* @param message
|
||||||
|
*/
|
||||||
public abstract void sendMessage(final String message);
|
public abstract void sendMessage(final String message);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Have the player execute a command
|
||||||
|
* @param substring
|
||||||
|
*/
|
||||||
public abstract void executeCommand(final String substring);
|
public abstract void executeCommand(final String substring);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the player's location
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
public abstract FaweLocation getLocation();
|
public abstract FaweLocation getLocation();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the WorldEdit player object
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
public abstract Player getPlayer();
|
public abstract Player getPlayer();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the player's current selection (or null)
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
public Region getSelection() {
|
public Region getSelection() {
|
||||||
try {
|
try {
|
||||||
return this.getSession().getSelection(this.getPlayer().getWorld());
|
return this.getSession().getSelection(this.getPlayer().getWorld());
|
||||||
@ -122,20 +202,36 @@ public abstract class FawePlayer<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the player's current LocalSession
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
public LocalSession getSession() {
|
public LocalSession getSession() {
|
||||||
return (this.session != null || this.getPlayer() == null) ? this.session : (session = Fawe.get().getWorldEdit().getSession(this.getPlayer()));
|
return (this.session != null || this.getPlayer() == null) ? this.session : (session = Fawe.get().getWorldEdit().getSession(this.getPlayer()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the player's current allowed WorldEdit regions
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
public HashSet<RegionWrapper> getCurrentRegions() {
|
public HashSet<RegionWrapper> getCurrentRegions() {
|
||||||
return WEManager.IMP.getMask(this);
|
return WEManager.IMP.getMask(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the player's WorldEdit selection to the following CuboidRegion
|
||||||
|
* @param region
|
||||||
|
*/
|
||||||
public void setSelection(final RegionWrapper region) {
|
public void setSelection(final RegionWrapper region) {
|
||||||
final Player player = this.getPlayer();
|
final Player player = this.getPlayer();
|
||||||
final RegionSelector selector = new CuboidRegionSelector(player.getWorld(), region.getBottomVector(), region.getTopVector());
|
final RegionSelector selector = new CuboidRegionSelector(player.getWorld(), region.getBottomVector(), region.getTopVector());
|
||||||
this.getSession().setRegionSelector(player.getWorld(), selector);
|
this.getSession().setRegionSelector(player.getWorld(), selector);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the largest region in the player's allowed WorldEdit region
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
public RegionWrapper getLargestRegion() {
|
public RegionWrapper getLargestRegion() {
|
||||||
int area = 0;
|
int area = 0;
|
||||||
RegionWrapper max = null;
|
RegionWrapper max = null;
|
||||||
@ -154,6 +250,10 @@ public abstract class FawePlayer<T> {
|
|||||||
return this.getName();
|
return this.getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the player has WorldEdit bypass enabled
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
public boolean hasWorldEditBypass() {
|
public boolean hasWorldEditBypass() {
|
||||||
return this.hasPermission("fawe.bypass");
|
return this.hasPermission("fawe.bypass");
|
||||||
}
|
}
|
||||||
@ -183,6 +283,13 @@ public abstract class FawePlayer<T> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the metadata for a specific key (or return the default provided)
|
||||||
|
* @param key
|
||||||
|
* @param def
|
||||||
|
* @param <V>
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
public <V> V getMeta(String key, V def) {
|
public <V> V getMeta(String key, V def) {
|
||||||
if (this.meta != null) {
|
if (this.meta != null) {
|
||||||
V value = (V) this.meta.get(key);
|
V value = (V) this.meta.get(key);
|
||||||
@ -201,6 +308,10 @@ public abstract class FawePlayer<T> {
|
|||||||
return this.meta == null ? null : this.meta.remove(key);
|
return this.meta == null ? null : this.meta.remove(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregister this player (delets all metadata etc)
|
||||||
|
* - Usually called on logout
|
||||||
|
*/
|
||||||
public void unregister() {
|
public void unregister() {
|
||||||
getSession().setClipboard(null);
|
getSession().setClipboard(null);
|
||||||
getSession().clearHistory();
|
getSession().clearHistory();
|
||||||
|
@ -19,6 +19,11 @@ public class IntegerPair {
|
|||||||
return this.hash;
|
return this.hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return x + "," + z;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(final Object obj) {
|
public boolean equals(final Object obj) {
|
||||||
if (this == obj) {
|
if (this == obj) {
|
||||||
|
@ -46,6 +46,10 @@ public class RegionWrapper {
|
|||||||
return minZ - z;
|
return minZ - z;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean intersects(RegionWrapper other) {
|
||||||
|
return other.minX <= this.maxX && other.maxX >= this.minX && other.minZ <= this.maxZ && other.maxZ >= this.minZ;
|
||||||
|
}
|
||||||
|
|
||||||
public int distance(int x, int z) {
|
public int distance(int x, int z) {
|
||||||
if (isIn(x, z)) {
|
if (isIn(x, z)) {
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -130,7 +130,7 @@ public class DiskStorageHistory implements ChangeSet, FaweChangeSet {
|
|||||||
init(world, uuid, index);
|
init(world, uuid, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void init(World world, UUID uuid, int i) {
|
private void init(World world, UUID uuid, int i) {
|
||||||
this.uuid = uuid;
|
this.uuid = uuid;
|
||||||
this.world = world;
|
this.world = world;
|
||||||
String base = "history" + File.separator + world.getName() + File.separator + uuid;
|
String base = "history" + File.separator + world.getName() + File.separator + uuid;
|
||||||
|
@ -15,10 +15,10 @@ public class FaweException extends RuntimeException {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static FaweException get(Throwable e) {
|
public static FaweException get(Throwable e) {
|
||||||
Throwable cause = e.getCause();
|
if (e instanceof FaweException) {
|
||||||
if (cause instanceof FaweException) {
|
return (FaweException) e;
|
||||||
return (FaweException) cause;
|
|
||||||
}
|
}
|
||||||
|
Throwable cause = e.getCause();
|
||||||
if (cause == null) {
|
if (cause == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -2,24 +2,15 @@ package com.boydti.fawe.util;
|
|||||||
|
|
||||||
import com.boydti.fawe.Fawe;
|
import com.boydti.fawe.Fawe;
|
||||||
import com.boydti.fawe.config.BBC;
|
import com.boydti.fawe.config.BBC;
|
||||||
import com.boydti.fawe.object.FaweLocation;
|
|
||||||
import com.boydti.fawe.object.FawePlayer;
|
import com.boydti.fawe.object.FawePlayer;
|
||||||
import com.boydti.fawe.object.RegionWrapper;
|
|
||||||
import com.boydti.fawe.object.RunnableVal;
|
import com.boydti.fawe.object.RunnableVal;
|
||||||
import com.boydti.fawe.object.changeset.DiskStorageHistory;
|
|
||||||
import com.sk89q.jnbt.CompoundTag;
|
import com.sk89q.jnbt.CompoundTag;
|
||||||
import com.sk89q.jnbt.EndTag;
|
import com.sk89q.jnbt.EndTag;
|
||||||
import com.sk89q.jnbt.ListTag;
|
import com.sk89q.jnbt.ListTag;
|
||||||
import com.sk89q.jnbt.Tag;
|
import com.sk89q.jnbt.Tag;
|
||||||
import com.sk89q.worldedit.world.World;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public class MainUtil {
|
public class MainUtil {
|
||||||
/*
|
/*
|
||||||
@ -139,62 +130,6 @@ public class MainUtil {
|
|||||||
return time;
|
return time;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<DiskStorageHistory> getBDFiles(FaweLocation origin, UUID user, int radius, long timediff, boolean shallow) {
|
|
||||||
File history = new File(Fawe.imp().getDirectory(), "history" + File.separator + origin.world);
|
|
||||||
if (!history.exists()) {
|
|
||||||
return new ArrayList<>();
|
|
||||||
}
|
|
||||||
long now = System.currentTimeMillis();
|
|
||||||
ArrayList<File> files = new ArrayList<>();
|
|
||||||
for (File userFile : history.listFiles()) {
|
|
||||||
if (!userFile.isDirectory()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
UUID userUUID;
|
|
||||||
try {
|
|
||||||
userUUID = UUID.fromString(userFile.getName());
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (user != null && !userUUID.equals(user)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
ArrayList<Integer> ids = new ArrayList<>();
|
|
||||||
for (File file : userFile.listFiles()) {
|
|
||||||
if (file.getName().endsWith(".bd")) {
|
|
||||||
if (timediff > Integer.MAX_VALUE || now - file.lastModified() <= timediff) {
|
|
||||||
files.add(file);
|
|
||||||
if (files.size() > 2048) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
World world = origin.getWorld();
|
|
||||||
Collections.sort(files, new Comparator<File>() {
|
|
||||||
@Override
|
|
||||||
public int compare(File a, File b) {
|
|
||||||
long value = a.lastModified() - b.lastModified();
|
|
||||||
return value == 0 ? 0 : value < 0 ? 1 : -1;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
ArrayList<DiskStorageHistory> result = new ArrayList<>();
|
|
||||||
for (File file : files) {
|
|
||||||
UUID uuid = UUID.fromString(file.getParentFile().getName());
|
|
||||||
DiskStorageHistory dsh = new DiskStorageHistory(world, uuid, Integer.parseInt(file.getName().split("\\.")[0]));
|
|
||||||
DiskStorageHistory.DiskStorageSummary summary = dsh.summarize(new RegionWrapper(origin.x - 512, origin.x + 512, origin.z - 512, origin.z + 512), shallow);
|
|
||||||
RegionWrapper region = new RegionWrapper(summary.minX, summary.maxX, summary.minZ, summary.maxZ);
|
|
||||||
if (region.distance(origin.x, origin.z) <= radius) {
|
|
||||||
result.add(dsh);
|
|
||||||
if (result.size() > 64) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void deleteOlder(File directory, final long timeDiff) {
|
public static void deleteOlder(File directory, final long timeDiff) {
|
||||||
final long now = System.currentTimeMillis();
|
final long now = System.currentTimeMillis();
|
||||||
iterateFiles(directory, new RunnableVal<File>() {
|
iterateFiles(directory, new RunnableVal<File>() {
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package com.boydti.fawe.util;
|
package com.boydti.fawe.util;
|
||||||
|
|
||||||
import com.boydti.fawe.config.Settings;
|
import com.boydti.fawe.config.Settings;
|
||||||
|
import java.util.concurrent.BlockingQueue;
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
public class MemUtil {
|
public class MemUtil {
|
||||||
@ -30,11 +32,30 @@ public class MemUtil {
|
|||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static BlockingQueue<Runnable> memoryLimitedTasks = new LinkedBlockingQueue<>();
|
||||||
|
private static BlockingQueue<Runnable> memoryPlentifulTasks = new LinkedBlockingQueue<>();
|
||||||
|
|
||||||
|
public static void addMemoryLimitedTask(Runnable run) {
|
||||||
|
if (run != null)
|
||||||
|
memoryLimitedTasks.add(run);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void addMemoryPlentifulTask(Runnable run) {
|
||||||
|
if (run != null)
|
||||||
|
memoryPlentifulTasks.add(run);
|
||||||
|
}
|
||||||
|
|
||||||
public static void memoryLimitedTask() {
|
public static void memoryLimitedTask() {
|
||||||
|
for (Runnable task : memoryLimitedTasks) {
|
||||||
|
task.run();
|
||||||
|
}
|
||||||
memory.set(true);
|
memory.set(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void memoryPlentifulTask() {
|
public static void memoryPlentifulTask() {
|
||||||
|
for (Runnable task : memoryPlentifulTasks) {
|
||||||
|
task.run();
|
||||||
|
}
|
||||||
memory.set(false);
|
memory.set(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,19 +4,46 @@ import com.boydti.fawe.Fawe;
|
|||||||
import com.boydti.fawe.object.RunnableVal;
|
import com.boydti.fawe.object.RunnableVal;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
public abstract class TaskManager {
|
public abstract class TaskManager {
|
||||||
|
|
||||||
public static TaskManager IMP;
|
public static TaskManager IMP;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run a repeating task on the main thread
|
||||||
|
* @param r
|
||||||
|
* @param interval in ticks
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
public abstract int repeat(final Runnable r, final int interval);
|
public abstract int repeat(final Runnable r, final int interval);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run a repeating task asynchronously
|
||||||
|
* @param r
|
||||||
|
* @param interval in ticks
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
public abstract int repeatAsync(final Runnable r, final int interval);
|
public abstract int repeatAsync(final Runnable r, final int interval);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run a task asynchronously
|
||||||
|
* @param r
|
||||||
|
*/
|
||||||
public abstract void async(final Runnable r);
|
public abstract void async(final Runnable r);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run a task on the main thread
|
||||||
|
* @param r
|
||||||
|
*/
|
||||||
public abstract void task(final Runnable r);
|
public abstract void task(final Runnable r);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run a task on either the main thread or asynchronously
|
||||||
|
* - If it's already the main thread, it will jst call run()
|
||||||
|
* @param r
|
||||||
|
* @param async
|
||||||
|
*/
|
||||||
public void task(final Runnable r, boolean async) {
|
public void task(final Runnable r, boolean async) {
|
||||||
if (async) {
|
if (async) {
|
||||||
async(r);
|
async(r);
|
||||||
@ -31,12 +58,35 @@ public abstract class TaskManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run a task later on the main thread
|
||||||
|
* @param r
|
||||||
|
* @param delay in ticks
|
||||||
|
*/
|
||||||
public abstract void later(final Runnable r, final int delay);
|
public abstract void later(final Runnable r, final int delay);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run a task later asynchronously
|
||||||
|
* @param r
|
||||||
|
* @param delay in ticks
|
||||||
|
*/
|
||||||
public abstract void laterAsync(final Runnable r, final int delay);
|
public abstract void laterAsync(final Runnable r, final int delay);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel a task
|
||||||
|
* @param task
|
||||||
|
*/
|
||||||
public abstract void cancel(final int task);
|
public abstract void cancel(final int task);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Break up a task and run it in fragments of 5ms.<br>
|
||||||
|
* - Each task will run on the main thread.<br>
|
||||||
|
* - Usualy wait time is around 25ms<br>
|
||||||
|
* @param objects - The list of objects to run the task for
|
||||||
|
* @param task - The task to run on each object
|
||||||
|
* @param whenDone - When the object task completes
|
||||||
|
* @param <T>
|
||||||
|
*/
|
||||||
public <T> void objectTask(Collection<T> objects, final RunnableVal<T> task, final Runnable whenDone) {
|
public <T> void objectTask(Collection<T> objects, final RunnableVal<T> task, final Runnable whenDone) {
|
||||||
final Iterator<T> iterator = objects.iterator();
|
final Iterator<T> iterator = objects.iterator();
|
||||||
task(new Runnable() {
|
task(new Runnable() {
|
||||||
@ -57,11 +107,31 @@ public abstract class TaskManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Quickly run a task on the main thread, and wait for execution to finish:<br>
|
||||||
|
* - Useful if you need to access something from the Bukkit API from another thread<br>
|
||||||
|
* @param function
|
||||||
|
* @param <T>
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
public <T> T sync(final RunnableVal<T> function) {
|
public <T> T sync(final RunnableVal<T> function) {
|
||||||
|
return sync(function, Integer.MAX_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Quickly run a task on the main thread, and wait for execution to finish:<br>
|
||||||
|
* - Useful if you need to access something from the Bukkit API from another thread<br>
|
||||||
|
* @param function
|
||||||
|
* @param timeout - How long to wait for execution
|
||||||
|
* @param <T>
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public <T> T sync(final RunnableVal<T> function, int timeout) {
|
||||||
if (Fawe.get().getMainThread() == Thread.currentThread()) {
|
if (Fawe.get().getMainThread() == Thread.currentThread()) {
|
||||||
function.run();
|
function.run();
|
||||||
return function.value;
|
return function.value;
|
||||||
}
|
}
|
||||||
|
final AtomicBoolean running = new AtomicBoolean(true);
|
||||||
RunnableVal<RuntimeException> run = new RunnableVal<RuntimeException>() {
|
RunnableVal<RuntimeException> run = new RunnableVal<RuntimeException>() {
|
||||||
@Override
|
@Override
|
||||||
public void run(RuntimeException value) {
|
public void run(RuntimeException value) {
|
||||||
@ -71,6 +141,8 @@ public abstract class TaskManager {
|
|||||||
this.value = e;
|
this.value = e;
|
||||||
} catch (Throwable neverHappens) {
|
} catch (Throwable neverHappens) {
|
||||||
neverHappens.printStackTrace();
|
neverHappens.printStackTrace();
|
||||||
|
} finally {
|
||||||
|
running.set(false);
|
||||||
}
|
}
|
||||||
synchronized (function) {
|
synchronized (function) {
|
||||||
function.notifyAll();
|
function.notifyAll();
|
||||||
@ -78,16 +150,18 @@ public abstract class TaskManager {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
TaskManager.IMP.task(run);
|
TaskManager.IMP.task(run);
|
||||||
if (run.value != null) {
|
|
||||||
throw run.value;
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
synchronized (function) {
|
synchronized (function) {
|
||||||
function.wait(15000);
|
while (running.get()) {
|
||||||
|
function.wait(timeout);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
if (run.value != null) {
|
||||||
|
throw run.value;
|
||||||
|
}
|
||||||
return function.value;
|
return function.value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,6 @@
|
|||||||
|
|
||||||
package com.sk89q.worldedit.extension.platform;
|
package com.sk89q.worldedit.extension.platform;
|
||||||
|
|
||||||
import com.boydti.fawe.Fawe;
|
|
||||||
import com.boydti.fawe.config.BBC;
|
import com.boydti.fawe.config.BBC;
|
||||||
import com.boydti.fawe.object.FawePlayer;
|
import com.boydti.fawe.object.FawePlayer;
|
||||||
import com.boydti.fawe.object.exception.FaweException;
|
import com.boydti.fawe.object.exception.FaweException;
|
||||||
@ -86,7 +85,6 @@ import com.sk89q.worldedit.util.logging.DynamicStreamHandler;
|
|||||||
import com.sk89q.worldedit.util.logging.LogFormat;
|
import com.sk89q.worldedit.util.logging.LogFormat;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.util.logging.FileHandler;
|
import java.util.logging.FileHandler;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
@ -238,19 +236,8 @@ public final class CommandManager {
|
|||||||
LocalConfiguration config = worldEdit.getConfiguration();
|
LocalConfiguration config = worldEdit.getConfiguration();
|
||||||
|
|
||||||
CommandLocals locals = new CommandLocals();
|
CommandLocals locals = new CommandLocals();
|
||||||
FawePlayer fp;
|
FawePlayer fp = FawePlayer.wrap(actor);
|
||||||
if (actor != null && actor.isPlayer()) {
|
if (fp != null) {
|
||||||
try {
|
|
||||||
Field fieldBasePlayer = actor.getClass().getDeclaredField("basePlayer");
|
|
||||||
fieldBasePlayer.setAccessible(true);
|
|
||||||
Player player = (Player) fieldBasePlayer.get(actor);
|
|
||||||
Field fieldPlayer = player.getClass().getDeclaredField("player");
|
|
||||||
fieldPlayer.setAccessible(true);
|
|
||||||
fp = Fawe.imp().wrap(fieldPlayer.get(player));
|
|
||||||
} catch (Throwable e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
fp = Fawe.imp().wrap(actor.getName());
|
|
||||||
}
|
|
||||||
if (fp.getMeta("fawe_action") != null) {
|
if (fp.getMeta("fawe_action") != null) {
|
||||||
BBC.WORLDEDIT_COMMAND_LIMIT.send(fp);
|
BBC.WORLDEDIT_COMMAND_LIMIT.send(fp);
|
||||||
return;
|
return;
|
||||||
@ -259,7 +246,6 @@ public final class CommandManager {
|
|||||||
locals.put(Actor.class, new PlayerWrapper((Player) actor));
|
locals.put(Actor.class, new PlayerWrapper((Player) actor));
|
||||||
} else {
|
} else {
|
||||||
locals.put(Actor.class, actor);
|
locals.put(Actor.class, actor);
|
||||||
fp = null;
|
|
||||||
}
|
}
|
||||||
locals.put("arguments", event.getArguments());
|
locals.put("arguments", event.getArguments());
|
||||||
final long start = System.currentTimeMillis();
|
final long start = System.currentTimeMillis();
|
||||||
|
@ -0,0 +1,512 @@
|
|||||||
|
/*
|
||||||
|
* 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.extension.platform;
|
||||||
|
|
||||||
|
import com.boydti.fawe.config.BBC;
|
||||||
|
import com.boydti.fawe.object.exception.FaweException;
|
||||||
|
import com.sk89q.worldedit.LocalConfiguration;
|
||||||
|
import com.sk89q.worldedit.LocalSession;
|
||||||
|
import com.sk89q.worldedit.ServerInterface;
|
||||||
|
import com.sk89q.worldedit.Vector;
|
||||||
|
import com.sk89q.worldedit.WorldEdit;
|
||||||
|
import com.sk89q.worldedit.WorldVector;
|
||||||
|
import com.sk89q.worldedit.command.tool.BlockTool;
|
||||||
|
import com.sk89q.worldedit.command.tool.DoubleActionBlockTool;
|
||||||
|
import com.sk89q.worldedit.command.tool.DoubleActionTraceTool;
|
||||||
|
import com.sk89q.worldedit.command.tool.Tool;
|
||||||
|
import com.sk89q.worldedit.command.tool.TraceTool;
|
||||||
|
import com.sk89q.worldedit.entity.Player;
|
||||||
|
import com.sk89q.worldedit.event.platform.BlockInteractEvent;
|
||||||
|
import com.sk89q.worldedit.event.platform.ConfigurationLoadEvent;
|
||||||
|
import com.sk89q.worldedit.event.platform.Interaction;
|
||||||
|
import com.sk89q.worldedit.event.platform.PlatformInitializeEvent;
|
||||||
|
import com.sk89q.worldedit.event.platform.PlatformReadyEvent;
|
||||||
|
import com.sk89q.worldedit.event.platform.PlayerInputEvent;
|
||||||
|
import com.sk89q.worldedit.extension.platform.permission.ActorSelectorLimits;
|
||||||
|
import com.sk89q.worldedit.internal.ServerInterfaceAdapter;
|
||||||
|
import com.sk89q.worldedit.regions.RegionSelector;
|
||||||
|
import com.sk89q.worldedit.util.Location;
|
||||||
|
import com.sk89q.worldedit.util.eventbus.Subscribe;
|
||||||
|
import com.sk89q.worldedit.world.World;
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.EnumMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages registered {@link Platform}s for WorldEdit. Platforms are
|
||||||
|
* implementations of WorldEdit.
|
||||||
|
*
|
||||||
|
* <p>This class is thread-safe.</p>
|
||||||
|
*/
|
||||||
|
public class PlatformManager {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(PlatformManager.class.getCanonicalName());
|
||||||
|
|
||||||
|
private final WorldEdit worldEdit;
|
||||||
|
private final CommandManager commandManager;
|
||||||
|
private final List<Platform> platforms = new ArrayList<Platform>();
|
||||||
|
private final Map<Capability, Platform> preferences = new EnumMap<Capability, Platform>(Capability.class);
|
||||||
|
private @Nullable String firstSeenVersion;
|
||||||
|
private final AtomicBoolean initialized = new AtomicBoolean();
|
||||||
|
private final AtomicBoolean configured = new AtomicBoolean();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new platform manager.
|
||||||
|
*
|
||||||
|
* @param worldEdit the WorldEdit instance
|
||||||
|
*/
|
||||||
|
public PlatformManager(WorldEdit worldEdit) {
|
||||||
|
checkNotNull(worldEdit);
|
||||||
|
this.worldEdit = worldEdit;
|
||||||
|
this.commandManager = new CommandManager(worldEdit, this);
|
||||||
|
|
||||||
|
// Register this instance for events
|
||||||
|
worldEdit.getEventBus().register(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a platform with WorldEdit.
|
||||||
|
*
|
||||||
|
* @param platform the platform
|
||||||
|
*/
|
||||||
|
public synchronized void register(Platform platform) {
|
||||||
|
checkNotNull(platform);
|
||||||
|
|
||||||
|
logger.log(Level.FINE, "Got request to register " + platform.getClass() + " with WorldEdit [" + super.toString() + "]");
|
||||||
|
|
||||||
|
// Just add the platform to the list of platforms: we'll pick favorites
|
||||||
|
// once all the platforms have been loaded
|
||||||
|
platforms.add(platform);
|
||||||
|
|
||||||
|
// Make sure that versions are in sync
|
||||||
|
if (firstSeenVersion != null) {
|
||||||
|
if (!firstSeenVersion.equals(platform.getVersion())) {
|
||||||
|
logger.log(Level.WARNING, "Multiple ports of WorldEdit are installed but they report different versions ({0} and {1}). " +
|
||||||
|
"If these two versions are truly different, then you may run into unexpected crashes and errors.",
|
||||||
|
new Object[]{ firstSeenVersion, platform.getVersion() });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
firstSeenVersion = platform.getVersion();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregister a platform from WorldEdit.
|
||||||
|
*
|
||||||
|
* <p>If the platform has been chosen for any capabilities, then a new
|
||||||
|
* platform will be found.</p>
|
||||||
|
*
|
||||||
|
* @param platform the platform
|
||||||
|
*/
|
||||||
|
public synchronized boolean unregister(Platform platform) {
|
||||||
|
checkNotNull(platform);
|
||||||
|
|
||||||
|
boolean removed = platforms.remove(platform);
|
||||||
|
|
||||||
|
if (removed) {
|
||||||
|
logger.log(Level.FINE, "Unregistering " + platform.getClass().getCanonicalName() + " from WorldEdit");
|
||||||
|
|
||||||
|
boolean choosePreferred = false;
|
||||||
|
|
||||||
|
// Check whether this platform was chosen to be the preferred one
|
||||||
|
// for any capability and be sure to remove it
|
||||||
|
Iterator<Entry<Capability, Platform>> it = preferences.entrySet().iterator();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
Entry<Capability, Platform> entry = it.next();
|
||||||
|
if (entry.getValue().equals(platform)) {
|
||||||
|
Capability key = entry.getKey();
|
||||||
|
try {
|
||||||
|
Method methodUnload = key.getClass().getDeclaredMethod("unload", PlatformManager.class, Platform.class);
|
||||||
|
methodUnload.setAccessible(true);
|
||||||
|
methodUnload.invoke(key, this, entry.getValue());
|
||||||
|
} catch (Throwable e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
it.remove();
|
||||||
|
choosePreferred = true; // Have to choose new favorites
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (choosePreferred) {
|
||||||
|
choosePreferred();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return removed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the preferred platform for handling a certain capability. Returns
|
||||||
|
* null if none is available.
|
||||||
|
*
|
||||||
|
* @param capability the capability
|
||||||
|
* @return the platform
|
||||||
|
* @throws NoCapablePlatformException thrown if no platform is capable
|
||||||
|
*/
|
||||||
|
public synchronized Platform queryCapability(Capability capability) throws NoCapablePlatformException {
|
||||||
|
Platform platform = preferences.get(checkNotNull(capability));
|
||||||
|
if (platform != null) {
|
||||||
|
return platform;
|
||||||
|
} else {
|
||||||
|
throw new NoCapablePlatformException("No platform was found supporting " + capability.name());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Choose preferred platforms and perform necessary initialization.
|
||||||
|
*/
|
||||||
|
private synchronized void choosePreferred() {
|
||||||
|
for (Capability capability : Capability.values()) {
|
||||||
|
Platform preferred = findMostPreferred(capability);
|
||||||
|
if (preferred != null) {
|
||||||
|
preferences.put(capability, preferred);
|
||||||
|
try {
|
||||||
|
Method methodInitialize = Capability.class.getDeclaredMethod("initialize", PlatformManager.class, Platform.class);
|
||||||
|
methodInitialize.setAccessible(true);
|
||||||
|
methodInitialize.invoke(capability, this, preferred);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fire configuration event
|
||||||
|
if (preferences.containsKey(Capability.CONFIGURATION) && configured.compareAndSet(false, true)) {
|
||||||
|
worldEdit.getEventBus().post(new ConfigurationLoadEvent(queryCapability(Capability.CONFIGURATION).getConfiguration()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the most preferred platform for a given capability from the list of
|
||||||
|
* platforms. This does not use the map of preferred platforms.
|
||||||
|
*
|
||||||
|
* @param capability the capability
|
||||||
|
* @return the most preferred platform, or null if no platform was found
|
||||||
|
*/
|
||||||
|
private synchronized @Nullable Platform findMostPreferred(Capability capability) {
|
||||||
|
Platform preferred = null;
|
||||||
|
Preference highest = null;
|
||||||
|
|
||||||
|
for (Platform platform : platforms) {
|
||||||
|
Preference preference = platform.getCapabilities().get(capability);
|
||||||
|
if (preference != null && (highest == null || preference.isPreferredOver(highest))) {
|
||||||
|
preferred = platform;
|
||||||
|
highest = preference;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return preferred;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a list of loaded platforms.
|
||||||
|
*
|
||||||
|
* <p>The returned list is a copy of the original and is mutable.</p>
|
||||||
|
*
|
||||||
|
* @return a list of platforms
|
||||||
|
*/
|
||||||
|
public synchronized List<Platform> getPlatforms() {
|
||||||
|
return new ArrayList<Platform>(platforms);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a world, possibly return the same world but using a different
|
||||||
|
* platform preferred for world editing operations.
|
||||||
|
*
|
||||||
|
* @param base the world to match
|
||||||
|
* @return the preferred world, if one was found, otherwise the given world
|
||||||
|
*/
|
||||||
|
public World getWorldForEditing(World base) {
|
||||||
|
checkNotNull(base);
|
||||||
|
World match = queryCapability(Capability.WORLD_EDITING).matchWorld(base);
|
||||||
|
return match != null ? match : base;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an actor, return a new one that may use a different platform
|
||||||
|
* for permissions and world editing.
|
||||||
|
*
|
||||||
|
* @param base the base actor to match
|
||||||
|
* @return a new delegate actor
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T extends Actor> T createProxyActor(T base) {
|
||||||
|
checkNotNull(base);
|
||||||
|
|
||||||
|
if (base instanceof Player) {
|
||||||
|
Player player = (Player) base;
|
||||||
|
|
||||||
|
Player permActor = queryCapability(Capability.PERMISSIONS).matchPlayer(player);
|
||||||
|
if (permActor == null) {
|
||||||
|
permActor = player;
|
||||||
|
}
|
||||||
|
|
||||||
|
Player cuiActor = queryCapability(Capability.WORLDEDIT_CUI).matchPlayer(player);
|
||||||
|
if (cuiActor == null) {
|
||||||
|
cuiActor = player;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Class<?> clazz = Class.forName("com.sk89q.worldedit.extension.platform.PlayerProxy");
|
||||||
|
Constructor<?> constructor = clazz.getDeclaredConstructor(Player.class, Actor.class, Actor.class, World.class);
|
||||||
|
constructor.setAccessible(true);
|
||||||
|
return (T) constructor.newInstance(player, permActor, cuiActor, getWorldForEditing(player.getWorld()));
|
||||||
|
} catch (Throwable e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the command manager.
|
||||||
|
*
|
||||||
|
* @return the command manager
|
||||||
|
*/
|
||||||
|
public CommandManager getCommandManager() {
|
||||||
|
return commandManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current configuration.
|
||||||
|
*
|
||||||
|
* <p>If no platform has been registered yet, then a default configuration
|
||||||
|
* will be returned.</p>
|
||||||
|
*
|
||||||
|
* @return the configuration
|
||||||
|
*/
|
||||||
|
public LocalConfiguration getConfiguration() {
|
||||||
|
return queryCapability(Capability.CONFIGURATION).getConfiguration();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a legacy {@link ServerInterface}.
|
||||||
|
*
|
||||||
|
* @return a {@link ServerInterface}
|
||||||
|
* @throws IllegalStateException if no platform has been registered
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
public ServerInterface getServerInterface() throws IllegalStateException {
|
||||||
|
return ServerInterfaceAdapter.adapt(queryCapability(Capability.USER_COMMANDS));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void handlePlatformReady(PlatformReadyEvent event) {
|
||||||
|
choosePreferred();
|
||||||
|
if (initialized.compareAndSet(false, true)) {
|
||||||
|
worldEdit.getEventBus().post(new PlatformInitializeEvent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
@Subscribe
|
||||||
|
public void handleBlockInteract(BlockInteractEvent event) {
|
||||||
|
// Create a proxy actor with a potentially different world for
|
||||||
|
// making changes to the world
|
||||||
|
Actor actor = createProxyActor(event.getCause());
|
||||||
|
try {
|
||||||
|
Location location = event.getLocation();
|
||||||
|
Vector vector = location.toVector();
|
||||||
|
|
||||||
|
// At this time, only handle interaction from players
|
||||||
|
if (actor instanceof Player) {
|
||||||
|
Player player = (Player) actor;
|
||||||
|
LocalSession session = worldEdit.getSessionManager().get(actor);
|
||||||
|
|
||||||
|
if (event.getType() == Interaction.HIT) {
|
||||||
|
if (player.getItemInHand() == getConfiguration().wandItem) {
|
||||||
|
if (!session.isToolControlEnabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!actor.hasPermission("worldedit.selection.pos")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RegionSelector selector = session.getRegionSelector(player.getWorld());
|
||||||
|
|
||||||
|
if (selector.selectPrimary(location.toVector(), ActorSelectorLimits.forActor(player))) {
|
||||||
|
selector.explainPrimarySelection(actor, session, vector);
|
||||||
|
}
|
||||||
|
|
||||||
|
event.setCancelled(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (player.isHoldingPickAxe() && session.hasSuperPickAxe()) {
|
||||||
|
final BlockTool superPickaxe = session.getSuperPickaxe();
|
||||||
|
if (superPickaxe != null && superPickaxe.canUse(player)) {
|
||||||
|
event.setCancelled(superPickaxe.actPrimary(queryCapability(Capability.WORLD_EDITING), getConfiguration(), player, session, location));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Tool tool = session.getTool(player.getItemInHand());
|
||||||
|
if (tool != null && tool instanceof DoubleActionBlockTool) {
|
||||||
|
if (tool.canUse(player)) {
|
||||||
|
((DoubleActionBlockTool) tool).actSecondary(queryCapability(Capability.WORLD_EDITING), getConfiguration(), player, session, location);
|
||||||
|
event.setCancelled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (event.getType() == Interaction.OPEN) {
|
||||||
|
if (player.getItemInHand() == getConfiguration().wandItem) {
|
||||||
|
if (!session.isToolControlEnabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!actor.hasPermission("worldedit.selection.pos")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RegionSelector selector = session.getRegionSelector(player.getWorld());
|
||||||
|
if (selector.selectSecondary(vector, ActorSelectorLimits.forActor(player))) {
|
||||||
|
selector.explainSecondarySelection(actor, session, vector);
|
||||||
|
}
|
||||||
|
|
||||||
|
event.setCancelled(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Tool tool = session.getTool(player.getItemInHand());
|
||||||
|
if (tool != null && tool instanceof BlockTool) {
|
||||||
|
if (tool.canUse(player)) {
|
||||||
|
((BlockTool) tool).actPrimary(queryCapability(Capability.WORLD_EDITING), getConfiguration(), player, session, location);
|
||||||
|
event.setCancelled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
FaweException faweException = FaweException.get(e);
|
||||||
|
if (faweException != null) {
|
||||||
|
actor.printError(BBC.PREFIX.s() + " " + BBC.WORLDEDIT_CANCEL_REASON.format(faweException.getMessage()));
|
||||||
|
} else {
|
||||||
|
actor.printError("Please report this error: [See console]");
|
||||||
|
actor.printRaw(e.getClass().getName() + ": " + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
@Subscribe
|
||||||
|
public void handlePlayerInput(PlayerInputEvent event) {
|
||||||
|
// Create a proxy actor with a potentially different world for
|
||||||
|
// making changes to the world
|
||||||
|
Player player = createProxyActor(event.getPlayer());
|
||||||
|
try {
|
||||||
|
switch (event.getInputType()) {
|
||||||
|
case PRIMARY: {
|
||||||
|
if (player.getItemInHand() == getConfiguration().navigationWand) {
|
||||||
|
if (getConfiguration().navigationWandMaxDistance <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!player.hasPermission("worldedit.navigation.jumpto.tool")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
WorldVector pos = player.getSolidBlockTrace(getConfiguration().navigationWandMaxDistance);
|
||||||
|
if (pos != null) {
|
||||||
|
player.findFreePosition(pos);
|
||||||
|
} else {
|
||||||
|
player.printError("No block in sight (or too far)!");
|
||||||
|
}
|
||||||
|
|
||||||
|
event.setCancelled(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalSession session = worldEdit.getSessionManager().get(player);
|
||||||
|
|
||||||
|
Tool tool = session.getTool(player.getItemInHand());
|
||||||
|
if (tool != null && tool instanceof DoubleActionTraceTool) {
|
||||||
|
if (tool.canUse(player)) {
|
||||||
|
((DoubleActionTraceTool) tool).actSecondary(queryCapability(Capability.WORLD_EDITING), getConfiguration(), player, session);
|
||||||
|
event.setCancelled(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case SECONDARY: {
|
||||||
|
if (player.getItemInHand() == getConfiguration().navigationWand) {
|
||||||
|
if (getConfiguration().navigationWandMaxDistance <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!player.hasPermission("worldedit.navigation.thru.tool")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!player.passThroughForwardWall(40)) {
|
||||||
|
player.printError("Nothing to pass through!");
|
||||||
|
}
|
||||||
|
|
||||||
|
event.setCancelled(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalSession session = worldEdit.getSessionManager().get(player);
|
||||||
|
|
||||||
|
Tool tool = session.getTool(player.getItemInHand());
|
||||||
|
if (tool != null && tool instanceof TraceTool) {
|
||||||
|
if (tool.canUse(player)) {
|
||||||
|
((TraceTool) tool).actPrimary(queryCapability(Capability.WORLD_EDITING), getConfiguration(), player, session);
|
||||||
|
event.setCancelled(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
FaweException faweException = FaweException.get(e);
|
||||||
|
if (faweException != null) {
|
||||||
|
player.printError(BBC.PREFIX.s() + " " + BBC.WORLDEDIT_CANCEL_REASON.format(faweException.getMessage()));
|
||||||
|
} else {
|
||||||
|
player.printError("Please report this error: [See console]");
|
||||||
|
player.printRaw(e.getClass().getName() + ": " + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Class<?> inject() {
|
||||||
|
return PlatformManager.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user