diff --git a/.gitignore b/.gitignore
index 05139ba48..56d647934 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,4 +17,9 @@ out
run
/dependency-reduced-pom.xml
-*-private.sh
\ No newline at end of file
+*-private.sh
+logs/
+lib/
+*.bat
+worldedit-bukkit/src/main/java/ignore/*
+todo.txt
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index 9d91915c1..32532aa78 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,16 +1,4 @@
-println """
-*******************************************
- You are building WorldEdit!
-
- If you encounter trouble:
- 1) Read COMPILING.md if you haven't yet
- 2) Try running 'build' in a separate Gradle run
- 3) Use gradlew and not gradle
- 4) If you still need help, ask on IRC! irc.esper.net #sk89q
-
- Output files will be in [subproject]/build/libs
-*******************************************
-"""
+print new File('splash.txt').text
buildscript {
repositories {
@@ -38,73 +26,62 @@ allprojects {
version = '7.0.0-SNAPSHOT'
}
-if (!project.hasProperty("artifactory_contextUrl")) ext.artifactory_contextUrl = "http://localhost"
-if (!project.hasProperty("artifactory_user")) ext.artifactory_user = "guest"
-if (!project.hasProperty("artifactory_password")) ext.artifactory_password = ""
+apply plugin: 'java'
+clean { delete "target" }
-if (!project.hasProperty("gitCommitHash") && !JavaVersion.current().isJava6()) {
+group = 'com.boydti.fawe'
+def revision = ""
+def buildNumber = ""
+def date = ""
+ext {
try {
- def repo = org.ajoberstar.grgit.Grgit.open(dir: '.')
- ext.gitCommitHash = repo.head().abbreviatedId
- } catch (Exception e) {
- println "Error getting commit hash: " + e.getMessage()
+ git = org.ajoberstar.grgit.Grgit.open(file(".git"))
+ date = git.head().date.format("yy.MM.dd")
+ revision = "-${git.head().abbreviatedId}"
+ index = 0; // Offset to match CI
+ buildNumber = "-${index}"
+ } catch (Throwable ignore) {
+ revision = "unknown";
}
}
-if (!project.hasProperty("gitCommitHash")) {
- ext.gitCommitHash = "no_git_id"
-}
-apply plugin: 'com.jfrog.artifactory'
-artifactory {
- contextUrl = "${artifactory_contextUrl}"
- publish {
- repository {
- repoKey = project.version.contains("SNAPSHOT") ? 'libs-snapshot-local' : 'libs-release-local'
- username = "${artifactory_user}"
- password = "${artifactory_password}"
- maven = true
- ivy = false
- }
- }
-
- resolve {
- repository {
- repoKey = 'repo'
- username = "${artifactory_user}"
- password = "${artifactory_password}"
- maven = true
- }
- }
+version = date + revision + buildNumber
+if ( project.hasProperty("lzNoVersion") ) { // gradle build -PlzNoVersion
+ version = "unknown";
}
-artifactoryPublish.skip = true
+description = """FastAsyncWorldEdit"""
subprojects {
apply plugin: 'java'
apply plugin: 'maven'
- apply plugin: 'checkstyle'
apply plugin: 'com.github.johnrengelman.shadow'
- apply plugin: 'com.jfrog.artifactory'
- ext.internalVersion = version + ";" + gitCommitHash
+ sourceCompatibility = 1.8
+ targetCompatibility = 1.8
+
+ ext.internalVersion = version
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
- checkstyle.configFile = new File(rootProject.projectDir, "config/checkstyle/checkstyle.xml")
- checkstyle.toolVersion = '7.6.1'
-
repositories {
mavenCentral()
maven { url "http://repo.bukkit.org/content/groups/public" }
maven { url "http://maven.sk89q.com/repo/" }
maven { url "http://repo.maven.apache.org/maven2" }
- }
-
- if (JavaVersion.current().isJava8Compatible()) {
- // Java 8 turns on doclint which we fail
- tasks.withType(Javadoc) {
- options.addStringOption('Xdoclint:none', '-quiet')
- }
+ // Fawe
+ maven {url "https://mvnrepository.com/artifact/"}
+ maven {url "http://repo.dmulloy2.net/content/groups/public/"}
+ maven {url "https://repo.destroystokyo.com/repository/maven-public//"}
+ maven {url "http://ci.emc.gs/nexus/content/groups/aikar/" }
+ maven {url "http://ci.athion.net/job/PlotSquared/ws/mvn/"}
+ maven {url "http://empcraft.com/maven2"}
+ maven {url "https://hub.spigotmc.org/nexus/content/groups/public/"}
+ maven {url "http://ci.frostcast.net/plugin/repository/everything"}
+ maven {url "http://maven.sk89q.com/artifactory/repo"}
+ maven {url "http://repo.spongepowered.org/maven"}
+ maven {url "https://repo.inventivetalent.org/content/groups/public/"}
+ maven {url "http://dl.bintray.com/tastybento/maven-repo"}
}
task javadocJar(type: Jar, dependsOn: javadoc) {
@@ -114,7 +91,6 @@ subprojects {
artifacts {
archives jar
- archives javadocJar
}
if (!(name.equals('worldedit-forge') || name.equals('worldedit-sponge'))) {
@@ -129,23 +105,17 @@ subprojects {
build.dependsOn(sourcesJar)
}
- build.dependsOn(checkstyleMain)
- build.dependsOn(checkstyleTest)
- build.dependsOn(javadocJar)
-
shadowJar {
classifier 'dist'
dependencies {
include(dependency('com.sk89q:jchronic:0.2.4a'))
include(dependency('com.thoughtworks.paranamer:paranamer:2.6'))
include(dependency('com.sk89q.lib:jlibnoise:1.0.0'))
+ include(dependency('com.github.luben:zstd-jni:1.1.1'))
+ include(dependency('co.aikar:fastutil-lite:1.0'))
}
exclude 'GradleStart**'
exclude '.cache'
exclude 'LICENSE*'
}
-
- artifactoryPublish {
- publishConfigs('archives')
- }
}
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index a95009c3b..9ebf67f22 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,6 @@
+#Thu Jul 26 14:29:48 AEST 2018
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-all.zip
diff --git a/settings.gradle b/settings.gradle
index 576283ecc..8de797141 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,3 +1,3 @@
rootProject.name = 'worldedit'
-include 'worldedit-core', 'worldedit-bukkit', 'worldedit-forge', 'worldedit-sponge'
\ No newline at end of file
+include 'worldedit-core', 'worldedit-bukkit'
\ No newline at end of file
diff --git a/worldedit-bukkit/build.gradle b/worldedit-bukkit/build.gradle
index 5c1a39cd9..d1c02dbe0 100644
--- a/worldedit-bukkit/build.gradle
+++ b/worldedit-bukkit/build.gradle
@@ -9,8 +9,27 @@ repositories {
dependencies {
compile project(':worldedit-core')
compile 'com.sk89q:dummypermscompat:1.8'
- compile 'org.bukkit:bukkit:1.13-R0.1-SNAPSHOT' // zzz
+ compile 'com.destroystokyo.paper:paper-api:1.13-R0.1-SNAPSHOT'
+ compile "org.bukkit:craftbukkit:1.13-R0.1-SNAPSHOT"
testCompile 'org.mockito:mockito-core:1.9.0-rc1'
+ compile 'net.milkbowl.vault:VaultAPI:1.5.6'
+ compile 'com.massivecraft:factions:2.8.0'
+ compile 'com.drtshock:factions:1.6.9.5'
+ compile 'com.factionsone:FactionsOne:1.2.2'
+ compile 'me.ryanhamshire:GriefPrevention:11.5.2'
+ compile 'com.massivecraft:mcore:7.0.1'
+ compile 'net.sacredlabyrinth.Phaed:PreciousStones:10.0.4-SNAPSHOT'
+ compile 'net.jzx7:regios:5.9.9'
+ compile 'com.bekvon.bukkit.residence:Residence:4.5._13.1'
+ compile 'com.palmergames.bukkit:towny:0.84.0.9'
+ compile 'com.worldcretornica:plotme_core:0.16.3'
+ compile 'com.thevoxelbox.voxelsniper:voxelsniper:5.171.0'
+ compile 'com.comphenix.protocol:ProtocolLib-API:4.4.0-SNAPSHOT'
+ compile 'com.wasteofplastic:askyblock:3.0.8.2'
+ compile 'com.sk89q:worldguard:6.0.0-SNAPSHOT'
+ compile('org.inventivetalent:mapmanager:1.4.0-SNAPSHOT') {
+ transitive = false
+ }
}
processResources {
@@ -35,6 +54,8 @@ shadowJar {
dependencies {
include(dependency(':worldedit-core'))
}
+ archiveName = "${parent.name}-${project.name}-${parent.version}.jar"
+ destinationDir = file '../target'
}
build.dependsOn(shadowJar)
diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/BukkitCommand.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/BukkitCommand.java
new file mode 100644
index 000000000..083fe3304
--- /dev/null
+++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/BukkitCommand.java
@@ -0,0 +1,29 @@
+package com.boydti.fawe.bukkit;
+
+import com.boydti.fawe.Fawe;
+import com.boydti.fawe.config.BBC;
+import com.boydti.fawe.object.FaweCommand;
+import com.boydti.fawe.object.FawePlayer;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandExecutor;
+import org.bukkit.command.CommandSender;
+
+public class BukkitCommand implements CommandExecutor {
+
+ private final FaweCommand cmd;
+
+ public BukkitCommand(final FaweCommand cmd) {
+ this.cmd = cmd;
+ }
+
+ @Override
+ public boolean onCommand(final CommandSender sender, final Command cmd, final String label, final String[] args) {
+ final FawePlayer plr = Fawe.imp().wrap(sender);
+ if (!sender.hasPermission(this.cmd.getPerm()) && !sender.isOp()) {
+ BBC.NO_PERM.send(plr, this.cmd.getPerm());
+ return true;
+ }
+ this.cmd.executeSafe(plr, args);
+ return true;
+ }
+}
diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/BukkitPlayer.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/BukkitPlayer.java
new file mode 100644
index 000000000..0bf0f78d1
--- /dev/null
+++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/BukkitPlayer.java
@@ -0,0 +1,114 @@
+package com.boydti.fawe.bukkit;
+
+import com.boydti.fawe.Fawe;
+import com.boydti.fawe.object.FaweLocation;
+import com.boydti.fawe.object.FawePlayer;
+import com.boydti.fawe.wrappers.PlayerWrapper;
+import java.lang.reflect.Method;
+import java.util.UUID;
+
+import com.intellectualcrafters.plot.config.C;
+import com.sk89q.worldedit.WorldEdit;
+import com.sk89q.worldedit.bukkit.WorldEditPlugin;
+import org.bukkit.Bukkit;
+import org.bukkit.ChatColor;
+import org.bukkit.Location;
+import org.bukkit.command.ConsoleCommandSender;
+import org.bukkit.entity.Player;
+
+public class BukkitPlayer extends FawePlayer {
+
+ private static ConsoleCommandSender console;
+
+ public BukkitPlayer(final Player parent) {
+ super(parent);
+ }
+
+ @Override
+ public String getName() {
+ return this.parent.getName();
+ }
+
+ @Override
+ public UUID getUUID() {
+ return this.parent.getUniqueId();
+ }
+
+ @Override
+ public boolean hasPermission(final String perm) {
+ return this.parent.hasPermission(perm);
+ }
+
+ @Override
+ public boolean isSneaking() {
+ return parent.isSneaking();
+ }
+
+ @Override
+ public void setPermission(final String perm, final boolean flag) {
+ /*
+ * Permissions are used to managing WorldEdit region restrictions
+ * - The `/wea` command will give/remove the required bypass permission
+ */
+ if (Fawe. imp().getVault() == null || Fawe. imp().getVault().permission == null) {
+ this.parent.addAttachment(Fawe. imp().getPlugin()).setPermission(perm, flag);
+ } else if (flag) {
+ if (!Fawe. imp().getVault().permission.playerAdd(this.parent, perm)) {
+ this.parent.addAttachment(Fawe. imp().getPlugin()).setPermission(perm, flag);
+ }
+ } else {
+ if (!Fawe. imp().getVault().permission.playerRemove(this.parent, perm)) {
+ this.parent.addAttachment(Fawe. imp().getPlugin()).setPermission(perm, flag);
+ }
+ }
+ }
+
+
+ @Override
+ public void resetTitle() {
+ sendTitle("","");
+ }
+
+ public void sendTitle(String title, String sub) {
+ try {
+ Method methodSendTitle = Player.class.getDeclaredMethod("sendTitle", String.class, String.class, int.class, int.class, int.class);
+ methodSendTitle.invoke(parent, ChatColor.GOLD + title, ChatColor.GOLD + sub, 0, 70, 20);
+ return;
+ } catch (Throwable ignore) {
+ try {
+ Method methodSendTitle = Player.class.getDeclaredMethod("sendTitle", String.class, String.class);
+ methodSendTitle.invoke(parent, ChatColor.GOLD + title, ChatColor.GOLD + sub);
+ return;
+ } catch (Throwable ignore2) {}
+ }
+ if (console == null) {
+ console = Bukkit.getConsoleSender();
+ Bukkit.getServer().dispatchCommand(console, "gamerule sendCommandFeedback false");
+ Bukkit.getServer().dispatchCommand(console, "title " + getName() + " times 0 60 20");
+ }
+ Bukkit.getServer().dispatchCommand(console, "title " + getName() + " subtitle [{\"text\":\"" + sub + "\",\"color\":\"gold\"}]");
+ Bukkit.getServer().dispatchCommand(console, "title " + getName() + " title [{\"text\":\"" + title + "\",\"color\":\"gold\"}]");
+ }
+
+ @Override
+ public void sendMessage(final String message) {
+ this.parent.sendMessage(C.color(message));
+ }
+
+ @Override
+ public void executeCommand(final String cmd) {
+ Bukkit.getServer().dispatchCommand(this.parent, cmd);
+ }
+
+ @Override
+ public FaweLocation getLocation() {
+ final Location loc = this.parent.getLocation();
+ return new FaweLocation(loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ());
+ }
+
+ @Override
+ public com.sk89q.worldedit.entity.Player toWorldEditPlayer() {
+ return WorldEditPlugin.getInstance().wrapPlayer(this.parent);
+ }
+
+}
diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/FaweBukkit.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/FaweBukkit.java
new file mode 100644
index 000000000..09487470d
--- /dev/null
+++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/FaweBukkit.java
@@ -0,0 +1,665 @@
+package com.boydti.fawe.bukkit;
+
+import com.boydti.fawe.Fawe;
+import com.boydti.fawe.IFawe;
+import com.boydti.fawe.bukkit.chat.BukkitChatManager;
+import com.boydti.fawe.bukkit.listener.BrushListener;
+import com.boydti.fawe.bukkit.listener.BukkitImageListener;
+import com.boydti.fawe.bukkit.listener.CFIPacketListener;
+import com.boydti.fawe.bukkit.listener.RenderListener;
+import com.boydti.fawe.bukkit.regions.*;
+import com.boydti.fawe.bukkit.util.BukkitReflectionUtils;
+import com.boydti.fawe.bukkit.util.BukkitTaskMan;
+import com.boydti.fawe.bukkit.util.ItemUtil;
+import com.boydti.fawe.bukkit.util.VaultUtil;
+import com.boydti.fawe.bukkit.util.cui.CUIListener;
+import com.boydti.fawe.bukkit.util.cui.StructureCUI;
+import com.boydti.fawe.bukkit.util.image.BukkitImageViewer;
+import com.boydti.fawe.bukkit.v0.BukkitQueue_0;
+import com.boydti.fawe.bukkit.v0.BukkitQueue_All;
+import com.boydti.fawe.bukkit.v0.ChunkListener_8;
+import com.boydti.fawe.bukkit.v0.ChunkListener_9;
+import com.boydti.fawe.config.BBC;
+import com.boydti.fawe.config.Settings;
+import com.boydti.fawe.object.FaweCommand;
+import com.boydti.fawe.object.FawePlayer;
+import com.boydti.fawe.object.FaweQueue;
+import com.boydti.fawe.regions.FaweMaskManager;
+import com.boydti.fawe.util.Jars;
+import com.boydti.fawe.util.MainUtil;
+import com.boydti.fawe.util.ReflectionUtils;
+import com.boydti.fawe.util.TaskManager;
+import com.boydti.fawe.util.cui.CUI;
+import com.boydti.fawe.util.image.ImageViewer;
+import com.boydti.fawe.util.metrics.BStats;
+import com.sk89q.worldedit.WorldEdit;
+import com.sk89q.worldedit.bukkit.WorldEditPlugin;
+import com.sk89q.worldedit.world.World;
+import org.bukkit.Bukkit;
+import org.bukkit.command.ConsoleCommandSender;
+import org.bukkit.command.PluginCommand;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.Listener;
+import org.bukkit.event.player.PlayerQuitEvent;
+import org.bukkit.plugin.Plugin;
+import org.bukkit.plugin.PluginManager;
+import org.bukkit.plugin.RegisteredServiceProvider;
+import org.bukkit.plugin.java.JavaPlugin;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.UUID;
+
+public class FaweBukkit implements IFawe, Listener {
+
+// private final WorldEditPlugin plugin;
+ private final Plugin plugin;
+ private VaultUtil vault;
+ private ItemUtil itemUtil;
+
+ private boolean listeningImages;
+ private BukkitImageListener imageListener;
+ private CFIPacketListener packetListener;
+
+ private boolean listeningCui;
+ private CUIListener cuiListener;
+
+ public VaultUtil getVault() {
+ return this.vault;
+ }
+
+ public FaweBukkit(Plugin plugin) {
+ this.plugin = plugin;
+ try {
+ Settings.IMP.TICK_LIMITER.ENABLED = !Bukkit.hasWhitelist();
+ Fawe.set(this);
+ Fawe.setupInjector();
+ try {
+ new BrushListener(plugin);
+ } catch (Throwable e) {
+ debug("====== BRUSH LISTENER FAILED ======");
+ e.printStackTrace();
+ debug("===================================");
+ }
+ if (Bukkit.getVersion().contains("git-Spigot")) {
+ debug("====== USE PAPER ======");
+ debug("DOWNLOAD: https://ci.destroystokyo.com/job/PaperSpigot/");
+ debug("GUIDE: https://www.spigotmc.org/threads/21726/");
+ debug(" - This is only a recommendation");
+ debug("==============================");
+ }
+ if (Bukkit.getVersion().contains("git-Paper") && Settings.IMP.EXPERIMENTAL.DYNAMIC_CHUNK_RENDERING > 1) {
+ new RenderListener(plugin);
+ }
+ try {
+ Fawe.get().setChatManager(new BukkitChatManager());
+ } catch (Throwable ignore) {
+ ignore.printStackTrace();
+ }
+ } catch (final Throwable e) {
+ MainUtil.handleError(e);
+ Bukkit.getServer().shutdown();
+ }
+
+ // Registered delayed Event Listeners
+ TaskManager.IMP.task(new Runnable() {
+ @Override
+ public void run() {
+ // This class
+ Bukkit.getPluginManager().registerEvents(FaweBukkit.this, FaweBukkit.this.plugin);
+
+ // The tick limiter
+ try {
+ Class.forName("sun.misc.SharedSecrets");
+ new ChunkListener_8();
+ } catch (ClassNotFoundException e) {
+ new ChunkListener_9();
+ }
+ }
+ });
+ }
+
+ @Override
+ public CUI getCUI(FawePlayer player) {
+ if (Settings.IMP.EXPERIMENTAL.VANILLA_CUI) {
+ if (listeningCui && cuiListener == null) return null;
+ listeningCui = true;
+ if (cuiListener == null) {
+ Plugin protocolLib = Bukkit.getPluginManager().getPlugin("ProtocolLib");
+ if (protocolLib != null && protocolLib.isEnabled()) {
+ cuiListener = new CUIListener(plugin);
+ } else {
+ return null;
+ }
+ }
+ return new StructureCUI(player);
+ }
+ return null;
+ }
+
+ @Override
+ public void registerPacketListener() {
+ PluginManager manager = Bukkit.getPluginManager();
+ if (packetListener == null && manager.getPlugin("ProtocolLib") != null) {
+ packetListener = new CFIPacketListener(plugin);
+ }
+ }
+
+ @Override
+ public synchronized ImageViewer getImageViewer(FawePlayer fp) {
+ if (listeningImages && imageListener == null) return null;
+ try {
+ listeningImages = true;
+ registerPacketListener();
+ PluginManager manager = Bukkit.getPluginManager();
+
+ if (manager.getPlugin("PacketListenerApi") == null) {
+ File output = new File(plugin.getDataFolder().getParentFile(), "PacketListenerAPI_v3.6.0-SNAPSHOT.jar");
+ byte[] jarData = Jars.PL_v3_6_0.download();
+ try (FileOutputStream fos = new FileOutputStream(output)) {
+ fos.write(jarData);
+ }
+ }
+ if (manager.getPlugin("MapManager") == null) {
+ File output = new File(plugin.getDataFolder().getParentFile(), "MapManager_v1.4.0-SNAPSHOT.jar");
+ byte[] jarData = Jars.MM_v1_4_0.download();
+ try (FileOutputStream fos = new FileOutputStream(output)) {
+ fos.write(jarData);
+ }
+ }
+ BukkitImageViewer viewer = new BukkitImageViewer((Player) fp.parent);
+ if (imageListener == null) {
+ this.imageListener = new BukkitImageListener(plugin);
+ }
+ return viewer;
+ } catch (Throwable ignore) {}
+ return null;
+ }
+
+ @Override
+ public int getPlayerCount() {
+ return plugin.getServer().getOnlinePlayers().size();
+ }
+
+ @Override
+ public boolean isOnlineMode() {
+ return Bukkit.getOnlineMode();
+ }
+
+ @Override
+ public String getPlatformVersion() {
+ String bukkitVersion = Bukkit.getVersion();
+ int index = bukkitVersion.indexOf("MC: ");
+ return index == -1 ? bukkitVersion : bukkitVersion.substring(index + 4, bukkitVersion.length() - 1);
+ }
+
+ @Override
+ public void debug(final String s) {
+ ConsoleCommandSender console = Bukkit.getConsoleSender();
+ if (console != null) {
+ console.sendMessage(BBC.color(s));
+ } else {
+ Bukkit.getLogger().info(BBC.color(s));
+ }
+ }
+
+ @Override
+ public File getDirectory() {
+ return plugin.getDataFolder();
+ }
+
+ @Override
+ public void setupCommand(final String label, final FaweCommand cmd) {
+ if (plugin instanceof JavaPlugin) {
+ PluginCommand registered = ((JavaPlugin) plugin).getCommand(label);
+ if (registered == null) {
+ debug("Command not registered in plugin.yml: " + label);
+ return;
+ }
+ registered.setExecutor(new BukkitCommand(cmd));
+ }
+ }
+
+ @Override
+ public FawePlayer wrap(final Object obj) {
+ if (obj.getClass() == String.class) {
+ String name = (String) obj;
+ FawePlayer existing = Fawe.get().getCachedPlayer(name);
+ if (existing != null) {
+ return existing;
+ }
+ Player player = Bukkit.getPlayer(name);
+ return player != null ? new BukkitPlayer(player) : null;
+ } else if (obj instanceof Player) {
+ Player player = (Player) obj;
+ FawePlayer existing = Fawe.get().getCachedPlayer(player.getName());
+ return existing != null ? existing : new BukkitPlayer(player);
+ } else if (obj != null && obj.getClass().getName().contains("EntityPlayer")) {
+ try {
+ Method method = obj.getClass().getDeclaredMethod("getBukkitEntity");
+ return wrap(method.invoke(obj));
+ } catch (Throwable e) {
+ e.printStackTrace();
+ return null;
+ }
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public void startMetrics() {
+ Metrics metrics = new Metrics(plugin);
+ metrics.start();
+ TaskManager.IMP.task(new Runnable() {
+ @Override
+ public void run() {
+ ArrayList> services = new ArrayList(Bukkit.getServicesManager().getKnownServices());
+ services.forEach(service -> {
+ try {
+ service.getField("B_STATS_VERSION");
+ ArrayList> providers = new ArrayList(Bukkit.getServicesManager().getRegistrations(service));
+ for (RegisteredServiceProvider> provider : providers) {
+ Object instance = provider.getProvider();
+
+ // Link it to FAWE's metrics instead
+ BStats.linkMetrics(instance);
+
+ // Disable the other metrics
+ Bukkit.getServicesManager().unregister(service, instance);
+ try {
+ Class extends Object> clazz = instance.getClass();
+ Field logFailedRequests = ReflectionUtils.findField(clazz, boolean.class);
+ logFailedRequests.set(null, false);
+ Field url = null;
+ try { url = clazz.getDeclaredField("URL"); } catch (NoSuchFieldException ignore) {
+ for (Field field : clazz.getDeclaredFields()) if (ReflectionUtils.setAccessible(field).get(null).toString().startsWith("http")) { url = field; break; }
+ }
+ if (url != null) ReflectionUtils.setFailsafeFieldValue(url, null, null);
+ } catch (NoSuchFieldError | IllegalAccessException ignore) {}
+ catch (Throwable e) {}
+ }
+ } catch (NoSuchFieldException ignored) { }
+ });
+ }
+ });
+ }
+
+ public ItemUtil getItemUtil() {
+ ItemUtil tmp = itemUtil;
+ if (tmp == null) {
+ try {
+ this.itemUtil = tmp = new ItemUtil();
+ } catch (Throwable e) {
+ Settings.IMP.EXPERIMENTAL.PERSISTENT_BRUSHES = false;
+ debug("===== PERSISTENT BRUSH FAILED =====");
+ e.printStackTrace();
+ debug("===================================");
+ }
+ }
+ return tmp;
+ }
+
+ /**
+ * Vault isn't required, but used for setting player permissions (WorldEdit bypass)
+ * @return
+ */
+ @Override
+ public void setupVault() {
+ try {
+ this.vault = new VaultUtil();
+ } catch (final Throwable e) {
+ this.debug("&dVault is used for persistent `/wea` toggles.");
+ }
+ }
+
+ @Override
+ public String getDebugInfo() {
+ StringBuilder msg = new StringBuilder();
+ List pl = new ArrayList<>();
+ msg.append("server.plugins: \n");
+ for (Plugin p : Bukkit.getPluginManager().getPlugins()) {
+ msg.append(" - " + p.getName() + ": " + p.getDescription().getVersion() + "\n");
+ }
+ msg.append("server.version: " + Bukkit.getVersion() + " / " + Bukkit.getBukkitVersion() + "\n");
+ return msg.toString();
+ }
+
+ /**
+ * The task manager handles sync/async tasks
+ */
+ @Override
+ public TaskManager getTaskManager() {
+ return new BukkitTaskMan(plugin);
+ }
+
+ private boolean hasNMS = true;
+ private boolean playerChunk = false;
+
+ @Override
+ public FaweQueue getNewQueue(String world, boolean fast) {
+ if (playerChunk != (playerChunk = true)) {
+ try {
+ Field fieldDirtyCount = BukkitReflectionUtils.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 ignore) {}
+ }
+ try {
+ return getQueue(world);
+ } catch (Throwable ignore) {
+ // Disable incompatible settings
+ Settings.IMP.QUEUE.PARALLEL_THREADS = 1; // BukkitAPI placer is too slow to parallel thread at the chunk level
+ Settings.IMP.HISTORY.COMBINE_STAGES = false; // Performing a chunk copy (if possible) wouldn't be faster using the BukkitAPI
+ if (hasNMS) {
+
+ debug("====== NO NMS BLOCK PLACER FOUND ======");
+ debug("FAWE couldn't find a fast block placer");
+ debug("Bukkit version: " + Bukkit.getVersion());
+ debug("NMS label: " + plugin.getClass().getSimpleName().split("_")[1]);
+ debug("Fallback placer: " + BukkitQueue_All.class);
+ debug("=======================================");
+ debug("Download the version of FAWE for your platform");
+ debug(" - http://ci.athion.net/job/FastAsyncWorldEdit/lastSuccessfulBuild/artifact/target");
+ debug("=======================================");
+ ignore.printStackTrace();
+ debug("=======================================");
+ TaskManager.IMP.laterAsync(new Runnable() {
+ @Override
+ public void run() {
+ MainUtil.sendAdmin("&cNo NMS placer found, see console!");
+ }
+ }, 1);
+ hasNMS = false;
+ }
+ return new BukkitQueue_All(world);
+ }
+ }
+
+ /**
+ * The FaweQueue is a core part of block placement
+ * - The queue returned here is used in the SetQueue class (SetQueue handles the implementation specific queue)
+ * - Block changes are grouped by chunk (as it's more efficient for lighting/packet sending)
+ * - The FaweQueue returned here will provide the wrapper around the chunk object (FaweChunk)
+ * - When a block change is requested, the SetQueue will first check if the chunk exists in the queue, or it will create and add it
+ */
+ @Override
+ public FaweQueue getNewQueue(World world, boolean fast) {
+ if (fast) {
+ if (playerChunk != (playerChunk = true)) {
+ try {
+ Field fieldDirtyCount = BukkitReflectionUtils.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 ignore) {
+ }
+ }
+ Throwable error = null;
+ try {
+ return getQueue(world);
+ } catch (Throwable ignore) {
+ error = ignore;
+ }
+ // Disable incompatible settings
+ Settings.IMP.QUEUE.PARALLEL_THREADS = 1; // BukkitAPI placer is too slow to parallel thread at the chunk level
+ Settings.IMP.HISTORY.COMBINE_STAGES = false; // Performing a chunk copy (if possible) wouldn't be faster using the BukkitAPI
+ if (hasNMS) {
+ debug("====== NO NMS BLOCK PLACER FOUND ======");
+ debug("FAWE couldn't find a fast block placer");
+ debug("Bukkit version: " + Bukkit.getVersion());
+ debug("NMS label: " + plugin.getClass().getSimpleName());
+ debug("Fallback placer: " + BukkitQueue_All.class);
+ debug("=======================================");
+ debug("Download the version of FAWE for your platform");
+ debug(" - http://ci.athion.net/job/FastAsyncWorldEdit/lastSuccessfulBuild/artifact/target");
+ debug("=======================================");
+ error.printStackTrace();
+ debug("=======================================");
+ TaskManager.IMP.laterAsync(new Runnable() {
+ @Override
+ public void run() {
+ MainUtil.sendAdmin("&cNo NMS placer found, see console!");
+ }
+ }, 1);
+ hasNMS = false;
+ }
+ }
+ return new BukkitQueue_All(world);
+ }
+
+ public Plugin getPlugin() {
+ return plugin;
+ }
+
+ @Override
+ public String getWorldName(World world) {
+ return world.getName();
+ }
+
+ /**
+ * A mask manager handles region restrictions e.g. PlotSquared plots / WorldGuard regions
+ */
+ @Override
+ public Collection getMaskManagers() {
+ final Plugin worldguardPlugin = Bukkit.getServer().getPluginManager().getPlugin("WorldGuard");
+ final ArrayList managers = new ArrayList<>();
+ if ((worldguardPlugin != null) && worldguardPlugin.isEnabled()) {
+ try {
+ managers.add(new Worldguard(worldguardPlugin, this));
+ Fawe.debug("Plugin 'WorldGuard' found. Using it now.");
+ } catch (final Throwable e) {
+ MainUtil.handleError(e);
+ }
+ }
+ final Plugin plotmePlugin = Bukkit.getServer().getPluginManager().getPlugin("PlotMe");
+ if ((plotmePlugin != null) && plotmePlugin.isEnabled()) {
+ try {
+ managers.add(new PlotMeFeature(plotmePlugin, this));
+ Fawe.debug("Plugin 'PlotMe' found. Using it now.");
+ } catch (final Throwable e) {
+ MainUtil.handleError(e);
+ }
+ }
+ final Plugin townyPlugin = Bukkit.getServer().getPluginManager().getPlugin("Towny");
+ if ((townyPlugin != null) && townyPlugin.isEnabled()) {
+ try {
+ managers.add(new TownyFeature(townyPlugin, this));
+ Fawe.debug("Plugin 'Towny' found. Using it now.");
+ } catch (final Throwable e) {
+ MainUtil.handleError(e);
+ }
+ }
+ final Plugin factionsPlugin = Bukkit.getServer().getPluginManager().getPlugin("Factions");
+ if ((factionsPlugin != null) && factionsPlugin.isEnabled()) {
+ try {
+ managers.add(new FactionsFeature(factionsPlugin, this));
+ Fawe.debug("Plugin 'Factions' found. Using it now.");
+ } catch (final Throwable e) {
+ try {
+ managers.add(new FactionsUUIDFeature(factionsPlugin, this));
+ Fawe.debug("Plugin 'FactionsUUID' found. Using it now.");
+ } catch (Throwable e2) {
+ try {
+ managers.add(new FactionsOneFeature(factionsPlugin, this));
+ Fawe.debug("Plugin 'FactionsUUID' found. Using it now.");
+ } catch (Throwable e3) {
+ MainUtil.handleError(e);
+ }
+
+ }
+ }
+ }
+ final Plugin residencePlugin = Bukkit.getServer().getPluginManager().getPlugin("Residence");
+ if ((residencePlugin != null) && residencePlugin.isEnabled()) {
+ try {
+ managers.add(new ResidenceFeature(residencePlugin, this));
+ Fawe.debug("Plugin 'Residence' found. Using it now.");
+ } catch (final Throwable e) {
+ MainUtil.handleError(e);
+ }
+ }
+ final Plugin griefpreventionPlugin = Bukkit.getServer().getPluginManager().getPlugin("GriefPrevention");
+ if ((griefpreventionPlugin != null) && griefpreventionPlugin.isEnabled()) {
+ try {
+ managers.add(new GriefPreventionFeature(griefpreventionPlugin, this));
+ Fawe.debug("Plugin 'GriefPrevention' found. Using it now.");
+ } catch (final Throwable e) {
+ MainUtil.handleError(e);
+ }
+ }
+ final Plugin preciousstonesPlugin = Bukkit.getServer().getPluginManager().getPlugin("PreciousStones");
+ if ((preciousstonesPlugin != null) && preciousstonesPlugin.isEnabled()) {
+ try {
+ managers.add(new PreciousStonesFeature(preciousstonesPlugin, this));
+ Fawe.debug("Plugin 'PreciousStones' found. Using it now.");
+ } catch (final Throwable e) {
+ MainUtil.handleError(e);
+ }
+ }
+
+
+ final Plugin aSkyBlock = Bukkit.getServer().getPluginManager().getPlugin("ASkyBlock");
+ if ((aSkyBlock != null) && aSkyBlock.isEnabled()) {
+ try {
+ managers.add(new ASkyBlockHook(aSkyBlock, this));
+ Fawe.debug("Plugin 'ASkyBlock' found. Using it now.");
+ } catch (final Throwable e) {
+ MainUtil.handleError(e);
+ }
+ }
+
+ return managers;
+ }
+//
+// @EventHandler
+// public void onWorldLoad(WorldLoadEvent event) {
+// org.bukkit.World world = event.getWorld();
+// world.setKeepSpawnInMemory(false);
+// WorldServer nmsWorld = ((CraftWorld) world).getHandle();
+// ChunkProviderServer provider = nmsWorld.getChunkProviderServer();
+// try {
+// Field fieldChunkLoader = provider.getClass().getDeclaredField("chunkLoader");
+// ReflectionUtils.setFailsafeFieldValue(fieldChunkLoader, provider, new FaweChunkLoader());
+// } catch (Throwable e) {
+// e.printStackTrace();
+// }
+// }
+
+ @EventHandler(priority = EventPriority.MONITOR)
+ public void onPlayerQuit(PlayerQuitEvent event) {
+ Player player = event.getPlayer();
+ String name = player.getName();
+ FawePlayer fp = Fawe.get().getCachedPlayer(name);
+ if (fp != null) {
+ fp.unregister();
+ Fawe.get().unregister(name);
+ }
+ }
+
+ @Override
+ public String getPlatform() {
+ return "bukkit";
+ }
+
+ @Override
+ public UUID getUUID(String name) {
+ return Bukkit.getOfflinePlayer(name).getUniqueId();
+ }
+
+ @Override
+ public String getName(UUID uuid) {
+ return Bukkit.getOfflinePlayer(uuid).getName();
+ }
+
+ private boolean enabledBlocksHub = true;
+
+ @Override
+ public Object getBlocksHubApi() {
+ if (!enabledBlocksHub) {
+ return null;
+ }
+ Plugin blocksHubPlugin = Bukkit.getPluginManager().getPlugin("BlocksHub");
+ if (blocksHubPlugin == null) {
+ enabledBlocksHub = false;
+ return null;
+ }
+ return null;
+// return ((BlocksHubBukkit) blocksHubPlugin).getApi();
+ }
+
+ private Version version = null;
+
+ public Version getVersion() {
+ Version tmp = this.version;
+ if (tmp == null) {
+ tmp = Version.NONE;
+ for (Version v : Version.values()) {
+ try {
+ BukkitQueue_0.checkVersion(v.name());
+ this.version = tmp = v;
+ if (tmp == Version.v1_13_R1) {
+ try {
+ Fawe.debug("Running 1.13 registry dumper!");
+ // TODO FIXME
+// NMSRegistryDumper dumper = new NMSRegistryDumper(MainUtil.getFile(plugin.getDataFolder(), "extrablocks.json"));
+// dumper.run();
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+ }
+ break;
+ } catch (IllegalStateException e) {}
+ }
+ }
+ return tmp;
+ }
+
+ public enum Version {
+// v1_7_R4,
+// v1_8_R3,
+// v1_9_R2,
+// v1_10_R1,
+// v1_11_R1,
+// v1_12_R2,
+ v1_13_R1,
+ NONE,
+ }
+
+ private FaweQueue getQueue(World world) {
+ switch (getVersion()) {
+ case v1_13_R1:
+// return new BukkitQueue_1_13(world);
+ default:
+ case NONE:
+ return new BukkitQueue_All(world);
+ }
+ }
+
+ private FaweQueue getQueue(String world) {
+ switch (getVersion()) {
+ case v1_13_R1:
+// return new BukkitQueue_1_13(world);
+ default:
+ case NONE:
+ return new BukkitQueue_All(world);
+ }
+ }
+}
diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/Metrics.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/Metrics.java
new file mode 100644
index 000000000..ff1deca39
--- /dev/null
+++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/Metrics.java
@@ -0,0 +1,584 @@
+package com.boydti.fawe.bukkit;
+
+import com.boydti.fawe.Fawe;
+import com.boydti.fawe.object.io.FastByteArrayOutputStream;
+import com.boydti.fawe.object.io.PGZIPOutputStream;
+import com.boydti.fawe.util.MainUtil;
+import java.io.*;
+import java.lang.reflect.InvocationTargetException;
+import java.net.Proxy;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLEncoder;
+import java.util.*;
+import java.util.logging.Level;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.plugin.Plugin;
+import org.bukkit.plugin.PluginDescriptionFile;
+import org.bukkit.scheduler.BukkitTask;
+
+public class Metrics {
+
+ /**
+ * The current revision number.
+ */
+ private static final int REVISION = 7;
+ /**
+ * The base url of the metrics domain.
+ */
+ private static final String BASE_URL = "http://report.mcstats.org";
+ /**
+ * The url used to report a server's status.
+ */
+ private static final String REPORT_URL = "/plugin/%s";
+ /**
+ * Interval of time to ping (in minutes).
+ */
+ private static final int PING_INTERVAL = 15;
+ /**
+ * The plugin this metrics submits for.
+ */
+ private final Plugin plugin;
+ /**
+ * All of the custom graphs to submit to metrics.
+ */
+ private final Set graphs = Collections.synchronizedSet(new HashSet());
+ /**
+ * Unique server id.
+ */
+ private final String guid;
+ /**
+ * Debug mode.
+ */
+ private final boolean debug;
+ /**
+ * The scheduled task.
+ */
+ private volatile BukkitTask task = null;
+
+ public Metrics(Plugin plugin) {
+ if (plugin == null) {
+ throw new IllegalArgumentException("Plugin cannot be null");
+ }
+ this.plugin = plugin;
+ this.guid = UUID.randomUUID().toString();
+ this.debug = false;
+ }
+
+ /**
+ * GZip compress a string of bytes.
+ *
+ * @param input
+ *
+ * @return byte[] the file as a byte array
+ */
+ public static byte[] gzip(String input) {
+ FastByteArrayOutputStream baos = new FastByteArrayOutputStream();
+ PGZIPOutputStream gzos = null;
+ try {
+ gzos = new PGZIPOutputStream(baos);
+ gzos.write(input.getBytes("UTF-8"));
+ } catch (IOException e) {
+ MainUtil.handleError(e);
+ } finally {
+ if (gzos != null) {
+ try {
+ gzos.close();
+ } catch (IOException ignore) {
+ }
+ }
+ }
+ return baos.toByteArray();
+ }
+
+ /**
+ * Appends a json encoded key/value pair to the given string builder.
+ *
+ * @param json
+ * @param key
+ * @param value
+ *
+ */
+ private static void appendJSONPair(StringBuilder json, String key, String value) {
+ boolean isValueNumeric = false;
+ try {
+ if (value.equals("0") || !value.endsWith("0")) {
+ Double.parseDouble(value);
+ isValueNumeric = true;
+ }
+ } catch (NumberFormatException e) {
+ isValueNumeric = false;
+ }
+ if (json.charAt(json.length() - 1) != '{') {
+ json.append(',');
+ }
+ json.append(escapeJSON(key));
+ json.append(':');
+ if (isValueNumeric) {
+ json.append(value);
+ } else {
+ json.append(escapeJSON(value));
+ }
+ }
+
+ /**
+ * Escape a string to create a valid JSON string
+ *
+ * @param text
+ *
+ * @return String
+ */
+ private static String escapeJSON(String text) {
+ StringBuilder builder = new StringBuilder();
+ builder.append('"');
+ for (int index = 0; index < text.length(); index++) {
+ char chr = text.charAt(index);
+ switch (chr) {
+ case '"':
+ case '\\':
+ builder.append('\\');
+ builder.append(chr);
+ break;
+ case '\b':
+ builder.append("\\b");
+ break;
+ case '\t':
+ builder.append("\\t");
+ break;
+ case '\n':
+ builder.append("\\n");
+ break;
+ case '\r':
+ builder.append("\\r");
+ break;
+ default:
+ if (chr < ' ') {
+ String t = "000" + Integer.toHexString(chr);
+ builder.append("\\u" + t.substring(t.length() - 4));
+ } else {
+ builder.append(chr);
+ }
+ break;
+ }
+ }
+ builder.append('"');
+ return builder.toString();
+ }
+
+ /**
+ * Encode text as UTF-8
+ *
+ * @param text the text to encode
+ *
+ * @return the encoded text, as UTF-8
+ */
+ private static String urlEncode(String text) throws UnsupportedEncodingException {
+ return URLEncoder.encode(text, "UTF-8");
+ }
+
+ /**
+ * Construct and create a Graph that can be used to separate specific plotters to their own graphs on the metrics
+ * website. Plotters can be added to the graph object returned.
+ *
+ * @param name The name of the graph
+ *
+ * @return Graph object created. Will never return NULL under normal circumstances unless bad parameters are given
+ */
+ public Graph createGraph(String name) {
+ if (name == null) {
+ throw new IllegalArgumentException("Graph name cannot be null");
+ }
+ // Construct the graph object
+ Graph graph = new Graph(name);
+ // Now we can add our graph
+ this.graphs.add(graph);
+ // and return back
+ return graph;
+ }
+
+ /**
+ * Add a Graph object to BukkitMetrics that represents data for the plugin that should be sent to the backend
+ *
+ * @param graph The name of the graph
+ */
+ public void addGraph(Graph graph) {
+ if (graph == null) {
+ throw new IllegalArgumentException("Graph cannot be null");
+ }
+ this.graphs.add(graph);
+ }
+
+ /**
+ * Start measuring statistics. This will immediately create an async repeating task as the plugin and send the
+ * initial data to the metrics backend, and then after that it will post in increments of PING_INTERVAL * 1200
+ * ticks.
+ *
+ * @return True if statistics measuring is running, otherwise false.
+ */
+ public boolean start() {
+ // Is metrics already running?
+ if (this.task != null) {
+ return true;
+ }
+ // Begin hitting the server with glorious data
+ this.task = this.plugin.getServer().getScheduler().runTaskTimerAsynchronously(this.plugin, new Runnable() {
+ private boolean firstPost = true;
+
+ @Override
+ public void run() {
+ try {
+ postPlugin(!this.firstPost);
+ // After the first post we set firstPost to
+ // false
+ // Each post thereafter will be a ping
+ this.firstPost = false;
+ } catch (IOException e) {
+ MainUtil.handleError(e);
+ if (Metrics.this.debug) {
+ Bukkit.getLogger().log(Level.INFO, "[Metrics] " + e.getMessage());
+ }
+ }
+ }
+ }, 0, PING_INTERVAL * 1200);
+ return true;
+ }
+
+ /**
+ * Enables metrics for the server by setting "opt-out" to false in the config file and starting the metrics task.
+ *
+ * @throws IOException
+ */
+ public void enable() {
+ // Enable Task, if it is not running
+ if (this.task == null) {
+ start();
+ }
+ }
+
+ /**
+ * Disables metrics for the server by setting "opt-out" to true in the config file and canceling the metrics task.
+ *
+ */
+ public void disable() {
+ // Disable Task, if it is running
+ if (this.task != null) {
+ this.task.cancel();
+ this.task = null;
+ }
+ }
+
+ /**
+ * Gets the File object of the config file that should be used to store
+ * data such as the GUID and opt-out status.
+ *
+ * @return the File object for the config file
+ */
+ public File getConfigFile() {
+ // I believe the easiest way to get the base folder (e.g craftbukkit set
+ // via -P) for plugins to use
+ // is to abuse the plugin object we already have
+ // plugin.getDataFolder() => base/plugins/PluginA/
+ // pluginsFolder => base/plugins/
+ // The base is not necessarily relative to the startup directory.
+ File pluginsFolder = this.plugin.getDataFolder().getParentFile();
+ // return => base/plugins/PluginMetrics/config.yml
+ return new File(new File(pluginsFolder, "PluginMetrics"), "config.yml");
+ }
+
+ /**
+ * Generic method that posts a plugin to the metrics website.
+ */
+ private void postPlugin(boolean isPing) throws IOException {
+ // Server software specific section
+ PluginDescriptionFile description = this.plugin.getDescription();
+ String pluginName = description.getName();
+ boolean onlineMode = Bukkit.getServer().getOnlineMode(); // TRUE if online mode is enabled
+ String pluginVersion = description.getVersion();
+ String serverVersion = Bukkit.getVersion();
+ int playersOnline = 0;
+ try {
+ if (Bukkit.class.getMethod("getOnlinePlayers", new Class>[0]).getReturnType() == Collection.class) {
+ playersOnline = ((Collection>) Bukkit.class.getMethod("getOnlinePlayers", new Class>[0]).invoke(null)).size();
+ } else {
+ playersOnline = ((Player[]) Bukkit.class.getMethod("getOnlinePlayers", new Class>[0]).invoke(null)).length;
+ }
+ } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException ex) {
+ ex.printStackTrace();
+ }
+ // END server software specific section -- all code below does not use
+ // any code outside of this class / Java
+ // Construct the post data
+ StringBuilder json = new StringBuilder(1024);
+ json.append('{');
+ // The plugin's description file containing all of the plugin data such as name, version, author, etc
+ appendJSONPair(json, "guid", this.guid);
+ appendJSONPair(json, "plugin_version", pluginVersion);
+ appendJSONPair(json, "server_version", serverVersion);
+ appendJSONPair(json, "players_online", Integer.toString(playersOnline));
+ // New data as of R6
+ String osname = System.getProperty("os.name");
+ String osarch = System.getProperty("os.arch");
+ String osversion = System.getProperty("os.version");
+ String java_version = System.getProperty("java.version");
+ int coreCount = Runtime.getRuntime().availableProcessors();
+ // normalize os arch .. amd64 -> x86_64
+ if (osarch.equals("amd64")) {
+ osarch = "x86_64";
+ }
+ appendJSONPair(json, "osname", osname);
+ appendJSONPair(json, "osarch", osarch);
+ appendJSONPair(json, "osversion", osversion);
+ appendJSONPair(json, "cores", Integer.toString(coreCount));
+ appendJSONPair(json, "auth_mode", onlineMode ? "1" : "0");
+ appendJSONPair(json, "java_version", java_version);
+ // If we're pinging, append it
+ if (isPing) {
+ appendJSONPair(json, "ping", "1");
+ }
+ if (!this.graphs.isEmpty()) {
+ synchronized (this.graphs) {
+ json.append(',');
+ json.append('"');
+ json.append("graphs");
+ json.append('"');
+ json.append(':');
+ json.append('{');
+ boolean firstGraph = true;
+ for (Graph graph : this.graphs) {
+ StringBuilder graphJson = new StringBuilder();
+ graphJson.append('{');
+ for (Plotter plotter : graph.getPlotters()) {
+ appendJSONPair(graphJson, plotter.getColumnName(), Integer.toString(plotter.getValue()));
+ }
+ graphJson.append('}');
+ if (!firstGraph) {
+ json.append(',');
+ }
+ json.append(escapeJSON(graph.getName()));
+ json.append(':');
+ json.append(graphJson);
+ firstGraph = false;
+ }
+ json.append('}');
+ }
+ }
+ // close json
+ json.append('}');
+ // Create the url
+ URL url = new URL(BASE_URL + String.format(REPORT_URL, urlEncode(pluginName)));
+ // Connect to the website
+ URLConnection connection;
+ // Mineshafter creates a socks proxy, so we can safely bypass it
+ // It does not reroute POST requests so we need to go around it
+ if (isMineshafterPresent()) {
+ connection = url.openConnection(Proxy.NO_PROXY);
+ } else {
+ connection = url.openConnection();
+ }
+ byte[] uncompressed = json.toString().getBytes();
+ byte[] compressed = gzip(json.toString());
+ // Headers
+ connection.addRequestProperty("User-Agent", "MCStats/" + REVISION);
+ connection.addRequestProperty("Content-Type", "application/json");
+ connection.addRequestProperty("Content-Encoding", "gzip");
+ connection.addRequestProperty("Content-Length", Integer.toString(compressed.length));
+ connection.addRequestProperty("Accept", "application/json");
+ connection.addRequestProperty("Connection", "close");
+ connection.setDoOutput(true);
+ if (this.debug) {
+ Fawe.debug("[Metrics] Prepared request for " + pluginName + " uncompressed=" + uncompressed.length + " compressed=" + compressed.length);
+ }
+ try {
+ try (OutputStream os = connection.getOutputStream()) {
+ os.write(compressed);
+ os.flush();
+ }
+ String response;
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
+ response = reader.readLine();
+ if (this.debug) {
+ Fawe.debug("[Metrics] Response for " + pluginName + ": " + response);
+ }
+ }
+ if (response == null || response.startsWith("ERR") || response.startsWith("7")) {
+ if (response == null) {
+ response = "null";
+ } else if (response.startsWith("7")) {
+ response = response.substring(response.startsWith("7,") ? 2 : 1);
+ }
+ throw new IOException(response);
+ } else {
+ // Is this the first update this hour?
+ if ("1".equals(response) || response.contains("This is your first update this hour")) {
+ synchronized (this.graphs) {
+ for (Graph graph : this.graphs) {
+ for (Plotter plotter : graph.getPlotters()) {
+ plotter.reset();
+ }
+ }
+ }
+ }
+ }
+ } catch (Exception e) {
+ if (this.debug) {
+ MainUtil.handleError(e);
+ }
+ }
+ }
+
+ /**
+ * Check if mineshafter is present. If it is, we need to bypass it to send POST requests
+ *
+ * @return true if mineshafter is installed on the server
+ */
+ private boolean isMineshafterPresent() {
+ try {
+ Class.forName("mineshafter.MineServer");
+ return true;
+ } catch (ClassNotFoundException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Represents a custom graph on the website
+ */
+ public static class Graph {
+
+ /**
+ * The graph's name, alphanumeric and spaces only :) If it does not comply to the above when submitted, it is
+ * rejected
+ */
+ private final String name;
+ /**
+ * The set of plotters that are contained within this graph
+ */
+ private final Set plotters = new LinkedHashSet<>();
+
+ private Graph(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Gets the graph's name
+ *
+ * @return the Graph's name
+ */
+ public String getName() {
+ return this.name;
+ }
+
+ /**
+ * Add a plotter to the graph, which will be used to plot entries
+ *
+ * @param plotter the plotter to add to the graph
+ */
+ public void addPlotter(Plotter plotter) {
+ this.plotters.add(plotter);
+ }
+
+ /**
+ * Remove a plotter from the graph
+ *
+ * @param plotter the plotter to remove from the graph
+ */
+ public void removePlotter(Plotter plotter) {
+ this.plotters.remove(plotter);
+ }
+
+ /**
+ * Gets an unmodifiable set of the plotter objects in the graph
+ *
+ * @return an unmodifiable {@link Set} of the plotter objects
+ */
+ public Set getPlotters() {
+ return Collections.unmodifiableSet(this.plotters);
+ }
+
+ @Override
+ public int hashCode() {
+ return this.name.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if (!(object instanceof Graph)) {
+ return false;
+ }
+ Graph graph = (Graph) object;
+ return graph.name.equals(this.name);
+ }
+
+ /**
+ * Called when the server owner decides to opt-out of BukkitMetrics while the server is running.
+ */
+ protected void onOptOut() {
+ }
+ }
+
+ /**
+ * Interface used to collect custom data for a plugin
+ */
+ public abstract static class Plotter {
+
+ /**
+ * The plot's name
+ */
+ private final String name;
+
+ /**
+ * Construct a plotter with the default plot name
+ */
+ public Plotter() {
+ this("Default");
+ }
+
+ /**
+ * Construct a plotter with a specific plot name
+ *
+ * @param name the name of the plotter to use, which will show up on the website
+ */
+ public Plotter(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Get the current value for the plotted point. Since this function defers to an external function it may or may
+ * not return immediately thus cannot be guaranteed to be thread friendly or safe. This function can be called
+ * from any thread so care should be taken when accessing resources that need to be synchronized.
+ *
+ * @return the current value for the point to be plotted.
+ */
+ public abstract int getValue();
+
+ /**
+ * Get the column name for the plotted point
+ *
+ * @return the plotted point's column name
+ */
+ public String getColumnName() {
+ return this.name;
+ }
+
+ /**
+ * Called after the website graphs have been updated
+ */
+ public void reset() {
+ }
+
+ @Override
+ public int hashCode() {
+ return getColumnName().hashCode();
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if (!(object instanceof Plotter)) {
+ return false;
+ }
+ Plotter plotter = (Plotter) object;
+ return plotter.name.equals(this.name) && plotter.getValue() == getValue();
+ }
+ }
+}
\ No newline at end of file
diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/Spigot_v1_13_R1.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/Spigot_v1_13_R1.java
new file mode 100644
index 000000000..d1c061c5c
--- /dev/null
+++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/Spigot_v1_13_R1.java
@@ -0,0 +1,482 @@
+/*
+ * WorldEdit, a Minecraft world manipulation toolkit
+ * Copyright (C) sk89q
+ * Copyright (C) WorldEdit team and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.boydti.fawe.bukkit.adapter;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import ignore.test.DummyServer;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Maps;
+import com.sk89q.jnbt.ByteArrayTag;
+import com.sk89q.jnbt.ByteTag;
+import com.sk89q.jnbt.CompoundTag;
+import com.sk89q.jnbt.DoubleTag;
+import com.sk89q.jnbt.EndTag;
+import com.sk89q.jnbt.FloatTag;
+import com.sk89q.jnbt.IntArrayTag;
+import com.sk89q.jnbt.IntTag;
+import com.sk89q.jnbt.ListTag;
+import com.sk89q.jnbt.LongTag;
+import com.sk89q.jnbt.NBTConstants;
+import com.sk89q.jnbt.ShortTag;
+import com.sk89q.jnbt.StringTag;
+import com.sk89q.jnbt.Tag;
+import com.sk89q.worldedit.blocks.BaseBlock;
+import com.sk89q.worldedit.world.block.BlockState;
+import com.sk89q.worldedit.bukkit.BukkitAdapter;
+import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter;
+import com.sk89q.worldedit.entity.BaseEntity;
+import com.sk89q.worldedit.internal.Constants;
+import com.sk89q.worldedit.registry.state.BooleanProperty;
+import com.sk89q.worldedit.registry.state.DirectionalProperty;
+import com.sk89q.worldedit.registry.state.EnumProperty;
+import com.sk89q.worldedit.registry.state.IntegerProperty;
+import com.sk89q.worldedit.registry.state.Property;
+import com.sk89q.worldedit.util.Direction;
+import com.sk89q.worldedit.world.block.BlockStateHolder;
+import com.sk89q.worldedit.world.block.BlockType;
+import net.minecraft.server.v1_13_R1.BiomeBase;
+import net.minecraft.server.v1_13_R1.Block;
+import net.minecraft.server.v1_13_R1.BlockPosition;
+import net.minecraft.server.v1_13_R1.BlockStateBoolean;
+import net.minecraft.server.v1_13_R1.BlockStateDirection;
+import net.minecraft.server.v1_13_R1.BlockStateEnum;
+import net.minecraft.server.v1_13_R1.BlockStateInteger;
+import net.minecraft.server.v1_13_R1.BlockStateList;
+import net.minecraft.server.v1_13_R1.Entity;
+import net.minecraft.server.v1_13_R1.EntityTypes;
+import net.minecraft.server.v1_13_R1.IBlockData;
+import net.minecraft.server.v1_13_R1.IBlockState;
+import net.minecraft.server.v1_13_R1.INamable;
+import net.minecraft.server.v1_13_R1.MinecraftKey;
+import net.minecraft.server.v1_13_R1.NBTBase;
+import net.minecraft.server.v1_13_R1.NBTTagByte;
+import net.minecraft.server.v1_13_R1.NBTTagByteArray;
+import net.minecraft.server.v1_13_R1.NBTTagCompound;
+import net.minecraft.server.v1_13_R1.NBTTagDouble;
+import net.minecraft.server.v1_13_R1.NBTTagEnd;
+import net.minecraft.server.v1_13_R1.NBTTagFloat;
+import net.minecraft.server.v1_13_R1.NBTTagInt;
+import net.minecraft.server.v1_13_R1.NBTTagIntArray;
+import net.minecraft.server.v1_13_R1.NBTTagList;
+import net.minecraft.server.v1_13_R1.NBTTagLong;
+import net.minecraft.server.v1_13_R1.NBTTagShort;
+import net.minecraft.server.v1_13_R1.NBTTagString;
+import net.minecraft.server.v1_13_R1.TileEntity;
+import net.minecraft.server.v1_13_R1.World;
+import net.minecraft.server.v1_13_R1.WorldServer;
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.block.Biome;
+import org.bukkit.craftbukkit.v1_13_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_13_R1.CraftWorld;
+import org.bukkit.craftbukkit.v1_13_R1.block.CraftBlock;
+import org.bukkit.craftbukkit.v1_13_R1.entity.CraftEntity;
+import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.*;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nullable;
+
+public final class Spigot_v1_13_R1 implements BukkitImplAdapter {
+
+ private final Logger logger = Logger.getLogger(getClass().getCanonicalName());
+
+ private final Field nbtListTagListField;
+ private final Method nbtCreateTagMethod;
+
+ // ------------------------------------------------------------------------
+ // Code that may break between versions of Minecraft
+ // ------------------------------------------------------------------------
+
+ public Spigot_v1_13_R1() throws NoSuchFieldException, NoSuchMethodException {
+ // A simple test
+ if (Bukkit.getServer().getClass() != DummyServer.class) CraftServer.class.cast(Bukkit.getServer());
+ // test between 1.12 and 1.12.1 since md_5 didn't update revision numbers
+ TileEntity.class.getDeclaredMethod("load", NBTTagCompound.class);
+
+ // The list of tags on an NBTTagList
+ nbtListTagListField = NBTTagList.class.getDeclaredField("list");
+ nbtListTagListField.setAccessible(true);
+
+ // The method to create an NBTBase tag given its type ID
+ nbtCreateTagMethod = NBTBase.class.getDeclaredMethod("createTag", byte.class);
+ nbtCreateTagMethod.setAccessible(true);
+ }
+
+ /**
+ * Read the given NBT data into the given tile entity.
+ *
+ * @param tileEntity the tile entity
+ * @param tag the tag
+ */
+ private static void readTagIntoTileEntity(NBTTagCompound tag, TileEntity tileEntity) {
+ tileEntity.load(tag);
+ }
+
+ /**
+ * Write the tile entity's NBT data to the given tag.
+ *
+ * @param tileEntity the tile entity
+ * @param tag the tag
+ */
+ private static void readTileEntityIntoTag(TileEntity tileEntity, NBTTagCompound tag) {
+ tileEntity.save(tag);
+ }
+
+ /**
+ * Get the ID string of the given entity.
+ *
+ * @param entity the entity
+ * @return the entity ID or null if one is not known
+ */
+ @Nullable
+ private static String getEntityId(Entity entity) {
+ MinecraftKey minecraftkey = EntityTypes.getName(entity.getBukkitEntity().getHandle().P());
+ return minecraftkey == null ? null : minecraftkey.toString();
+ }
+
+ /**
+ * Create an entity using the given entity ID.
+ *
+ * @param id the entity ID
+ * @param world the world
+ * @return an entity or null
+ */
+ @Nullable
+ private static Entity createEntityFromId(String id, World world) {
+ return EntityTypes.a(world, new MinecraftKey(id));
+ }
+
+ /**
+ * Write the given NBT data into the given entity.
+ *
+ * @param entity the entity
+ * @param tag the tag
+ */
+ private static void readTagIntoEntity(NBTTagCompound tag, Entity entity) {
+ entity.f(tag);
+ }
+
+ /**
+ * Write the entity's NBT data to the given tag.
+ *
+ * @param entity the entity
+ * @param tag the tag
+ */
+ private static void readEntityIntoTag(Entity entity, NBTTagCompound tag) {
+ entity.save(tag);
+ }
+
+ // ------------------------------------------------------------------------
+ // Code that is less likely to break
+ // ------------------------------------------------------------------------
+
+ @Override
+ public int getBiomeId(Biome biome) {
+ BiomeBase mcBiome = CraftBlock.biomeToBiomeBase(biome);
+ return mcBiome != null ? BiomeBase.a(mcBiome) : 0;
+ }
+
+ @Override
+ public Biome getBiome(int id) {
+ BiomeBase mcBiome = BiomeBase.getBiome(id);
+ return CraftBlock.biomeBaseToBiome(mcBiome); // Defaults to ocean if it's an invalid ID
+ }
+
+ @SuppressWarnings("deprecation")
+ @Override
+ public BlockState getBlock(Location location) {
+ checkNotNull(location);
+
+ CraftWorld craftWorld = ((CraftWorld) location.getWorld());
+ int x = location.getBlockX();
+ int y = location.getBlockY();
+ int z = location.getBlockZ();
+
+ org.bukkit.block.Block bukkitBlock = location.getBlock();
+ BlockState state = BukkitAdapter.adapt(bukkitBlock.getBlockData());
+ if (state.getBlockType().getMaterial().hasContainer()) {
+ //Read the NBT data
+ TileEntity te = craftWorld.getHandle().getTileEntity(new BlockPosition(x, y, z));
+ if (te != null) {
+ NBTTagCompound tag = new NBTTagCompound();
+ readTileEntityIntoTag(te, tag); // Load data
+ return new BaseBlock(state, (CompoundTag) toNative(tag));
+ }
+ }
+
+ return state;
+ }
+
+ @Override
+ public boolean setBlock(Location location, BlockStateHolder state, boolean notifyAndLight) {
+ checkNotNull(location);
+ checkNotNull(state);
+
+ CraftWorld craftWorld = ((CraftWorld) location.getWorld());
+ int x = location.getBlockX();
+ int y = location.getBlockY();
+ int z = location.getBlockZ();
+
+ // Two pass update:
+ // Note, this will notify blocks BEFORE the tile entity is set
+ location.getBlock().setBlockData(BukkitAdapter.adapt(state), false);
+
+ // Copy NBT data for the block
+ CompoundTag nativeTag = state.getNbtData();
+ if (nativeTag != null) {
+ // We will assume that the tile entity was created for us,
+ // though we do not do this on the Forge version
+ TileEntity tileEntity = craftWorld.getHandle().getTileEntity(new BlockPosition(x, y, z));
+ if (tileEntity != null) {
+ NBTTagCompound tag = (NBTTagCompound) fromNative(nativeTag);
+ tag.set("x", new NBTTagInt(x));
+ tag.set("y", new NBTTagInt(y));
+ tag.set("z", new NBTTagInt(z));
+ readTagIntoTileEntity(tag, tileEntity); // Load data
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public BaseEntity getEntity(org.bukkit.entity.Entity entity) {
+ checkNotNull(entity);
+
+ CraftEntity craftEntity = ((CraftEntity) entity);
+ Entity mcEntity = craftEntity.getHandle();
+
+ String id = getEntityId(mcEntity);
+
+ if (id != null) {
+ NBTTagCompound tag = new NBTTagCompound();
+ readEntityIntoTag(mcEntity, tag);
+ return new BaseEntity(com.sk89q.worldedit.world.entity.EntityTypes.get(id), (CompoundTag) toNative(tag));
+ } else {
+ return null;
+ }
+ }
+
+ @Nullable
+ @Override
+ public org.bukkit.entity.Entity createEntity(Location location, BaseEntity state) {
+ checkNotNull(location);
+ checkNotNull(state);
+
+ CraftWorld craftWorld = ((CraftWorld) location.getWorld());
+ WorldServer worldServer = craftWorld.getHandle();
+
+ Entity createdEntity = createEntityFromId(state.getType().getId(), craftWorld.getHandle());
+
+ if (createdEntity != null) {
+ CompoundTag nativeTag = state.getNbtData();
+ if (nativeTag != null) {
+ NBTTagCompound tag = (NBTTagCompound) fromNative(nativeTag);
+ for (String name : Constants.NO_COPY_ENTITY_NBT_FIELDS) {
+ tag.remove(name);
+ }
+ readTagIntoEntity(tag, createdEntity);
+ }
+
+ createdEntity.setLocation(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch());
+
+ worldServer.addEntity(createdEntity, SpawnReason.CUSTOM);
+ return createdEntity.getBukkitEntity();
+ } else {
+ return null;
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Map getProperties(BlockType blockType) {
+ Block block;
+ try {
+ block = Block.getByName(blockType.getId());
+ } catch (Throwable e) {
+ return Collections.emptyMap();
+ }
+ if (block == null) {
+ logger.warning("Failed to find properties for " + blockType.getId());
+ return Collections.emptyMap();
+ }
+ Map properties = Maps.newLinkedHashMap();
+ BlockStateList blockStateList = block.getStates();
+ for (IBlockState state : blockStateList.d()) {
+ Property property;
+ if (state instanceof BlockStateBoolean) {
+ property = new BooleanProperty(state.a(), ImmutableList.copyOf(state.d()));
+ } else if (state instanceof BlockStateDirection) {
+ property = new DirectionalProperty(state.a(),
+ (List) state.d().stream().map(e -> Direction.valueOf(((INamable) e).getName().toUpperCase())).collect(Collectors.toList()));
+ } else if (state instanceof BlockStateEnum) {
+ property = new EnumProperty(state.a(),
+ (List) state.d().stream().map(e -> ((INamable) e).getName()).collect(Collectors.toList()));
+ } else if (state instanceof BlockStateInteger) {
+ property = new IntegerProperty(state.a(), ImmutableList.copyOf(state.d()));
+ } else {
+ throw new IllegalArgumentException("WorldEdit needs an update to support " + state.getClass().getSimpleName());
+ }
+
+ properties.put(property.getName(), property);
+ }
+ return properties;
+ }
+
+ /**
+ * Converts from a non-native NMS NBT structure to a native WorldEdit NBT
+ * structure.
+ *
+ * @param foreign non-native NMS NBT structure
+ * @return native WorldEdit NBT structure
+ */
+ @SuppressWarnings("unchecked")
+ @Override
+ public Tag toNative(NBTBase foreign) {
+ if (foreign == null) {
+ return null;
+ }
+ if (foreign instanceof NBTTagCompound) {
+ Map values = new HashMap<>();
+ Set foreignKeys = ((NBTTagCompound) foreign).getKeys(); // map.keySet
+
+ for (String str : foreignKeys) {
+ NBTBase base = ((NBTTagCompound) foreign).get(str);
+ values.put(str, toNative(base));
+ }
+ return new CompoundTag(values);
+ } else if (foreign instanceof NBTTagByte) {
+ return new ByteTag(((NBTTagByte) foreign).g()); // getByte
+ } else if (foreign instanceof NBTTagByteArray) {
+ return new ByteArrayTag(((NBTTagByteArray) foreign).c()); // data
+ } else if (foreign instanceof NBTTagDouble) {
+ return new DoubleTag(((NBTTagDouble) foreign).asDouble()); // getDouble
+ } else if (foreign instanceof NBTTagFloat) {
+ return new FloatTag(((NBTTagFloat) foreign).i()); // getFloat
+ } else if (foreign instanceof NBTTagInt) {
+ return new IntTag(((NBTTagInt) foreign).e()); // getInt
+ } else if (foreign instanceof NBTTagIntArray) {
+ return new IntArrayTag(((NBTTagIntArray) foreign).d()); // data
+ } else if (foreign instanceof NBTTagList) {
+ try {
+ return toNativeList((NBTTagList) foreign);
+ } catch (Throwable e) {
+ logger.log(Level.WARNING, "Failed to convert NBTTagList", e);
+ return new ListTag(ByteTag.class, new ArrayList());
+ }
+ } else if (foreign instanceof NBTTagLong) {
+ return new LongTag(((NBTTagLong) foreign).d()); // getLong
+ } else if (foreign instanceof NBTTagShort) {
+ return new ShortTag(((NBTTagShort) foreign).f()); // getShort
+ } else if (foreign instanceof NBTTagString) {
+ return new StringTag(foreign.b_()); // data
+ } else if (foreign instanceof NBTTagEnd) {
+ return new EndTag();
+ } else {
+ throw new IllegalArgumentException("Don't know how to make native " + foreign.getClass().getCanonicalName());
+ }
+ }
+
+ /**
+ * Convert a foreign NBT list tag into a native WorldEdit one.
+ *
+ * @param foreign the foreign tag
+ * @return the converted tag
+ * @throws NoSuchFieldException on error
+ * @throws SecurityException on error
+ * @throws IllegalArgumentException on error
+ * @throws IllegalAccessException on error
+ */
+ public ListTag toNativeList(NBTTagList foreign) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
+ List values = new ArrayList<>();
+ int type = foreign.getTypeId();
+
+ List foreignList;
+ foreignList = (List) nbtListTagListField.get(foreign);
+ for (int i = 0; i < foreign.size(); i++) {
+ NBTBase element = (NBTBase) foreignList.get(i);
+ values.add(toNative(element)); // List elements shouldn't have names
+ }
+
+ Class extends Tag> cls = NBTConstants.getClassFromType(type);
+ return new ListTag(cls, values);
+ }
+
+ /**
+ * Converts a WorldEdit-native NBT structure to a NMS structure.
+ *
+ * @param foreign structure to convert
+ * @return non-native structure
+ */
+ @Override
+ public NBTBase fromNative(Tag foreign) {
+ if (foreign == null) {
+ return null;
+ }
+ if (foreign instanceof CompoundTag) {
+ NBTTagCompound tag = new NBTTagCompound();
+ for (Map.Entry entry : ((CompoundTag) foreign)
+ .getValue().entrySet()) {
+ tag.set(entry.getKey(), fromNative(entry.getValue()));
+ }
+ return tag;
+ } else if (foreign instanceof ByteTag) {
+ return new NBTTagByte(((ByteTag) foreign).getValue());
+ } else if (foreign instanceof ByteArrayTag) {
+ return new NBTTagByteArray(((ByteArrayTag) foreign).getValue());
+ } else if (foreign instanceof DoubleTag) {
+ return new NBTTagDouble(((DoubleTag) foreign).getValue());
+ } else if (foreign instanceof FloatTag) {
+ return new NBTTagFloat(((FloatTag) foreign).getValue());
+ } else if (foreign instanceof IntTag) {
+ return new NBTTagInt(((IntTag) foreign).getValue());
+ } else if (foreign instanceof IntArrayTag) {
+ return new NBTTagIntArray(((IntArrayTag) foreign).getValue());
+ } else if (foreign instanceof ListTag) {
+ NBTTagList tag = new NBTTagList();
+ ListTag> foreignList = (ListTag) foreign;
+ for (Tag t : foreignList.getValue()) {
+ tag.add(fromNative(t));
+ }
+ return tag;
+ } else if (foreign instanceof LongTag) {
+ return new NBTTagLong(((LongTag) foreign).getValue());
+ } else if (foreign instanceof ShortTag) {
+ return new NBTTagShort(((ShortTag) foreign).getValue());
+ } else if (foreign instanceof StringTag) {
+ return new NBTTagString(((StringTag) foreign).getValue());
+ } else if (foreign instanceof EndTag) {
+ try {
+ return (NBTBase) nbtCreateTagMethod.invoke(null, (byte) 0);
+ } catch (Exception e) {
+ return null;
+ }
+ } else {
+ throw new IllegalArgumentException("Don't know how to make NMS " + foreign.getClass().getCanonicalName());
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/chat/ArrayWrapper.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/chat/ArrayWrapper.java
new file mode 100644
index 000000000..fc84afa67
--- /dev/null
+++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/chat/ArrayWrapper.java
@@ -0,0 +1,110 @@
+package com.boydti.fawe.bukkit.chat;
+
+import java.lang.reflect.Array;
+import java.util.Arrays;
+import java.util.Collection;
+import org.apache.commons.lang.Validate;
+
+/**
+ * Represents a wrapper around an array class of an arbitrary reference type,
+ * which properly implements "value" hash code and equality functions.
+ *
+ * This class is intended for use as a key to a map.
+ *
+ *
+ * @param The type of elements in the array.
+ * @author Glen Husman
+ * @see Arrays
+ */
+public final class ArrayWrapper {
+
+ /**
+ * Creates an array wrapper with some elements.
+ *
+ * @param elements The elements of the array.
+ */
+ public ArrayWrapper(E... elements) {
+ setArray(elements);
+ }
+
+ private E[] _array;
+
+ /**
+ * Retrieves a reference to the wrapped array instance.
+ *
+ * @return The array wrapped by this instance.
+ */
+ public E[] getArray() {
+ return _array;
+ }
+
+ /**
+ * Set this wrapper to wrap a new array instance.
+ *
+ * @param array The new wrapped array.
+ */
+ public void setArray(E[] array) {
+ Validate.notNull(array, "The array must not be null.");
+ _array = array;
+ }
+
+ /**
+ * Determines if this object has a value equivalent to another object.
+ *
+ * @see Arrays#equals(Object[], Object[])
+ */
+ @SuppressWarnings("rawtypes")
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof ArrayWrapper)) {
+ return false;
+ }
+ return Arrays.equals(_array, ((ArrayWrapper) other)._array);
+ }
+
+ /**
+ * Gets the hash code represented by this objects value.
+ *
+ * @return This object's hash code.
+ * @see Arrays#hashCode(Object[])
+ */
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(_array);
+ }
+
+ /**
+ * Converts an iterable element collection to an array of elements.
+ * The iteration order of the specified object will be used as the array element order.
+ *
+ * @param list The iterable of objects which will be converted to an array.
+ * @param c The type of the elements of the array.
+ * @return An array of elements in the specified iterable.
+ */
+ @SuppressWarnings("unchecked")
+ public static T[] toArray(Iterable extends T> list, Class c) {
+ int size = -1;
+ if (list instanceof Collection>) {
+ @SuppressWarnings("rawtypes")
+ Collection coll = (Collection) list;
+ size = coll.size();
+ }
+
+
+ if (size < 0) {
+ size = 0;
+ // Ugly hack: Count it ourselves
+ for (@SuppressWarnings("unused") T element : list) {
+ size++;
+ }
+ }
+
+ T[] result = (T[]) Array.newInstance(c, size);
+ int i = 0;
+ for (T element : list) { // Assumes iteration order is consistent
+ result[i++] = element; // Assign array element at index THEN increment counter
+ }
+ return result;
+ }
+
+}
diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/chat/BukkitChatManager.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/chat/BukkitChatManager.java
new file mode 100644
index 000000000..45a1ef127
--- /dev/null
+++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/chat/BukkitChatManager.java
@@ -0,0 +1,64 @@
+package com.boydti.fawe.bukkit.chat;
+
+import com.boydti.fawe.bukkit.BukkitPlayer;
+import com.boydti.fawe.config.BBC;
+import com.boydti.fawe.object.FawePlayer;
+import com.boydti.fawe.util.chat.ChatManager;
+import com.boydti.fawe.util.chat.Message;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.bukkit.ChatColor;
+
+public class BukkitChatManager implements ChatManager {
+
+ @Override
+ public FancyMessage builder() {
+ return new FancyMessage("");
+ }
+
+ @Override
+ public void color(Message message, String color) {
+ message.$(this).color(ChatColor.getByChar(BBC.color(color).substring(1)));
+ }
+
+ @Override
+ public void tooltip(Message message, Message... tooltips) {
+ List lines = new ArrayList<>();
+ for (Message tooltip : tooltips) {
+ lines.add(tooltip.$(this));
+ }
+ message.$(this).formattedTooltip(lines);
+ }
+
+ @Override
+ public void command(Message message, String command) {
+ message.$(this).command(command);
+ }
+
+ @Override
+ public void text(Message message, String text) {
+ message.$(this).color(BBC.color(text));
+ }
+
+ @Override
+ public void send(Message Message, FawePlayer player) {
+ if (!(player instanceof BukkitPlayer)) {
+ player.sendMessage(Message.$(this).toOldMessageFormat());
+ } else {
+ Message.$(this).send(((BukkitPlayer) player).parent);
+ }
+ }
+
+ @Override
+ public void suggest(Message Message, String command) {
+ Message.$(this).suggest(command);
+ }
+
+ @Override
+ public void link(Message Message, String url) {
+ Message.$(this).link(url);
+ }
+
+
+}
diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/chat/FancyMessage.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/chat/FancyMessage.java
new file mode 100644
index 000000000..35045946e
--- /dev/null
+++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/chat/FancyMessage.java
@@ -0,0 +1,955 @@
+package com.boydti.fawe.bukkit.chat;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.google.gson.stream.JsonWriter;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+import java.util.logging.Level;
+import org.bukkit.Achievement;
+import org.bukkit.Bukkit;
+import org.bukkit.ChatColor;
+import org.bukkit.Material;
+import org.bukkit.Statistic;
+import org.bukkit.Statistic.Type;
+import org.bukkit.command.CommandSender;
+import org.bukkit.configuration.serialization.ConfigurationSerializable;
+import org.bukkit.configuration.serialization.ConfigurationSerialization;
+import org.bukkit.entity.EntityType;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.ItemStack;
+
+
+import static com.boydti.fawe.bukkit.chat.TextualComponent.rawText;
+
+/**
+ * Represents a formattable message. Such messages can use elements such as colors, formatting codes, hover and click data, and other features provided by the vanilla Minecraft JSON message formatter.
+ * This class allows plugins to emulate the functionality of the vanilla Minecraft tellraw command.
+ *
+ * This class follows the builder pattern, allowing for method chaining.
+ * It is set up such that invocations of property-setting methods will affect the current editing component,
+ * and a call to {@link #then()} or {@link #then(String)} will append a new editing component to the end of the message,
+ * optionally initializing it with text. Further property-setting method calls will affect that editing component.
+ *
+ */
+public class FancyMessage implements JsonRepresentedObject, Cloneable, Iterable, ConfigurationSerializable {
+
+ static {
+ ConfigurationSerialization.registerClass(FancyMessage.class);
+ }
+
+ private List messageParts;
+ private int index = 0;
+ private String jsonString;
+ private boolean dirty;
+
+ private static Constructor> nmsPacketPlayOutChatConstructor;
+
+ @Override
+ public FancyMessage clone() throws CloneNotSupportedException {
+ FancyMessage instance = (FancyMessage) super.clone();
+ instance.messageParts = new ArrayList<>(messageParts.size());
+ for (int i = 0; i < messageParts.size(); i++) {
+ instance.messageParts.add(i, messageParts.get(i).clone());
+ }
+ instance.index = index;
+ instance.dirty = false;
+ instance.jsonString = null;
+ return instance;
+ }
+
+ /**
+ * Creates a JSON message with text.
+ *
+ * @param firstPartText The existing text in the message.
+ */
+ public FancyMessage(final String firstPartText) {
+ this(rawText(firstPartText));
+ }
+
+ private FancyMessage(final TextualComponent firstPartText) {
+ messageParts = new ArrayList<>();
+ messageParts.add(new MessagePart(firstPartText));
+ index = messageParts.size();
+ jsonString = null;
+ dirty = false;
+ if (nmsPacketPlayOutChatConstructor == null) {
+ try {
+ nmsPacketPlayOutChatConstructor = Reflection.getNMSClass("PacketPlayOutChat").getDeclaredConstructor(Reflection.getNMSClass("IChatBaseComponent"));
+ nmsPacketPlayOutChatConstructor.setAccessible(true);
+ } catch (NoSuchMethodException e) {
+ Bukkit.getLogger().log(Level.SEVERE, "Could not find Minecraft method or constructor.", e);
+ } catch (SecurityException e) {
+ Bukkit.getLogger().log(Level.WARNING, "Could not access constructor.", e);
+ }
+ }
+ }
+
+ /**
+ * Creates a JSON message without text.
+ */
+ public FancyMessage() {
+ this((TextualComponent) null);
+ }
+
+ /**
+ * Sets the text of the current editing component to a value.
+ *
+ * @param text The new text of the current editing component.
+ * @return This builder instance.
+ */
+ public FancyMessage text(String text) {
+ MessagePart latest = latest();
+ latest.text = rawText(text);
+ dirty = true;
+ return this;
+ }
+
+ /**
+ * Sets the text of the current editing component to a value.
+ *
+ * @param text The new text of the current editing component.
+ * @return This builder instance.
+ */
+ public FancyMessage text(TextualComponent text) {
+ MessagePart latest = latest();
+ latest.text = text;
+ dirty = true;
+ return this;
+ }
+
+ /**
+ *
+ * @param text Text with coloring
+ * @return This builder instance.
+ * @throws IllegalArgumentException If the specified {@code ChatColor} enumeration value is not a color (but a format value).
+ */
+ public FancyMessage color(String text) {
+ index = messageParts.size();
+ boolean color = false;
+ ArrayDeque colors = new ArrayDeque<>();
+ int last = 0;
+ for (int i = 0; i < text.length(); i++) {
+ char c = text.charAt(i);
+ if (color != (color = false)) {
+ ChatColor chatColor = ChatColor.getByChar(c);
+ if (chatColor != null) {
+ if (i - last > 1) {
+ append(text.substring(last, i - 1));
+ colors.forEach(this::color);
+ colors.clear();
+ }
+ colors.add(chatColor);
+ last = i + 1;
+ }
+ }
+ if (c == '\u00A7') {
+ color = true;
+ }
+ }
+ if (text.length() - last > 0) {
+ append(text.substring(last, text.length()));
+ colors.forEach(this::color);
+ }
+ index++;
+ return this;
+ }
+
+ /**
+ * Sets the color of the current editing component to a value.
+ * Setting the color will clear current styles
+ *
+ * @param color The new color of the current editing component.
+ * @return This builder instance.
+ * @throws IllegalArgumentException If the specified {@code ChatColor} enumeration value is not a color (but a format value).
+ */
+ public FancyMessage color(ChatColor color) {
+ if (!color.isColor()) {
+ if (color.isFormat()) {
+ return style(color);
+ }
+ if (color == ChatColor.RESET) {
+ color = ChatColor.WHITE;
+ }
+ } else {
+ latest().styles.clear();
+ }
+ latest().color = color;
+ dirty = true;
+ return this;
+ }
+
+ /**
+ * Sets the stylization of the current editing component.
+ *
+ * @param styles The array of styles to apply to the editing component.
+ * @return This builder instance.
+ * @throws IllegalArgumentException If any of the enumeration values in the array do not represent formatters.
+ */
+ public FancyMessage style(ChatColor... styles) {
+ for (final ChatColor style : styles) {
+ if (!style.isFormat()) {
+ color(style);
+ }
+ }
+ latest().styles.addAll(Arrays.asList(styles));
+ dirty = true;
+ return this;
+ }
+
+ /**
+ * Set the behavior of the current editing component to instruct the client to open a file on the client side filesystem when the currently edited part of the {@code FancyMessage} is clicked.
+ *
+ * @param path The path of the file on the client filesystem.
+ * @return This builder instance.
+ */
+ public FancyMessage file(final String path) {
+ onClick("open_file", path);
+ return this;
+ }
+
+ /**
+ * Set the behavior of the current editing component to instruct the client to open a webpage in the client's web browser when the currently edited part of the {@code FancyMessage} is clicked.
+ *
+ * @param url The URL of the page to open when the link is clicked.
+ * @return This builder instance.
+ */
+ public FancyMessage link(final String url) {
+ onClick("open_url", url);
+ return this;
+ }
+
+ /**
+ * Set the behavior of the current editing component to instruct the client to replace the chat input box content with the specified string when the currently edited part of the {@code FancyMessage} is clicked.
+ * The client will not immediately send the command to the server to be executed unless the client player submits the command/chat message, usually with the enter key.
+ *
+ * @param command The text to display in the chat bar of the client.
+ * @return This builder instance.
+ */
+ public FancyMessage suggest(final String command) {
+ onClick("suggest_command", command);
+ return this;
+ }
+
+ /**
+ * Set the behavior of the current editing component to instruct the client to append the chat input box content with the specified string when the currently edited part of the {@code FancyMessage} is SHIFT-CLICKED.
+ * The client will not immediately send the command to the server to be executed unless the client player submits the command/chat message, usually with the enter key.
+ *
+ * @param command The text to append to the chat bar of the client.
+ * @return This builder instance.
+ */
+ public FancyMessage insert(final String command) {
+ onCurrent(m -> m.insertionData = command);
+ dirty = true;
+ return this;
+ }
+
+ /**
+ * Set the behavior of the current editing component to instruct the client to send the specified string to the server as a chat message when the currently edited part of the {@code FancyMessage} is clicked.
+ * The client will immediately send the command to the server to be executed when the editing component is clicked.
+ *
+ * @param command The text to display in the chat bar of the client.
+ * @return This builder instance.
+ */
+ public FancyMessage command(final String command) {
+ onClick("run_command", command);
+ return this;
+ }
+
+ /**
+ * Set the behavior of the current editing component to display information about an achievement when the client hovers over the text.
+ *
Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.
+ *
+ * @param name The name of the achievement to display, excluding the "achievement." prefix.
+ * @return This builder instance.
+ */
+ public FancyMessage achievementTooltip(final String name) {
+ onHover("show_achievement", new JsonString("achievement." + name));
+ return this;
+ }
+
+ /**
+ * Set the behavior of the current editing component to display information about an achievement when the client hovers over the text.
+ *
Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.
+ *
+ * @param which The achievement to display.
+ * @return This builder instance.
+ */
+ public FancyMessage achievementTooltip(final Achievement which) {
+ try {
+ Object achievement = Reflection.getMethod(Reflection.getOBCClass("CraftStatistic"), "getNMSAchievement", Achievement.class).invoke(null, which);
+ return achievementTooltip((String) Reflection.getField(Reflection.getNMSClass("Achievement"), "name").get(achievement));
+ } catch (IllegalAccessException e) {
+ Bukkit.getLogger().log(Level.WARNING, "Could not access method.", e);
+ return this;
+ } catch (IllegalArgumentException e) {
+ Bukkit.getLogger().log(Level.WARNING, "Argument could not be passed.", e);
+ return this;
+ } catch (InvocationTargetException e) {
+ Bukkit.getLogger().log(Level.WARNING, "A error has occurred during invoking of method.", e);
+ return this;
+ }
+ }
+
+ /**
+ * Set the behavior of the current editing component to display information about a parameterless statistic when the client hovers over the text.
+ *
Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.
+ *
+ * @param which The statistic to display.
+ * @return This builder instance.
+ * @throws IllegalArgumentException If the statistic requires a parameter which was not supplied.
+ */
+ public FancyMessage statisticTooltip(final Statistic which) {
+ Type type = which.getType();
+ if (type != Type.UNTYPED) {
+ throw new IllegalArgumentException("That statistic requires an additional " + type + " parameter!");
+ }
+ try {
+ Object statistic = Reflection.getMethod(Reflection.getOBCClass("CraftStatistic"), "getNMSStatistic", Statistic.class).invoke(null, which);
+ return achievementTooltip((String) Reflection.getField(Reflection.getNMSClass("Statistic"), "name").get(statistic));
+ } catch (IllegalAccessException e) {
+ Bukkit.getLogger().log(Level.WARNING, "Could not access method.", e);
+ return this;
+ } catch (IllegalArgumentException e) {
+ Bukkit.getLogger().log(Level.WARNING, "Argument could not be passed.", e);
+ return this;
+ } catch (InvocationTargetException e) {
+ Bukkit.getLogger().log(Level.WARNING, "A error has occurred during invoking of method.", e);
+ return this;
+ }
+ }
+
+ /**
+ * Set the behavior of the current editing component to display information about a statistic parameter with a material when the client hovers over the text.
+ *
Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.
+ *
+ * @param which The statistic to display.
+ * @param item The sole material parameter to the statistic.
+ * @return This builder instance.
+ * @throws IllegalArgumentException If the statistic requires a parameter which was not supplied, or was supplied a parameter that was not required.
+ */
+ public FancyMessage statisticTooltip(final Statistic which, Material item) {
+ Type type = which.getType();
+ if (type == Type.UNTYPED) {
+ throw new IllegalArgumentException("That statistic needs no additional parameter!");
+ }
+ if ((type == Type.BLOCK && item.isBlock()) || type == Type.ENTITY) {
+ throw new IllegalArgumentException("Wrong parameter type for that statistic - needs " + type + "!");
+ }
+ try {
+ Object statistic = Reflection.getMethod(Reflection.getOBCClass("CraftStatistic"), "getMaterialStatistic", Statistic.class, Material.class).invoke(null, which, item);
+ return achievementTooltip((String) Reflection.getField(Reflection.getNMSClass("Statistic"), "name").get(statistic));
+ } catch (IllegalAccessException e) {
+ Bukkit.getLogger().log(Level.WARNING, "Could not access method.", e);
+ return this;
+ } catch (IllegalArgumentException e) {
+ Bukkit.getLogger().log(Level.WARNING, "Argument could not be passed.", e);
+ return this;
+ } catch (InvocationTargetException e) {
+ Bukkit.getLogger().log(Level.WARNING, "A error has occurred during invoking of method.", e);
+ return this;
+ }
+ }
+
+ /**
+ * Set the behavior of the current editing component to display information about a statistic parameter with an entity type when the client hovers over the text.
+ *
Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.
+ *
+ * @param which The statistic to display.
+ * @param entity The sole entity type parameter to the statistic.
+ * @return This builder instance.
+ * @throws IllegalArgumentException If the statistic requires a parameter which was not supplied, or was supplied a parameter that was not required.
+ */
+ public FancyMessage statisticTooltip(final Statistic which, EntityType entity) {
+ Type type = which.getType();
+ if (type == Type.UNTYPED) {
+ throw new IllegalArgumentException("That statistic needs no additional parameter!");
+ }
+ if (type != Type.ENTITY) {
+ throw new IllegalArgumentException("Wrong parameter type for that statistic - needs " + type + "!");
+ }
+ try {
+ Object statistic = Reflection.getMethod(Reflection.getOBCClass("CraftStatistic"), "getEntityStatistic", Statistic.class, EntityType.class).invoke(null, which, entity);
+ return achievementTooltip((String) Reflection.getField(Reflection.getNMSClass("Statistic"), "name").get(statistic));
+ } catch (IllegalAccessException e) {
+ Bukkit.getLogger().log(Level.WARNING, "Could not access method.", e);
+ return this;
+ } catch (IllegalArgumentException e) {
+ Bukkit.getLogger().log(Level.WARNING, "Argument could not be passed.", e);
+ return this;
+ } catch (InvocationTargetException e) {
+ Bukkit.getLogger().log(Level.WARNING, "A error has occurred during invoking of method.", e);
+ return this;
+ }
+ }
+
+ /**
+ * Set the behavior of the current editing component to display information about an item when the client hovers over the text.
+ *
Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.
+ *
+ * @param itemJSON A string representing the JSON-serialized NBT data tag of an {@link ItemStack}.
+ * @return This builder instance.
+ */
+ public FancyMessage itemTooltip(final String itemJSON) {
+ onHover("show_item", new JsonString(itemJSON)); // Seems a bit hacky, considering we have a JSON object as a parameter
+ return this;
+ }
+
+ /**
+ * Set the behavior of the current editing component to display information about an item when the client hovers over the text.
+ *
Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.
+ *
+ * @param itemStack The stack for which to display information.
+ * @return This builder instance.
+ */
+ public FancyMessage itemTooltip(final ItemStack itemStack) {
+ try {
+ Object nmsItem = Reflection.getMethod(Reflection.getOBCClass("inventory.CraftItemStack"), "asNMSCopy", ItemStack.class).invoke(null, itemStack);
+ return itemTooltip(Reflection.getMethod(Reflection.getNMSClass("ItemStack"), "save", Reflection.getNMSClass("NBTTagCompound")).invoke(nmsItem, Reflection.getNMSClass("NBTTagCompound").newInstance()).toString());
+ } catch (Exception e) {
+ e.printStackTrace();
+ return this;
+ }
+ }
+
+ /**
+ * Set the behavior of the current editing component to display raw text when the client hovers over the text.
+ *
Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.
+ *
+ * @param text The text, which supports newlines, which will be displayed to the client upon hovering.
+ * @return This builder instance.
+ */
+ public FancyMessage tooltip(final String text) {
+ onHover("show_text", new JsonString(text));
+ return this;
+ }
+
+ /**
+ * Set the behavior of the current editing component to display raw text when the client hovers over the text.
+ *
Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.
+ *
+ * @param lines The lines of text which will be displayed to the client upon hovering. The iteration order of this object will be the order in which the lines of the tooltip are created.
+ * @return This builder instance.
+ */
+ public FancyMessage tooltip(final Iterable lines) {
+ tooltip(com.boydti.fawe.bukkit.chat.ArrayWrapper.toArray(lines, String.class));
+ return this;
+ }
+
+ /**
+ * Set the behavior of the current editing component to display raw text when the client hovers over the text.
+ *
Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.
+ *
+ * @param lines The lines of text which will be displayed to the client upon hovering.
+ * @return This builder instance.
+ */
+ public FancyMessage tooltip(final String... lines) {
+ StringBuilder builder = new StringBuilder();
+ for (int i = 0; i < lines.length; i++) {
+ builder.append(lines[i]);
+ if (i != lines.length - 1) {
+ builder.append('\n');
+ }
+ }
+ tooltip(builder.toString());
+ return this;
+ }
+
+ /**
+ * Set the behavior of the current editing component to display formatted text when the client hovers over the text.
+ *
Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.
+ *
+ * @param text The formatted text which will be displayed to the client upon hovering.
+ * @return This builder instance.
+ */
+ public FancyMessage formattedTooltip(FancyMessage text) {
+ for (MessagePart component : text.messageParts) {
+ if (component.clickActionData != null && component.clickActionName != null) {
+ throw new IllegalArgumentException("The tooltip text cannot have click data.");
+ } else if (component.hoverActionData != null && component.hoverActionName != null) {
+ throw new IllegalArgumentException("The tooltip text cannot have a tooltip.");
+ }
+ }
+ onHover("show_text", text);
+ return this;
+ }
+
+ /**
+ * Set the behavior of the current editing component to display the specified lines of formatted text when the client hovers over the text.
+ *
Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.
+ *
+ * @param lines The lines of formatted text which will be displayed to the client upon hovering.
+ * @return This builder instance.
+ */
+ public FancyMessage formattedTooltip(FancyMessage... lines) {
+ if (lines.length < 1) {
+ onHover(null, null); // Clear tooltip
+ return this;
+ }
+
+ FancyMessage result = new FancyMessage();
+ result.messageParts.clear(); // Remove the one existing text component that exists by default, which destabilizes the object
+
+ for (int i = 0; i < lines.length; i++) {
+ try {
+ for (MessagePart component : lines[i]) {
+ if (component.clickActionData != null && component.clickActionName != null) {
+ throw new IllegalArgumentException("The tooltip text cannot have click data.");
+ } else if (component.hoverActionData != null && component.hoverActionName != null) {
+ throw new IllegalArgumentException("The tooltip text cannot have a tooltip.");
+ }
+ if (component.hasText()) {
+ result.messageParts.add(component.clone());
+ result.index = result.messageParts.size();
+ }
+ }
+ if (i != lines.length - 1) {
+ result.messageParts.add(new MessagePart(rawText("\n")));
+ result.index = result.messageParts.size();
+ }
+ } catch (CloneNotSupportedException e) {
+ Bukkit.getLogger().log(Level.WARNING, "Failed to clone object", e);
+ return this;
+ }
+ }
+ return formattedTooltip(result.messageParts.isEmpty() ? null : result); // Throws NPE if size is 0, intended
+ }
+
+ /**
+ * Set the behavior of the current editing component to display the specified lines of formatted text when the client hovers over the text.
+ *
Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.
+ *
+ * @param lines The lines of text which will be displayed to the client upon hovering. The iteration order of this object will be the order in which the lines of the tooltip are created.
+ * @return This builder instance.
+ */
+ public FancyMessage formattedTooltip(final Iterable lines) {
+ return formattedTooltip(com.boydti.fawe.bukkit.chat.ArrayWrapper.toArray(lines, FancyMessage.class));
+ }
+
+ /**
+ * If the text is a translatable key, and it has replaceable values, this function can be used to set the replacements that will be used in the message.
+ *
+ * @param replacements The replacements, in order, that will be used in the language-specific message.
+ * @return This builder instance.
+ */
+ public FancyMessage translationReplacements(final String... replacements) {
+ for (String str : replacements) {
+ latest().translationReplacements.add(new JsonString(str));
+ }
+ dirty = true;
+
+ return this;
+ }
+ /*
+
+ /**
+ * If the text is a translatable key, and it has replaceable values, this function can be used to set the replacements that will be used in the message.
+ * @param replacements The replacements, in order, that will be used in the language-specific message.
+ * @return This builder instance.
+ */ /* ------------
+ public FancyMessage translationReplacements(final Iterable extends CharSequence> replacements){
+ for(CharSequence str : replacements){
+ latest().translationReplacements.add(new JsonString(str));
+ }
+
+ return this;
+ }
+
+ */
+
+ /**
+ * If the text is a translatable key, and it has replaceable values, this function can be used to set the replacements that will be used in the message.
+ *
+ * @param replacements The replacements, in order, that will be used in the language-specific message.
+ * @return This builder instance.
+ */
+ public FancyMessage translationReplacements(final FancyMessage... replacements) {
+ for (FancyMessage str : replacements) {
+ latest().translationReplacements.add(str);
+ }
+
+ dirty = true;
+
+ return this;
+ }
+
+ /**
+ * If the text is a translatable key, and it has replaceable values, this function can be used to set the replacements that will be used in the message.
+ *
+ * @param replacements The replacements, in order, that will be used in the language-specific message.
+ * @return This builder instance.
+ */
+ public FancyMessage translationReplacements(final Iterable replacements) {
+ return translationReplacements(com.boydti.fawe.bukkit.chat.ArrayWrapper.toArray(replacements, FancyMessage.class));
+ }
+
+ /**
+ * Terminate construction of the current editing component, and begin construction of a new message component.
+ * After a successful call to this method, all setter methods will refer to a new message component, created as a result of the call to this method.
+ *
+ * @param text The text which will populate the new message component.
+ * @return This builder instance.
+ */
+ public FancyMessage then(final String text) {
+ return then(rawText(text));
+ }
+
+ private FancyMessage append(final String text) {
+ if (!latest().hasText()) {
+ throw new IllegalStateException("previous message part has no text");
+ }
+ MessagePart latest = latest();
+ messageParts.add(new MessagePart(rawText(text)));
+ latest().color = latest.color;
+ latest().styles.addAll(latest.styles);
+ dirty = true;
+ return this;
+ }
+
+ /**
+ * Terminate construction of the current editing component, and begin construction of a new message component.
+ * After a successful call to this method, all setter methods will refer to a new message component, created as a result of the call to this method.
+ *
+ * @param text The text which will populate the new message component.
+ * @return This builder instance.
+ */
+ public FancyMessage then(final TextualComponent text) {
+ if (!latest().hasText()) {
+ throw new IllegalStateException("previous message part has no text");
+ }
+ messageParts.add(new MessagePart(text));
+ index = messageParts.size();
+ dirty = true;
+ return this;
+ }
+
+ /**
+ * Terminate construction of the current editing component, and begin construction of a new message component.
+ * After a successful call to this method, all setter methods will refer to a new message component, created as a result of the call to this method.
+ *
+ * @return This builder instance.
+ */
+ public FancyMessage then() {
+ if (!latest().hasText()) {
+ throw new IllegalStateException("previous message part has no text");
+ }
+ messageParts.add(new MessagePart());
+ index = messageParts.size();
+ dirty = true;
+ return this;
+ }
+
+ @Override
+ public void writeJson(JsonWriter writer) throws IOException {
+ if (messageParts.size() == 1) {
+ latest().writeJson(writer);
+ } else {
+ writer.beginObject().name("text").value("").name("extra").beginArray();
+ for (final MessagePart part : this) {
+ part.writeJson(writer);
+ }
+ writer.endArray().endObject();
+ }
+ }
+
+ /**
+ * Serialize this fancy message, converting it into syntactically-valid JSON using a {@link JsonWriter}.
+ * This JSON should be compatible with vanilla formatter commands such as {@code /tellraw}.
+ *
+ * @return The JSON string representing this object.
+ */
+ public String toJSONString() {
+ if (!dirty && jsonString != null) {
+ return jsonString;
+ }
+ StringWriter string = new StringWriter();
+ JsonWriter json = new JsonWriter(string);
+ try {
+ writeJson(json);
+ json.close();
+ } catch (IOException e) {
+ throw new RuntimeException("invalid message");
+ }
+ jsonString = string.toString();
+ dirty = false;
+ return jsonString;
+ }
+
+ /**
+ * Sends this message to a player. The player will receive the fully-fledged formatted display of this message.
+ *
+ * @param player The player who will receive the message.
+ */
+ public void send(Player player) {
+ send(player, toJSONString());
+ }
+
+ private void send(CommandSender sender, String jsonString) {
+ if (!(sender instanceof Player)) {
+ sender.sendMessage(toOldMessageFormat());
+ return;
+ }
+ Player player = (Player) sender;
+ try {
+ Object handle = Reflection.getHandle(player);
+ Object connection = Reflection.getField(handle.getClass(), "playerConnection").get(handle);
+ Reflection.getMethod(connection.getClass(), "sendPacket", Reflection.getNMSClass("Packet")).invoke(connection, createChatPacket(jsonString));
+ } catch (IllegalArgumentException e) {
+ Bukkit.getLogger().log(Level.WARNING, "Argument could not be passed.", e);
+ } catch (IllegalAccessException e) {
+ Bukkit.getLogger().log(Level.WARNING, "Could not access method.", e);
+ } catch (InstantiationException e) {
+ Bukkit.getLogger().log(Level.WARNING, "Underlying class is abstract.", e);
+ } catch (InvocationTargetException e) {
+ Bukkit.getLogger().log(Level.WARNING, "A error has occurred during invoking of method.", e);
+ } catch (NoSuchMethodException e) {
+ Bukkit.getLogger().log(Level.WARNING, "Could not find method.", e);
+ } catch (ClassNotFoundException e) {
+ Bukkit.getLogger().log(Level.WARNING, "Could not find class.", e);
+ }
+ }
+
+ // The ChatSerializer's instance of Gson
+ private static Object nmsChatSerializerGsonInstance;
+ private static Method fromJsonMethod;
+
+ private Object createChatPacket(String json) throws IllegalArgumentException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchMethodException, ClassNotFoundException {
+ if (nmsChatSerializerGsonInstance == null) {
+ // Find the field and its value, completely bypassing obfuscation
+ Class> chatSerializerClazz;
+
+ // Get the three parts of the version string (major version is currently unused)
+ // vX_Y_RZ
+ // X = major
+ // Y = minor
+ // Z = revision
+ final String version = Reflection.getVersion();
+ String[] split = version.substring(1, version.length() - 1).split("_"); // Remove trailing dot
+ //int majorVersion = Integer.parseInt(split[0]);
+ int minorVersion = Integer.parseInt(split[1]);
+ int revisionVersion = Integer.parseInt(split[2].substring(1)); // Substring to ignore R
+
+ if (minorVersion < 8 || (minorVersion == 8 && revisionVersion == 1)) {
+ chatSerializerClazz = Reflection.getNMSClass("ChatSerializer");
+ } else {
+ chatSerializerClazz = Reflection.getNMSClass("IChatBaseComponent$ChatSerializer");
+ }
+
+ if (chatSerializerClazz == null) {
+ throw new ClassNotFoundException("Can't find the ChatSerializer class");
+ }
+
+ for (Field declaredField : chatSerializerClazz.getDeclaredFields()) {
+ if (Modifier.isFinal(declaredField.getModifiers()) && Modifier.isStatic(declaredField.getModifiers()) && declaredField.getType().getName().endsWith("Gson")) {
+ // We've found our field
+ declaredField.setAccessible(true);
+ nmsChatSerializerGsonInstance = declaredField.get(null);
+ fromJsonMethod = nmsChatSerializerGsonInstance.getClass().getMethod("fromJson", String.class, Class.class);
+ break;
+ }
+ }
+ }
+
+ // Since the method is so simple, and all the obfuscated methods have the same name, it's easier to reimplement 'IChatBaseComponent a(String)' than to reflectively call it
+ // Of course, the implementation may change, but fuzzy matches might break with signature changes
+ Object serializedChatComponent = fromJsonMethod.invoke(nmsChatSerializerGsonInstance, json, Reflection.getNMSClass("IChatBaseComponent"));
+
+ return nmsPacketPlayOutChatConstructor.newInstance(serializedChatComponent);
+ }
+
+ /**
+ * Sends this message to a command sender.
+ * If the sender is a player, they will receive the fully-fledged formatted display of this message.
+ * Otherwise, they will receive a version of this message with less formatting.
+ *
+ * @param sender The command sender who will receive the message.
+ * @see #toOldMessageFormat()
+ */
+ public void send(CommandSender sender) {
+ send(sender, toJSONString());
+ }
+
+ /**
+ * Sends this message to multiple command senders.
+ *
+ * @param senders The command senders who will receive the message.
+ * @see #send(CommandSender)
+ */
+ public void send(final Iterable extends CommandSender> senders) {
+ String string = toJSONString();
+ for (final CommandSender sender : senders) {
+ send(sender, string);
+ }
+ }
+
+ /**
+ * Convert this message to a human-readable string with limited formatting.
+ * This method is used to send this message to clients without JSON formatting support.
+ *
+ * Serialization of this message by using this message will include (in this order for each message part):
+ *
+ *
The color of each message part.
+ *
The applicable stylizations for each message part.
+ *
The core text of the message part.
+ *
+ * The primary omissions are tooltips and clickable actions. Consequently, this method should be used only as a last resort.
+ *
+ *
+ * Color and formatting can be removed from the returned string by using {@link ChatColor#stripColor(String)}.
+ *
+ * @return A human-readable string representing limited formatting in addition to the core text of this message.
+ */
+ public String toOldMessageFormat() {
+ StringBuilder result = new StringBuilder();
+ for (MessagePart part : this) {
+ result.append(part.color == null ? "" : part.color);
+ for (ChatColor formatSpecifier : part.styles) {
+ result.append(formatSpecifier);
+ }
+ result.append(part.text);
+ }
+ return result.toString();
+ }
+
+ private void onCurrent(Consumer call) {
+ for (int i = index - 1; i < messageParts.size(); i++) {
+ call.accept(messageParts.get(i));
+ }
+ }
+
+ private MessagePart latest() {
+ return messageParts.get(messageParts.size() - 1);
+ }
+
+ private void onClick(final String name, final String data) {
+ onCurrent(m -> { m.clickActionName = name; m.clickActionData = data; });
+ dirty = true;
+ }
+
+ private void onHover(final String name, final JsonRepresentedObject data) {
+ onCurrent(m -> { m.hoverActionName = name; m.hoverActionData = data; });
+ dirty = true;
+ }
+
+ // Doc copied from interface
+ public Map serialize() {
+ HashMap map = new HashMap<>();
+ map.put("messageParts", messageParts);
+// map.put("JSON", toJSONString());
+ return map;
+ }
+
+ /**
+ * Deserializes a JSON-represented message from a mapping of key-value pairs.
+ * This is called by the Bukkit serialization API.
+ * It is not intended for direct public API consumption.
+ *
+ * @param serialized The key-value mapping which represents a fancy message.
+ */
+ @SuppressWarnings("unchecked")
+ public static FancyMessage deserialize(Map serialized) {
+ FancyMessage msg = new FancyMessage();
+ msg.messageParts = (List) serialized.get("messageParts");
+ msg.jsonString = serialized.containsKey("JSON") ? serialized.get("JSON").toString() : null;
+ msg.dirty = !serialized.containsKey("JSON");
+ return msg;
+ }
+
+ /**
+ * Internally called method. Not for API consumption.
+ */
+ public Iterator iterator() {
+ return messageParts.iterator();
+ }
+
+ private static JsonParser _stringParser = new JsonParser();
+
+ /**
+ * Deserializes a fancy message from its JSON representation. This JSON representation is of the format of
+ * that returned by {@link #toJSONString()}, and is compatible with vanilla inputs.
+ *
+ * @param json The JSON string which represents a fancy message.
+ * @return A {@code FancyMessage} representing the parameterized JSON message.
+ */
+ public static FancyMessage deserialize(String json) {
+ JsonObject serialized = _stringParser.parse(json).getAsJsonObject();
+ JsonArray extra = serialized.getAsJsonArray("extra"); // Get the extra component
+ FancyMessage returnVal = new FancyMessage();
+ returnVal.messageParts.clear();
+ for (JsonElement mPrt : extra) {
+ MessagePart component = new MessagePart();
+ JsonObject messagePart = mPrt.getAsJsonObject();
+ for (Map.Entry entry : messagePart.entrySet()) {
+ // Deserialize text
+ if (TextualComponent.isTextKey(entry.getKey())) {
+ // The map mimics the YAML serialization, which has a "key" field and one or more "value" fields
+ Map serializedMapForm = new HashMap<>(); // Must be object due to Bukkit serializer API compliance
+ serializedMapForm.put("key", entry.getKey());
+ if (entry.getValue().isJsonPrimitive()) {
+ // Assume string
+ serializedMapForm.put("value", entry.getValue().getAsString());
+ } else {
+ // Composite object, but we assume each element is a string
+ for (Map.Entry compositeNestedElement : entry.getValue().getAsJsonObject().entrySet()) {
+ serializedMapForm.put("value." + compositeNestedElement.getKey(), compositeNestedElement.getValue().getAsString());
+ }
+ }
+ component.text = TextualComponent.deserialize(serializedMapForm);
+ } else if (MessagePart.stylesToNames.inverse().containsKey(entry.getKey())) {
+ if (entry.getValue().getAsBoolean()) {
+ component.styles.add(MessagePart.stylesToNames.inverse().get(entry.getKey()));
+ }
+ } else if (entry.getKey().equals("color")) {
+ component.color = ChatColor.valueOf(entry.getValue().getAsString().toUpperCase());
+ } else if (entry.getKey().equals("clickEvent")) {
+ JsonObject object = entry.getValue().getAsJsonObject();
+ component.clickActionName = object.get("action").getAsString();
+ component.clickActionData = object.get("value").getAsString();
+ } else if (entry.getKey().equals("hoverEvent")) {
+ JsonObject object = entry.getValue().getAsJsonObject();
+ component.hoverActionName = object.get("action").getAsString();
+ if (object.get("value").isJsonPrimitive()) {
+ // Assume string
+ component.hoverActionData = new JsonString(object.get("value").getAsString());
+ } else {
+ // Assume composite type
+ // The only composite type we currently store is another FancyMessage
+ // Therefore, recursion time!
+ component.hoverActionData = deserialize(object.get("value").toString() /* This should properly serialize the JSON object as a JSON string */);
+ }
+ } else if (entry.getKey().equals("insertion")) {
+ component.insertionData = entry.getValue().getAsString();
+ } else if (entry.getKey().equals("with")) {
+ for (JsonElement object : entry.getValue().getAsJsonArray()) {
+ if (object.isJsonPrimitive()) {
+ component.translationReplacements.add(new JsonString(object.getAsString()));
+ } else {
+ // Only composite type stored in this array is - again - FancyMessages
+ // Recurse within this function to parse this as a translation replacement
+ component.translationReplacements.add(deserialize(object.toString()));
+ }
+ }
+ }
+ }
+ returnVal.messageParts.add(component);
+ returnVal.index = returnVal.messageParts.size();
+ }
+ return returnVal;
+ }
+
+}
diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/chat/JsonRepresentedObject.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/chat/JsonRepresentedObject.java
new file mode 100644
index 000000000..0996a1aaf
--- /dev/null
+++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/chat/JsonRepresentedObject.java
@@ -0,0 +1,18 @@
+package com.boydti.fawe.bukkit.chat;
+
+import com.google.gson.stream.JsonWriter;
+import java.io.IOException;
+
+/**
+ * Represents an object that can be serialized to a JSON writer instance.
+ */
+interface JsonRepresentedObject {
+
+ /**
+ * Writes the JSON representation of this object to the specified writer.
+ * @param writer The JSON writer which will receive the object.
+ * @throws IOException If an error occurs writing to the stream.
+ */
+ public void writeJson(JsonWriter writer) throws IOException;
+
+}
diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/chat/JsonString.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/chat/JsonString.java
new file mode 100644
index 000000000..e59fb2e70
--- /dev/null
+++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/chat/JsonString.java
@@ -0,0 +1,46 @@
+package com.boydti.fawe.bukkit.chat;
+
+import com.google.gson.stream.JsonWriter;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import org.bukkit.configuration.serialization.ConfigurationSerializable;
+
+/**
+ * Represents a JSON string value.
+ * Writes by this object will not write name values nor begin/end objects in the JSON stream.
+ * All writes merely write the represented string value.
+ */
+final class JsonString implements JsonRepresentedObject, ConfigurationSerializable {
+
+ private String _value;
+
+ public JsonString(CharSequence value) {
+ _value = value == null ? null : value.toString();
+ }
+
+ @Override
+ public void writeJson(JsonWriter writer) throws IOException {
+ writer.value(getValue());
+ }
+
+ public String getValue() {
+ return _value;
+ }
+
+ public Map serialize() {
+ HashMap theSingleValue = new HashMap();
+ theSingleValue.put("stringValue", _value);
+ return theSingleValue;
+ }
+
+ public static JsonString deserialize(Map map) {
+ return new JsonString(map.get("stringValue").toString());
+ }
+
+ @Override
+ public String toString() {
+ return _value;
+ }
+
+}
diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/chat/MessagePart.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/chat/MessagePart.java
new file mode 100644
index 000000000..805ad0b9d
--- /dev/null
+++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/chat/MessagePart.java
@@ -0,0 +1,156 @@
+package com.boydti.fawe.bukkit.chat;
+
+import com.google.common.collect.BiMap;
+import com.google.common.collect.ImmutableBiMap;
+import com.google.gson.stream.JsonWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Level;
+import org.bukkit.Bukkit;
+import org.bukkit.ChatColor;
+import org.bukkit.configuration.serialization.ConfigurationSerializable;
+import org.bukkit.configuration.serialization.ConfigurationSerialization;
+
+/**
+ * Internal class: Represents a component of a JSON-serializable {@link FancyMessage}.
+ */
+final class MessagePart implements JsonRepresentedObject, ConfigurationSerializable, Cloneable {
+
+ ChatColor color = ChatColor.WHITE;
+ ArrayList styles = new ArrayList<>();
+ String clickActionName = null;
+ String clickActionData = null;
+ String hoverActionName = null;
+ JsonRepresentedObject hoverActionData = null;
+ TextualComponent text = null;
+ String insertionData = null;
+ ArrayList translationReplacements = new ArrayList<>();
+
+ MessagePart(final TextualComponent text) {
+ this.text = text;
+ }
+
+ MessagePart() {
+ this.text = null;
+ }
+
+ boolean hasText() {
+ return text != null;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public MessagePart clone() throws CloneNotSupportedException {
+ MessagePart obj = (MessagePart) super.clone();
+ obj.styles = (ArrayList) styles.clone();
+ if (hoverActionData instanceof JsonString) {
+ obj.hoverActionData = new JsonString(((JsonString) hoverActionData).getValue());
+ } else if (hoverActionData instanceof FancyMessage) {
+ obj.hoverActionData = ((FancyMessage) hoverActionData).clone();
+ }
+ obj.translationReplacements = (ArrayList) translationReplacements.clone();
+ return obj;
+
+ }
+
+ static final BiMap stylesToNames;
+
+ static {
+ ImmutableBiMap.Builder builder = ImmutableBiMap.builder();
+ for (final ChatColor style : ChatColor.values()) {
+ if (!style.isFormat()) {
+ continue;
+ }
+
+ String styleName;
+ switch (style) {
+ case MAGIC:
+ styleName = "obfuscated";
+ break;
+ case UNDERLINE:
+ styleName = "underlined";
+ break;
+ default:
+ styleName = style.name().toLowerCase();
+ break;
+ }
+
+ builder.put(style, styleName);
+ }
+ stylesToNames = builder.build();
+ }
+
+ public void writeJson(JsonWriter json) {
+ try {
+ json.beginObject();
+ text.writeJson(json);
+ json.name("color").value(color.name().toLowerCase());
+ for (final ChatColor style : styles) {
+ json.name(stylesToNames.get(style)).value(true);
+ }
+ if (clickActionName != null && clickActionData != null) {
+ json.name("clickEvent")
+ .beginObject()
+ .name("action").value(clickActionName)
+ .name("value").value(clickActionData)
+ .endObject();
+ }
+ if (hoverActionName != null && hoverActionData != null) {
+ json.name("hoverEvent")
+ .beginObject()
+ .name("action").value(hoverActionName)
+ .name("value");
+ hoverActionData.writeJson(json);
+ json.endObject();
+ }
+ if (insertionData != null) {
+ json.name("insertion").value(insertionData);
+ }
+ if (translationReplacements.size() > 0 && text != null && TextualComponent.isTranslatableText(text)) {
+ json.name("with").beginArray();
+ for (JsonRepresentedObject obj : translationReplacements) {
+ obj.writeJson(json);
+ }
+ json.endArray();
+ }
+ json.endObject();
+ } catch (IOException e) {
+ Bukkit.getLogger().log(Level.WARNING, "A problem occured during writing of JSON string", e);
+ }
+ }
+
+ public Map serialize() {
+ HashMap map = new HashMap<>();
+ map.put("text", text);
+ map.put("styles", styles);
+ map.put("color", color.getChar());
+ map.put("hoverActionName", hoverActionName);
+ map.put("hoverActionData", hoverActionData);
+ map.put("clickActionName", clickActionName);
+ map.put("clickActionData", clickActionData);
+ map.put("insertion", insertionData);
+ map.put("translationReplacements", translationReplacements);
+ return map;
+ }
+
+ @SuppressWarnings("unchecked")
+ public static MessagePart deserialize(Map serialized) {
+ MessagePart part = new MessagePart((TextualComponent) serialized.get("text"));
+ part.styles = (ArrayList) serialized.get("styles");
+ part.color = ChatColor.getByChar(serialized.get("color").toString());
+ part.hoverActionName = (String) serialized.get("hoverActionName");
+ part.hoverActionData = (JsonRepresentedObject) serialized.get("hoverActionData");
+ part.clickActionName = (String) serialized.get("clickActionName");
+ part.clickActionData = (String) serialized.get("clickActionData");
+ part.insertionData = (String) serialized.get("insertion");
+ part.translationReplacements = (ArrayList) serialized.get("translationReplacements");
+ return part;
+ }
+
+ static {
+ ConfigurationSerialization.registerClass(MessagePart.class);
+ }
+
+}
diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/chat/Reflection.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/chat/Reflection.java
new file mode 100644
index 000000000..d0e7b60c7
--- /dev/null
+++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/chat/Reflection.java
@@ -0,0 +1,205 @@
+package com.boydti.fawe.bukkit.chat;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import org.bukkit.Bukkit;
+
+/**
+ * A class containing static utility methods and caches which are intended as reflective conveniences.
+ * Unless otherwise noted, upon failure methods will return {@code null}.
+ */
+public final class Reflection {
+
+ /**
+ * Stores loaded classes from the {@code net.minecraft.server} package.
+ */
+ private static final Map> _loadedNMSClasses = new HashMap<>();
+ /**
+ * Stores loaded classes from the {@code org.bukkit.craftbukkit} package (and subpackages).
+ */
+ private static final Map> _loadedOBCClasses = new HashMap<>();
+ private static final Map, Map> _loadedFields = new HashMap<>();
+ /**
+ * Contains loaded methods in a cache.
+ * The map maps [types to maps of [method names to maps of [parameter types to method instances]]].
+ */
+ private static final Map, Map>, Method>>> _loadedMethods = new HashMap<>();
+ private static String _versionString;
+
+ private Reflection() { }
+
+ /**
+ * Gets the version string from the package name of the CraftBukkit server implementation.
+ * This is needed to bypass the JAR package name changing on each update.
+ *
+ * @return The version string of the OBC and NMS packages, including the trailing dot.
+ */
+ public synchronized static String getVersion() {
+ if (_versionString == null) {
+ if (Bukkit.getServer() == null) {
+ // The server hasn't started, static initializer call?
+ return null;
+ }
+ String name = Bukkit.getServer().getClass().getPackage().getName();
+ _versionString = name.substring(name.lastIndexOf('.') + 1) + ".";
+ }
+
+ return _versionString;
+ }
+
+ /**
+ * Gets a {@link Class} object representing a type contained within the {@code net.minecraft.server} versioned package.
+ * The class instances returned by this method are cached, such that no lookup will be done twice (unless multiple threads are accessing this method simultaneously).
+ *
+ * @param className The name of the class, excluding the package, within NMS.
+ * @return The class instance representing the specified NMS class, or {@code null} if it could not be loaded.
+ */
+ public synchronized static Class> getNMSClass(String className) {
+ if (_loadedNMSClasses.containsKey(className)) {
+ return _loadedNMSClasses.get(className);
+ }
+
+ String fullName = "net.minecraft.server." + getVersion() + className;
+ Class> clazz;
+ try {
+ clazz = Class.forName(fullName);
+ } catch (ClassNotFoundException e) {
+ _loadedNMSClasses.put(className, null);
+ throw new RuntimeException(e);
+ }
+ _loadedNMSClasses.put(className, clazz);
+ return clazz;
+ }
+
+ /**
+ * Gets a {@link Class} object representing a type contained within the {@code org.bukkit.craftbukkit} versioned package.
+ * The class instances returned by this method are cached, such that no lookup will be done twice (unless multiple threads are accessing this method simultaneously).
+ *
+ * @param className The name of the class, excluding the package, within OBC. This name may contain a subpackage name, such as {@code inventory.CraftItemStack}.
+ * @return The class instance representing the specified OBC class, or {@code null} if it could not be loaded.
+ */
+ public synchronized static Class> getOBCClass(String className) {
+ if (_loadedOBCClasses.containsKey(className)) {
+ return _loadedOBCClasses.get(className);
+ }
+
+ String fullName = "org.bukkit.craftbukkit." + getVersion() + className;
+ Class> clazz;
+ try {
+ clazz = Class.forName(fullName);
+ } catch (ClassNotFoundException e) {
+ _loadedOBCClasses.put(className, null);
+ throw new RuntimeException(e);
+ }
+ _loadedOBCClasses.put(className, clazz);
+ return clazz;
+ }
+
+ /**
+ * Attempts to get the NMS handle of a CraftBukkit object.
+ *
+ * The only match currently attempted by this method is a retrieval by using a parameterless {@code getHandle()} method implemented by the runtime type of the specified object.
+ *
+ *
+ * @param obj The object for which to retrieve an NMS handle.
+ * @return The NMS handle of the specified object, or {@code null} if it could not be retrieved using {@code getHandle()}.
+ */
+ public synchronized static Object getHandle(Object obj) throws InvocationTargetException, IllegalAccessException, IllegalArgumentException {
+ return getMethod(obj.getClass(), "getHandle").invoke(obj);
+ }
+
+ /**
+ * Retrieves a {@link Field} instance declared by the specified class with the specified name.
+ * Java access modifiers are ignored during this retrieval. No guarantee is made as to whether the field
+ * returned will be an instance or static field.
+ *
+ * A global caching mechanism within this class is used to store fields. Combined with synchronization, this guarantees that
+ * no field will be reflectively looked up twice.
+ *
+ *
+ * If a field is deemed suitable for return, {@link Field#setAccessible(boolean) setAccessible} will be invoked with an argument of {@code true} before it is returned.
+ * This ensures that callers do not have to check or worry about Java access modifiers when dealing with the returned instance.
+ *
+ *
+ * @param clazz The class which contains the field to retrieve.
+ * @param name The declared name of the field in the class.
+ * @return A field object with the specified name declared by the specified class.
+ * @see Class#getDeclaredField(String)
+ */
+ public synchronized static Field getField(Class> clazz, String name) {
+ Map loaded;
+ if (!_loadedFields.containsKey(clazz)) {
+ loaded = new HashMap<>();
+ _loadedFields.put(clazz, loaded);
+ } else {
+ loaded = _loadedFields.get(clazz);
+ }
+ if (loaded.containsKey(name)) {
+ // If the field is loaded (or cached as not existing), return the relevant value, which might be null
+ return loaded.get(name);
+ }
+ try {
+ Field field = clazz.getDeclaredField(name);
+ field.setAccessible(true);
+ loaded.put(name, field);
+ return field;
+ } catch (NoSuchFieldException | SecurityException e) {
+ // Error loading
+ e.printStackTrace();
+ // Cache field as not existing
+ loaded.put(name, null);
+ return null;
+ }
+ }
+
+ /**
+ * Retrieves a {@link Method} instance declared by the specified class with the specified name and argument types.
+ * Java access modifiers are ignored during this retrieval. No guarantee is made as to whether the field
+ * returned will be an instance or static field.
+ *
+ * A global caching mechanism within this class is used to store method. Combined with synchronization, this guarantees that
+ * no method will be reflectively looked up twice.
+ *
+ * If a method is deemed suitable for return, {@link Method#setAccessible(boolean) setAccessible} will be invoked with an argument of {@code true} before it is returned.
+ * This ensures that callers do not have to check or worry about Java access modifiers when dealing with the returned instance.
+ *
+ * This method does not search superclasses of the specified type for methods with the specified signature.
+ * Callers wishing this behavior should use {@link Class#getDeclaredMethod(String, Class...)}.
+ *
+ * @param clazz The class which contains the method to retrieve.
+ * @param name The declared name of the method in the class.
+ * @param args The formal argument types of the method.
+ * @return A method object with the specified name declared by the specified class.
+ */
+ public synchronized static Method getMethod(Class> clazz, String name, Class>... args) {
+ if (!_loadedMethods.containsKey(clazz)) {
+ _loadedMethods.put(clazz, new HashMap>, Method>>());
+ }
+
+ Map>, Method>> loadedMethodNames = _loadedMethods.get(clazz);
+ if (!loadedMethodNames.containsKey(name)) {
+ loadedMethodNames.put(name, new HashMap>, Method>());
+ }
+
+ Map>, Method> loadedSignatures = loadedMethodNames.get(name);
+ ArrayWrapper> wrappedArg = new ArrayWrapper<>(args);
+ if (loadedSignatures.containsKey(wrappedArg)) {
+ return loadedSignatures.get(wrappedArg);
+ }
+
+ for (Method m : clazz.getMethods()) {
+ if (m.getName().equals(name) && Arrays.equals(args, m.getParameterTypes())) {
+ m.setAccessible(true);
+ loadedSignatures.put(wrappedArg, m);
+ return m;
+ }
+ }
+ loadedSignatures.put(wrappedArg, null);
+ return null;
+ }
+
+}
diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/chat/TextualComponent.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/chat/TextualComponent.java
new file mode 100644
index 000000000..e42b691bc
--- /dev/null
+++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/chat/TextualComponent.java
@@ -0,0 +1,316 @@
+package com.boydti.fawe.bukkit.chat;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
+import com.google.gson.stream.JsonWriter;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import org.bukkit.configuration.serialization.ConfigurationSerializable;
+import org.bukkit.configuration.serialization.ConfigurationSerialization;
+
+/**
+ * Represents a textual component of a message part.
+ * This can be used to not only represent string literals in a JSON message,
+ * but also to represent localized strings and other text values.
+ *
Different instances of this class can be created with static constructor methods.
This is the default type of textual component when a single string
+ * literal is given to a method.
+ *
+ * @param textValue The text which will be represented.
+ * @return The text component representing the specified literal text.
+ */
+ public static TextualComponent rawText(String textValue) {
+ return new ArbitraryTextTypeComponent("text", textValue);
+ }
+
+ /**
+ * Create a textual component representing a localized string.
+ * The client will see this text component as their localized version of the specified string key, which can be overridden by a
+ * resource pack.
+ *
+ * If the specified translation key is not present on the client resource pack, the translation key will be displayed as a string literal to
+ * the client.
+ *
+ *
+ * @param translateKey The string key which maps to localized text.
+ * @return The text component representing the specified localized text.
+ */
+ public static TextualComponent localizedText(String translateKey) {
+ return new ArbitraryTextTypeComponent("translate", translateKey);
+ }
+
+ private static void throwUnsupportedSnapshot() {
+ throw new UnsupportedOperationException("This feature is only supported in snapshot releases.");
+ }
+
+ /**
+ * Create a textual component representing a scoreboard value.
+ * The client will see their own score for the specified objective as the text represented by this component.
+ *
+ * This method is currently guaranteed to throw an {@code UnsupportedOperationException} as it is only supported on snapshot clients.
+ *
+ *
+ * @param scoreboardObjective The name of the objective for which to display the score.
+ * @return The text component representing the specified scoreboard score (for the viewing player), or {@code null} if an error occurs during
+ * JSON serialization.
+ */
+ public static TextualComponent objectiveScore(String scoreboardObjective) {
+ return objectiveScore("*", scoreboardObjective);
+ }
+
+ /**
+ * Create a textual component representing a scoreboard value.
+ * The client will see the score of the specified player for the specified objective as the text represented by this component.
+ *
+ *
This method is currently guaranteed to throw an {@code UnsupportedOperationException}
+ * as it is only supported on snapshot clients.
+ *
+ * @param playerName The name of the player whos score will be shown. If
+ * this string represents the single-character sequence
+ * "*", the viewing player's score will be displayed.
+ * Standard minecraft selectors (@a, @p, etc)
+ * are not supported.
+ * @param scoreboardObjective The name of the objective for
+ * which to display the score.
+ * @return The text component representing the specified scoreboard score
+ * for the specified player, or {@code null} if an error occurs during JSON serialization.
+ */
+ public static TextualComponent objectiveScore(String playerName, String scoreboardObjective) {
+ throwUnsupportedSnapshot(); // Remove this line when the feature is released to non-snapshot versions, in addition to updating ALL THE
+ // OVERLOADS documentation accordingly
+
+ return new ComplexTextTypeComponent("score", ImmutableMap.builder()
+ .put("name", playerName)
+ .put("objective", scoreboardObjective)
+ .build());
+ }
+
+ /**
+ * Create a textual component representing a player name, retrievable by using a standard minecraft selector.
+ * The client will see the players or entities captured by the specified selector as the text represented by this component.
+ *
+ * This method is currently guaranteed to throw an {@code UnsupportedOperationException} as it is only supported on snapshot clients.
+ *
+ *
+ * @param selector The minecraft player or entity selector which will capture the entities whose string representations will be displayed in
+ * the place of this text component.
+ * @return The text component representing the name of the entities captured by the selector.
+ */
+ public static TextualComponent selector(String selector) {
+ throwUnsupportedSnapshot(); // Remove this line when the feature is released to non-snapshot versions, in addition to updating ALL THE
+ // OVERLOADS documentation accordingly
+
+ return new ArbitraryTextTypeComponent("selector", selector);
+ }
+
+ @Override
+ public String toString() {
+ return getReadableString();
+ }
+
+ /**
+ * @return The JSON key used to represent text components of this type.
+ */
+ public abstract String getKey();
+
+ /**
+ * @return A readable String
+ */
+ public abstract String getReadableString();
+
+ /**
+ * Clones a textual component instance.
+ * The returned object should not reference this textual component instance, but should maintain the same key and value.
+ */
+ @Override
+ public abstract TextualComponent clone() throws CloneNotSupportedException;
+
+ /**
+ * Writes the text data represented by this textual component to the specified JSON writer object.
+ * A new object within the writer is not started.
+ *
+ * @param writer The object to which to write the JSON data.
+ * @throws IOException If an error occurs while writing to the stream.
+ */
+ public abstract void writeJson(JsonWriter writer) throws IOException;
+
+ /**
+ * Internal class used to represent all types of text components.
+ * Exception validating done is on keys and values.
+ */
+ private static final class ArbitraryTextTypeComponent extends TextualComponent implements ConfigurationSerializable {
+
+ private String key;
+ private String value;
+
+ public ArbitraryTextTypeComponent(String key, String value) {
+ setKey(key);
+ setValue(value);
+ }
+
+ public static ArbitraryTextTypeComponent deserialize(Map map) {
+ return new ArbitraryTextTypeComponent(map.get("key").toString(), map.get("value").toString());
+ }
+
+ @Override
+ public String getKey() {
+ return key;
+ }
+
+ public void setKey(String key) {
+ Preconditions.checkArgument(key != null && !key.isEmpty(), "The key must be specified.");
+ this.key = key;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ Preconditions.checkArgument(value != null, "The value must be specified.");
+ this.value = value;
+ }
+
+ @Override
+ public TextualComponent clone() throws CloneNotSupportedException {
+ // Since this is a private and final class, we can just reinstantiate this class instead of casting super.clone
+ return new ArbitraryTextTypeComponent(getKey(), getValue());
+ }
+
+ @Override
+ public void writeJson(JsonWriter writer) throws IOException {
+ writer.name(getKey()).value(getValue());
+ }
+
+ @Override
+ @SuppressWarnings("serial")
+ public Map serialize() {
+ return new HashMap() {
+ {
+ put("key", getKey());
+ put("value", getValue());
+ }
+ };
+ }
+
+ @Override
+ public String getReadableString() {
+ return getValue();
+ }
+ }
+
+ /**
+ * Internal class used to represent a text component with a nested JSON
+ * value.
+ *
+ *
Exception validating done is on keys and values.
+ */
+ private static final class ComplexTextTypeComponent extends TextualComponent implements ConfigurationSerializable {
+
+ private String key;
+ private Map value;
+
+ public ComplexTextTypeComponent(String key, Map values) {
+ setKey(key);
+ setValue(values);
+ }
+
+ public static ComplexTextTypeComponent deserialize(Map map) {
+ String key = null;
+ Map value = new HashMap<>();
+ for (Map.Entry valEntry : map.entrySet()) {
+ if (valEntry.getKey().equals("key")) {
+ key = (String) valEntry.getValue();
+ } else if (valEntry.getKey().startsWith("value.")) {
+ value.put(valEntry.getKey().substring(6) /* Strips out the value prefix */, valEntry.getValue().toString());
+ }
+ }
+ return new ComplexTextTypeComponent(key, value);
+ }
+
+ @Override
+ public String getKey() {
+ return key;
+ }
+
+ public void setKey(String key) {
+ Preconditions.checkArgument(key != null && !key.isEmpty(), "The key must be specified.");
+ this.key = key;
+ }
+
+ public Map getValue() {
+ return value;
+ }
+
+ public void setValue(Map value) {
+ Preconditions.checkArgument(value != null, "The value must be specified.");
+ this.value = value;
+ }
+
+ @Override
+ public TextualComponent clone() {
+ // Since this is a private and final class, we can just reinstantiate this class instead of casting super.clone
+ return new ComplexTextTypeComponent(getKey(), getValue());
+ }
+
+ @Override
+ public void writeJson(JsonWriter writer) throws IOException {
+ writer.name(getKey());
+ writer.beginObject();
+ for (Map.Entry jsonPair : value.entrySet()) {
+ writer.name(jsonPair.getKey()).value(jsonPair.getValue());
+ }
+ writer.endObject();
+ }
+
+ @Override
+ @SuppressWarnings("serial")
+ public Map serialize() {
+ return new HashMap() {
+ {
+ put("key", getKey());
+ for (Entry valEntry : getValue().entrySet()) {
+ put("value." + valEntry.getKey(), valEntry.getValue());
+ }
+ }
+ };
+ }
+
+ @Override
+ public String getReadableString() {
+ return getKey();
+ }
+ }
+}
diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/filter/GriefPreventionFilter.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/filter/GriefPreventionFilter.java
new file mode 100644
index 000000000..a67438547
--- /dev/null
+++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/filter/GriefPreventionFilter.java
@@ -0,0 +1,43 @@
+package com.boydti.fawe.bukkit.filter;
+
+import com.boydti.fawe.regions.general.CuboidRegionFilter;
+import com.intellectualcrafters.plot.object.RunnableVal;
+import com.intellectualcrafters.plot.util.TaskManager;
+import com.sk89q.worldedit.BlockVector2D;
+import java.util.ArrayDeque;
+import java.util.Collection;
+import me.ryanhamshire.GriefPrevention.Claim;
+import me.ryanhamshire.GriefPrevention.GriefPrevention;
+import org.bukkit.World;
+
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+public class GriefPreventionFilter extends CuboidRegionFilter {
+ private final Collection claims;
+ private final World world;
+
+ public GriefPreventionFilter(World world) {
+ checkNotNull(world);
+ this.claims = TaskManager.IMP.sync(new RunnableVal>() {
+ @Override
+ public void run(Collection claims) {
+ this.value = new ArrayDeque(GriefPrevention.instance.dataStore.getClaims());
+ }
+ });
+ this.world = world;
+ }
+
+ @Override
+ public void calculateRegions() {
+ for (Claim claim : claims) {
+ org.bukkit.Location bot = claim.getGreaterBoundaryCorner();
+ if (world.equals(bot.getWorld())) {
+ org.bukkit.Location top = claim.getGreaterBoundaryCorner();
+ BlockVector2D pos1 = new BlockVector2D(bot.getBlockX(), bot.getBlockZ());
+ BlockVector2D pos2 = new BlockVector2D(top.getBlockX(), top.getBlockZ());
+ add(pos1, pos2);
+ }
+ }
+ }
+}
diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/filter/WorldGuardFilter.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/filter/WorldGuardFilter.java
new file mode 100644
index 000000000..a26687528
--- /dev/null
+++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/filter/WorldGuardFilter.java
@@ -0,0 +1,66 @@
+package com.boydti.fawe.bukkit.filter;
+
+import com.boydti.fawe.Fawe;
+import com.boydti.fawe.object.RunnableVal;
+import com.boydti.fawe.regions.general.CuboidRegionFilter;
+import com.boydti.fawe.util.TaskManager;
+import com.sk89q.worldedit.BlockVector;
+import com.sk89q.worldguard.bukkit.WorldGuardPlugin;
+import com.sk89q.worldguard.protection.ApplicableRegionSet;
+import com.sk89q.worldguard.protection.managers.RegionManager;
+import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion;
+import com.sk89q.worldguard.protection.regions.ProtectedRegion;
+import org.bukkit.World;
+
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+public class WorldGuardFilter extends CuboidRegionFilter {
+ private final World world;
+ private boolean large;
+ private RegionManager manager;
+
+ public WorldGuardFilter(World world) {
+ checkNotNull(world);
+ this.world = world;
+ }
+ @Override
+ public void calculateRegions() {
+ TaskManager.IMP.sync(new RunnableVal