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 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 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 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 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 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): + *

    + *
  1. The color of each message part.
  2. + *
  3. The applicable stylizations for each message part.
  4. + *
  5. The core text of the message part.
  6. + *
+ * 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.

+ */ +public abstract class TextualComponent implements Cloneable { + + static { + ConfigurationSerialization.registerClass(ArbitraryTextTypeComponent.class); + ConfigurationSerialization.registerClass(ComplexTextTypeComponent.class); + } + + static TextualComponent deserialize(Map map) { + if (map.containsKey("key") && map.size() == 2 && map.containsKey("value")) { + // Arbitrary text component + return ArbitraryTextTypeComponent.deserialize(map); + } else if (map.size() >= 2 && map.containsKey("key") && !map.containsKey("value") /* It contains keys that START WITH value */) { + // Complex JSON object + return ComplexTextTypeComponent.deserialize(map); + } + + return null; + } + + static boolean isTextKey(String key) { + return key.equals("translate") || key.equals("text") || key.equals("score") || key.equals("selector"); + } + + static boolean isTranslatableText(TextualComponent component) { + return component instanceof ComplexTextTypeComponent && component.getKey().equals("translate"); + } + + /** + * Create a textual component representing a string literal. + * + *

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() { + @Override + public void run(Object value) { + WorldGuardFilter.this.manager = WorldGuardPlugin.inst().getRegionManager(world); + for (ProtectedRegion region : manager.getRegions().values()) { + BlockVector min = region.getMinimumPoint(); + BlockVector max = region.getMaximumPoint(); + if (max.getBlockX() - min.getBlockX() > 1024 || max.getBlockZ() - min.getBlockZ() > 1024) { + Fawe.debug("Large or complex region shapes cannot be optimized. Filtering will be slower"); + large = true; + break; + } + add(min.toVector2D(), max.toVector2D()); + } + } + }); + } + + @Override + public boolean containsChunk(int chunkX, int chunkZ) { + if (!large) return super.containsChunk(chunkX, chunkZ); + BlockVector pos1 = new BlockVector(chunkX << 4, 0, chunkZ << 4); + BlockVector pos2 = new BlockVector(pos1.getBlockX() + 15, 255, pos1.getBlockZ() + 15); + ProtectedCuboidRegion chunkRegion = new ProtectedCuboidRegion("unimportant", pos1, pos2); + ApplicableRegionSet set = manager.getApplicableRegions(chunkRegion); + return set.size() > 0 && !set.getRegions().iterator().next().getId().equals("__global__"); + } + + @Override + public boolean containsRegion(int mcaX, int mcaZ) { + if (!large) return super.containsRegion(mcaX, mcaZ); + BlockVector pos1 = new BlockVector(mcaX << 9, 0, mcaZ << 9); + BlockVector pos2 = new BlockVector(pos1.getBlockX() + 511, 255, pos1.getBlockZ() + 511); + ProtectedCuboidRegion regionRegion = new ProtectedCuboidRegion("unimportant", pos1, pos2); + ApplicableRegionSet set = manager.getApplicableRegions(regionRegion); + return set.size() > 0 && !set.getRegions().iterator().next().getId().equals("__global__"); + } +} \ No newline at end of file diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/listener/BrushListener.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/listener/BrushListener.java new file mode 100644 index 000000000..9e41c4620 --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/listener/BrushListener.java @@ -0,0 +1,100 @@ +package com.boydti.fawe.bukkit.listener; + +import com.boydti.fawe.config.Settings; +import com.boydti.fawe.object.FawePlayer; +import com.boydti.fawe.object.brush.MovableTool; +import com.boydti.fawe.object.brush.ResettableTool; +import com.boydti.fawe.object.brush.scroll.ScrollTool; +import com.sk89q.worldedit.LocalSession; +import com.sk89q.worldedit.command.tool.Tool; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.block.Action; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerItemHeldEvent; +import org.bukkit.event.player.PlayerMoveEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; +import org.bukkit.plugin.Plugin; + +public class BrushListener implements Listener { + public BrushListener(Plugin plugin) { + Bukkit.getPluginManager().registerEvents(this, plugin); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onPlayerItemHoldEvent(final PlayerItemHeldEvent event) { + final Player bukkitPlayer = event.getPlayer(); + if (bukkitPlayer.isSneaking()) { + return; + } + FawePlayer fp = FawePlayer.wrap(bukkitPlayer); + com.sk89q.worldedit.entity.Player player = fp.getPlayer(); + LocalSession session = fp.getSession(); + Tool tool = session.getTool(player); + if (tool instanceof ScrollTool) { + final int slot = event.getNewSlot(); + final int oldSlot = event.getPreviousSlot(); + final int ri; + if ((((slot - oldSlot) <= 4) && ((slot - oldSlot) > 0)) || (((slot - oldSlot) < -4))) { + ri = 1; + } else { + ri = -1; + } + ScrollTool scrollable = (ScrollTool) tool; + if (scrollable.increment(player, ri)) { + if (Settings.IMP.EXPERIMENTAL.PERSISTENT_BRUSHES) { + bukkitPlayer.getInventory().setHeldItemSlot(oldSlot); + } else { + final PlayerInventory inv = bukkitPlayer.getInventory(); + final ItemStack item = inv.getItem(slot); + final ItemStack newItem = inv.getItem(oldSlot); + inv.setItem(slot, newItem); + inv.setItem(oldSlot, item); + bukkitPlayer.updateInventory(); + } + } + } + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onPlayerMove(PlayerMoveEvent event) { + Location from = event.getFrom(); + Location to = event.getTo(); + if ((from.getYaw() != to.getYaw() && from.getPitch() != to.getPitch()) || from.getBlockX() != to.getBlockX() || from.getBlockZ() != to.getBlockZ() || from.getBlockY() != to.getBlockY()) { + Player bukkitPlayer = event.getPlayer(); + FawePlayer fp = FawePlayer.wrap(bukkitPlayer); + com.sk89q.worldedit.entity.Player player = fp.getPlayer(); + LocalSession session = fp.getSession(); + Tool tool = session.getTool(player); + if (tool != null) { + if (tool instanceof MovableTool) { + ((MovableTool) tool).move(player); + } + } + } + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onPlayerInteract(final PlayerInteractEvent event) { + Player bukkitPlayer = event.getPlayer(); + if (bukkitPlayer.isSneaking()) { + if (event.getAction() == Action.PHYSICAL) { + return; + } + FawePlayer fp = FawePlayer.wrap(bukkitPlayer); + com.sk89q.worldedit.entity.Player player = fp.getPlayer(); + LocalSession session = fp.getSession(); + Tool tool = session.getTool(player); + if (tool instanceof ResettableTool) { + if (((ResettableTool) tool).reset()) { + event.setCancelled(true); + } + } + } + } +} diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/listener/BukkitImageListener.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/listener/BukkitImageListener.java new file mode 100644 index 000000000..68bb97377 --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/listener/BukkitImageListener.java @@ -0,0 +1,280 @@ +package com.boydti.fawe.bukkit.listener; + +import com.boydti.fawe.bukkit.util.image.BukkitImageViewer; +import com.boydti.fawe.command.CFICommands; +import com.boydti.fawe.jnbt.anvil.HeightMapMCAGenerator; +import com.boydti.fawe.object.FawePlayer; +import com.boydti.fawe.object.brush.BrushSettings; +import com.boydti.fawe.object.extent.FastWorldEditExtent; +import com.boydti.fawe.util.EditSessionBuilder; +import com.boydti.fawe.util.ExtentTraverser; +import com.boydti.fawe.util.TaskManager; +import com.boydti.fawe.util.image.ImageViewer; +import com.sk89q.worldedit.*; +import com.sk89q.worldedit.command.tool.BrushTool; +import com.sk89q.worldedit.command.tool.InvalidToolBindException; +import com.sk89q.worldedit.command.tool.brush.Brush; +import java.util.ArrayDeque; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Rotation; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Entity; +import org.bukkit.entity.ItemFrame; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.block.Action; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.hanging.HangingBreakByEntityEvent; +import org.bukkit.event.player.AsyncPlayerChatEvent; +import org.bukkit.event.player.PlayerEvent; +import org.bukkit.event.player.PlayerInteractEntityEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.plugin.Plugin; + +public class BukkitImageListener implements Listener { + private Location mutable = new Location(Bukkit.getWorlds().get(0), 0, 0, 0); + + public BukkitImageListener(Plugin plugin) { + Bukkit.getPluginManager().registerEvents(this, plugin); + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void onPlayerInteractEntity(AsyncPlayerChatEvent event) { + Set recipients = event.getRecipients(); + Iterator iter = recipients.iterator(); + while (iter.hasNext()) { + Player player = iter.next(); + if (player.equals(event.getPlayer())) continue; + + FawePlayer fp = FawePlayer.wrap(player); + if (!fp.hasMeta()) continue; + + CFICommands.CFISettings settings = fp.getMeta("CFISettings"); + if (settings == null || !settings.hasGenerator()) continue; + + String name = player.getName().toLowerCase(); + if (!event.getMessage().toLowerCase().contains(name)) { + ArrayDeque buffered = fp.getMeta("CFIBufferedMessages"); + if (buffered == null) fp.setMeta("CFIBufferedMessaged", buffered = new ArrayDeque()); + String full = String.format(event.getFormat(), event.getPlayer().getDisplayName(), event.getMessage()); + buffered.add(full); + iter.remove(); + } + } + } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onHangingBreakByEntity(HangingBreakByEntityEvent event) { + if(!(event.getRemover() instanceof Player)) return; + handleInteract(event, (Player) event.getRemover(), event.getEntity(), false); + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onEntityDamageByEntity(EntityDamageByEntityEvent event) { + if(!(event.getDamager() instanceof Player)) return; + handleInteract(event, (Player) event.getDamager(), event.getEntity(), false); + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onPlayerInteract(PlayerInteractEvent event) { + if (event.useItemInHand() == Event.Result.DENY) return; + + Player player = event.getPlayer(); + FawePlayer fp = FawePlayer.wrap(player); + if (fp.getMeta("CFISettings") == null) return; + try { + if (event.getHand() == EquipmentSlot.OFF_HAND) return; + } catch (NoSuchFieldError | NoSuchMethodError ignored) {} + + List target = player.getLastTwoTargetBlocks((Set) null, 100); + if (target.isEmpty()) return; + + Block targetBlock = target.get(0); + World world = player.getWorld(); + mutable.setWorld(world); + mutable.setX(targetBlock.getX() + 0.5); + mutable.setY(targetBlock.getY() + 0.5); + mutable.setZ(targetBlock.getZ() + 0.5); + Collection entities = world.getNearbyEntities(mutable, 0.46875, 0, 0.46875); + + if (!entities.isEmpty()) { + Action action = event.getAction(); + boolean primary = action == Action.RIGHT_CLICK_AIR || action == Action.RIGHT_CLICK_BLOCK; + + double minDist = Integer.MAX_VALUE; + ItemFrame minItemFrame = null; + + for (Entity entity : entities) { + if (entity instanceof ItemFrame) { + ItemFrame itemFrame = (ItemFrame) entity; + Location loc = itemFrame.getLocation(); + double dx = loc.getX() - mutable.getX(); + double dy = loc.getY() - mutable.getY(); + double dz = loc.getZ() - mutable.getZ(); + double dist = dx * dx + dy * dy + dz * dz; + if (dist < minDist) { + minItemFrame = itemFrame; + minDist = dist; + } + } + } + if (minItemFrame != null) { + handleInteract(event, minItemFrame, primary); + if (event.isCancelled()) return; + } + } + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onPlayerInteractEntity(PlayerInteractEntityEvent event) { + handleInteract(event, event.getRightClicked(), true); + } + + private BukkitImageViewer get(HeightMapMCAGenerator generator) { + if (generator == null) return null; + + ImageViewer viewer = generator.getImageViewer(); + if (viewer == null || !(viewer instanceof BukkitImageViewer)) return null; + + BukkitImageViewer biv = (BukkitImageViewer) viewer; + return biv; + } + + private void handleInteract(PlayerEvent event, Entity entity, boolean primary) { + handleInteract(event, event.getPlayer(), entity, primary); + } + + private void handleInteract(Event event, Player player, Entity entity, boolean primary) { + if (!(entity instanceof ItemFrame)) return; + ItemFrame itemFrame = (ItemFrame) entity; + + FawePlayer fp = FawePlayer.wrap(player); + CFICommands.CFISettings settings = fp.getMeta("CFISettings"); + HeightMapMCAGenerator generator = settings == null ? null : settings.getGenerator(); + BukkitImageViewer viewer = get(generator); + if (viewer == null) return; + + if (itemFrame.getRotation() != Rotation.NONE) { + itemFrame.setRotation(Rotation.NONE); + } + + LocalSession session = fp.getSession(); + BrushTool tool; + try { + tool = session.getBrushTool(fp.getPlayer(), false); + } catch (InvalidToolBindException e) { return; } + + ItemFrame[][] frames = viewer.getItemFrames(); + if (frames == null || tool == null) { + viewer.selectFrame(itemFrame); + player.updateInventory(); + TaskManager.IMP.laterAsync(new Runnable() { + @Override + public void run() { + viewer.view(generator); + } + }, 1); + return; + } + + if (tool == null) return; + BrushSettings context = primary ? tool.getPrimary() : tool.getSecondary(); + Brush brush = context.getBrush(); + if (brush == null) return; + tool.setContext(context); + + if (event instanceof Cancellable) { + ((Cancellable) event).setCancelled(true); + } + + Location target = itemFrame.getLocation(); + Location source = player.getLocation(); + + double yawRad = Math.toRadians(source.getYaw() + 90d); + double pitchRad = Math.toRadians(-source.getPitch()); + + double a = Math.cos(pitchRad); + double xRat = Math.cos(yawRad) * a; + double zRat = Math.sin(yawRad) * a; + + BlockFace facing = itemFrame.getFacing(); + double thickness = 1/32d + 1/128d; + double modX = facing.getModX(); + double modZ = facing.getModZ(); + double dx = source.getX() - target.getX() - modX * thickness; + double dy = source.getY() + player.getEyeHeight() - target.getY(); + double dz = source.getZ() - target.getZ() - modZ * thickness; + + double offset; + double localX; + if (modX != 0) { + offset = dx / xRat; + localX = (-modX) * (dz - offset * zRat); + } else { + offset = dz / zRat; + localX = (modZ) * (dx - offset * xRat); + } + double localY = dy - offset * Math.sin(pitchRad); + int localPixelX = (int)((localX + 0.5) * 128); + int localPixelY = (int)((localY + 0.5) * 128); + + UUID uuid = itemFrame.getUniqueId(); + for (int blockX = 0; blockX < frames.length; blockX++) { + for (int blockY = 0; blockY < frames[0].length; blockY++) { + if (uuid.equals(frames[blockX][blockY].getUniqueId())) { + int pixelX = localPixelX + blockX * 128; + int pixelY = (128 * frames[0].length) - (localPixelY + blockY * 128 + 1); + + int width = generator.getWidth(); + int length = generator.getLength(); + int worldX = (int) (pixelX * width / (frames.length * 128d)); + int worldZ = (int) (pixelY * length / (frames[0].length * 128d)); + + if (worldX < 0 || worldX > width || worldZ < 0 || worldZ > length) return; + + Vector wPos = new Vector(worldX, 0, worldZ); + + fp.runAction(new Runnable() { + @Override + public void run() { + viewer.refresh(); + int topY = generator.getNearestSurfaceTerrainBlock(wPos.getBlockX(), wPos.getBlockZ(), 255, 0, 255); + wPos.mutY(topY); + + EditSession es = new EditSessionBuilder(fp.getWorld()).player(fp).combineStages(false).autoQueue(false).blockBag(null).limitUnlimited().build(); + ExtentTraverser last = new ExtentTraverser(es.getExtent()).last(); + if (last.get() instanceof FastWorldEditExtent) last = last.previous(); + last.setNext(generator); + try { + brush.build(es, wPos, context.getMaterial(), context.getSize()); + } catch (WorldEditException e) { + e.printStackTrace(); + } + es.flushQueue(); + viewer.view(generator); + } + }, true, true); + + + + + return; + } + } + } + } +} \ No newline at end of file diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/listener/CFIPacketListener.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/listener/CFIPacketListener.java new file mode 100644 index 000000000..f5e2fe05c --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/listener/CFIPacketListener.java @@ -0,0 +1,336 @@ +package com.boydti.fawe.bukkit.listener; + +import com.boydti.fawe.FaweCache; +import com.boydti.fawe.command.CFICommands; +import com.boydti.fawe.object.FaweChunk; +import com.boydti.fawe.object.FawePlayer; +import com.boydti.fawe.object.FaweQueue; +import com.boydti.fawe.object.RunnableVal3; +import com.boydti.fawe.object.brush.visualization.VirtualWorld; +import com.boydti.fawe.util.SetQueue; +import com.comphenix.protocol.PacketType; +import com.comphenix.protocol.ProtocolLibrary; +import com.comphenix.protocol.ProtocolManager; +import com.comphenix.protocol.events.ListenerPriority; +import com.comphenix.protocol.events.PacketAdapter; +import com.comphenix.protocol.events.PacketContainer; +import com.comphenix.protocol.events.PacketEvent; +import com.comphenix.protocol.injector.netty.WirePacket; +import com.comphenix.protocol.reflect.StructureModifier; +import com.comphenix.protocol.wrappers.BlockPosition; +import com.comphenix.protocol.wrappers.ChunkCoordIntPair; +import com.comphenix.protocol.wrappers.EnumWrappers; +import com.comphenix.protocol.wrappers.WrappedBlockData; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.blocks.BaseBlock; +import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.event.platform.BlockInteractEvent; +import com.sk89q.worldedit.event.platform.Interaction; +import com.sk89q.worldedit.extension.platform.PlatformManager; +import java.lang.reflect.InvocationTargetException; +import java.util.List; + +import com.sk89q.worldedit.world.block.BlockStateHolder; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerTeleportEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; +import org.bukkit.plugin.Plugin; + +/** + * The CFIPacketListener handles packets for editing the VirtualWorld + * The generator is a virtual world which only the creator can see + * - The virtual world is displayed inside the current world + * - Block/Chunk/Movement packets need to be handled properly + */ +public class CFIPacketListener implements Listener { + + private final Plugin plugin; + private final ProtocolManager protocolmanager; + + public CFIPacketListener(Plugin plugin) { + this.plugin = plugin; + this.protocolmanager = ProtocolLibrary.getProtocolManager(); + + // Direct digging to the virtual world + registerBlockEvent(PacketType.Play.Client.BLOCK_DIG, false, new RunnableVal3() { + @Override + public void run(PacketEvent event, VirtualWorld gen, Vector pt) { + try { + Player plr = event.getPlayer(); + Vector realPos = pt.add(gen.getOrigin()); + if (!sendBlockChange(plr, gen, pt, Interaction.HIT)) { + gen.setBlock(pt, EditSession.nullBlock); + } + } catch (WorldEditException e) { + e.printStackTrace(); + } + } + }); + + // Direct placing to the virtual world + RunnableVal3 placeTask = new RunnableVal3() { + @Override + public void run(PacketEvent event, VirtualWorld gen, Vector pt) { + try { + Player plr = event.getPlayer(); + List hands = event.getPacket().getHands().getValues(); + + EnumWrappers.Hand enumHand = hands.isEmpty() ? EnumWrappers.Hand.MAIN_HAND : hands.get(0); + PlayerInventory inv = plr.getInventory(); + ItemStack hand = enumHand == EnumWrappers.Hand.MAIN_HAND ? inv.getItemInMainHand() : inv.getItemInOffHand(); + if (hand != null && hand.getType().isBlock()) { + switch (hand.getType()) { + case AIR: + case CAVE_AIR: + case VOID_AIR: + break; + default: + BlockStateHolder block = BukkitAdapter.asBlockState(hand); + if (block != null) { + gen.setBlock(pt, block); + return; + } + } + } + pt = getRelPos(event, gen); + sendBlockChange(plr, gen, pt, Interaction.OPEN); + } catch (WorldEditException e) { + e.printStackTrace(); + } + } + }; + registerBlockEvent(PacketType.Play.Client.BLOCK_PLACE, true, placeTask); + registerBlockEvent(PacketType.Play.Client.USE_ITEM, true, placeTask); + + // Cancel block change packets where the real world overlaps with the virtual one + registerBlockEvent(PacketType.Play.Server.BLOCK_CHANGE, false, new RunnableVal3() { + @Override + public void run(PacketEvent event, VirtualWorld gen, Vector pt) { + // Do nothing + } + }); + + // Modify chunk packets where the real world overlaps with the virtual one + protocolmanager.addPacketListener(new PacketAdapter(plugin, ListenerPriority.NORMAL, PacketType.Play.Server.MAP_CHUNK) { + @Override + public void onPacketSending(PacketEvent event) { + if (!event.isServerPacket()) return; + + VirtualWorld gen = getGenerator(event); + if (gen != null) { + Vector origin = gen.getOrigin(); + PacketContainer packet = event.getPacket(); + StructureModifier ints = packet.getIntegers(); + int cx = ints.read(0); + int cz = ints.read(1); + + int ocx = origin.getBlockX() >> 4; + int ocz = origin.getBlockZ() >> 4; + + if (gen.contains(new Vector((cx - ocx) << 4, 0, (cz - ocz) << 4))) { + event.setCancelled(true); + + Player plr = event.getPlayer(); + + FaweQueue queue = SetQueue.IMP.getNewQueue(plr.getWorld().getName(), true, false); + + FaweChunk toSend = gen.getSnapshot(cx - ocx, cz - ocz); + toSend.setLoc(gen, cx, cz); + queue.sendChunkUpdate(toSend, FawePlayer.wrap(plr)); + } + } + } + }); + + // The following few listeners are to ignore block collisions where the virtual and real world overlap + + protocolmanager.addPacketListener(new PacketAdapter(plugin, ListenerPriority.NORMAL, PacketType.Play.Server.ENTITY_VELOCITY) { + @Override + public void onPacketSending(PacketEvent event) { + if (!event.isServerPacket()) return; + + Player player = event.getPlayer(); + Location pos = player.getLocation(); + VirtualWorld gen = getGenerator(event); + if (gen != null) { + Vector origin = gen.getOrigin(); + Vector pt = new Vector(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ()); + + StructureModifier ints = event.getPacket().getIntegers(); + int id = ints.read(0); + int mx = ints.read(1); + int my = ints.read(2); + int mz = ints.read(3); + + if (gen.contains(pt.subtract(origin)) && mx == 0 && my == 0 && mz == 0) { + event.setCancelled(true); + } + } + } + }); + + protocolmanager.addPacketListener(new PacketAdapter(plugin, ListenerPriority.NORMAL, PacketType.Play.Server.POSITION) { + @Override + public void onPacketSending(PacketEvent event) { + if (!event.isServerPacket()) return; + + Player player = event.getPlayer(); + Location pos = player.getLocation(); + VirtualWorld gen = getGenerator(event); + if (gen != null) { + Vector origin = gen.getOrigin(); + Vector from = new Vector(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ()); + + PacketContainer packet = event.getPacket(); + StructureModifier doubles = packet.getDoubles(); + Vector to = new Vector(doubles.read(0), doubles.read(1), doubles.read(2)); + if (gen.contains(to.subtract(origin)) && from.distanceSq(to) < 8) { + int id = packet.getIntegers().read(0); + PacketContainer reply = new PacketContainer(PacketType.Play.Client.TELEPORT_ACCEPT); + reply.getIntegers().write(0, id); + try { + protocolmanager.recieveClientPacket(player, reply); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } + event.setCancelled(true); + } + } + } + }); + + protocolmanager.addPacketListener(new PacketAdapter(plugin, ListenerPriority.NORMAL, PacketType.Play.Server.MULTI_BLOCK_CHANGE) { + @Override + public void onPacketSending(PacketEvent event) { + if (!event.isServerPacket()) return; + + VirtualWorld gen = getGenerator(event); + if (gen != null) { + PacketContainer packet = event.getPacket(); + ChunkCoordIntPair chunk = packet.getChunkCoordIntPairs().read(0); + Vector origin = gen.getOrigin(); + int cx = chunk.getChunkX() - (origin.getBlockX() >> 4); + int cz = chunk.getChunkZ() - (origin.getBlockX() >> 4); + if (gen.contains(new Vector(cx << 4, 0, cz << 4))) { + event.setCancelled(true); + } + } + } + }); + } + + @EventHandler + public void onTeleport(PlayerTeleportEvent event) { + final Player player = event.getPlayer(); + VirtualWorld gen = getGenerator(player); + if (gen != null) { + Location from = event.getFrom(); + Location to = event.getTo(); + if (to.getWorld().equals(from.getWorld()) && to.distanceSquared(from) < 8) { + event.setTo(player.getLocation()); + event.setCancelled(true); + player.setVelocity(player.getVelocity()); + } + } + } + + private boolean sendBlockChange(Player plr, VirtualWorld gen, Vector pt, Interaction action) { + PlatformManager platform = WorldEdit.getInstance().getPlatformManager(); + com.sk89q.worldedit.entity.Player actor = FawePlayer.wrap(plr).getPlayer(); + com.sk89q.worldedit.util.Location location = new com.sk89q.worldedit.util.Location(actor.getWorld(), pt); + BlockInteractEvent toCall = new BlockInteractEvent(actor, location, action); + platform.handleBlockInteract(toCall); + if (toCall.isCancelled() || action == Interaction.OPEN) { + Vector realPos = pt.add(gen.getOrigin()); + BlockStateHolder block = gen.getBlock(pt); + sendBlockChange(plr, realPos, block); + return true; + } + return false; + } + + private void sendBlockChange(Player plr, Vector pt, BlockStateHolder block) { + plr.sendBlockChange(new Location(plr.getWorld(), pt.getBlockX(), pt.getBlockY(), pt.getBlockZ()), BukkitAdapter.adapt(block)); + } + + private VirtualWorld getGenerator(PacketEvent event) { + return getGenerator(event.getPlayer()); + } + + private VirtualWorld getGenerator(Player player) { + FawePlayer fp = FawePlayer.wrap(player); + VirtualWorld vw = fp.getSession().getVirtualWorld(); + if (vw != null) return vw; + CFICommands.CFISettings settings = fp.getMeta("CFISettings"); + if (settings != null && settings.hasGenerator() && settings.getGenerator().hasPacketViewer()) { + return settings.getGenerator(); + } + return null; + } + + private Vector getRelPos(PacketEvent event, VirtualWorld generator) { + PacketContainer packet = event.getPacket(); + StructureModifier position = packet.getBlockPositionModifier(); + BlockPosition loc = position.readSafely(0); + if (loc == null) return null; + Vector origin = generator.getOrigin(); + Vector pt = new Vector(loc.getX() - origin.getBlockX(), loc.getY() - origin.getBlockY(), loc.getZ() - origin.getBlockZ()); + return pt; + } + + private void handleBlockEvent(PacketEvent event, boolean relative, RunnableVal3 task) { + VirtualWorld gen = getGenerator(event); + if (gen != null) { + Vector pt = getRelPos(event, gen); + if (pt != null) { + if (relative) pt = getRelative(event, pt); + if (gen.contains(pt)) { + event.setCancelled(true); + task.run(event, gen, pt); + } + } + } + } + + private void registerBlockEvent(PacketType type, boolean relative, RunnableVal3 task) { + protocolmanager.addPacketListener(new PacketAdapter(plugin, ListenerPriority.NORMAL, type) { + @Override + public void onPacketReceiving(final PacketEvent event) { + if (type.isClient() || event.isServerPacket()) handleBlockEvent(event, relative, task); + } + + @Override + public void onPacketSending(PacketEvent event) { + onPacketReceiving(event); + } + }); + } + + private Vector getRelative(PacketEvent container, Vector pt) { + PacketContainer packet = container.getPacket(); + StructureModifier dirs = packet.getDirections(); + EnumWrappers.Direction dir = dirs.readSafely(0); + if (dir == null) return pt; + switch (dir.ordinal()) { + case 0: return pt.add(0, -1, 0); + case 1: return pt.add(0, 1, 0); + case 2: return pt.add(0, 0, -1); + case 3: return pt.add(0, 0, 1); + case 4: return pt.add(-1, 0, 0); + case 5: return pt.add(1, 0, 0); + default: return pt; + } + } +} diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/listener/RenderListener.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/listener/RenderListener.java new file mode 100644 index 000000000..8824f369c --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/listener/RenderListener.java @@ -0,0 +1,136 @@ +package com.boydti.fawe.bukkit.listener; + +import com.boydti.fawe.Fawe; +import com.boydti.fawe.config.Settings; +import com.boydti.fawe.object.FawePlayer; +import com.boydti.fawe.util.TaskManager; +import java.util.Iterator; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import org.bukkit.Bukkit; +import org.bukkit.Location; +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.PlayerMoveEvent; +import org.bukkit.event.player.PlayerTeleportEvent; +import org.bukkit.plugin.Plugin; + +public class RenderListener implements Listener { + + private final Map views = new ConcurrentHashMap<>(); + private Iterator> entrySet; + private int OFFSET = 6; + + public RenderListener(Plugin plugin) { + Bukkit.getPluginManager().registerEvents(this, plugin); + TaskManager.IMP.repeat(new Runnable() { + private long last = 0; + + @Override + public void run() { + if (views.isEmpty()) return; + + long now = System.currentTimeMillis(); + int tps32 = (int) (Math.round(Fawe.get().getTimer().getTPS()) * 32); + long diff = now - last; + last = now; + if (diff > 75) { + OFFSET = diff > 100 ? 0 : 4; + return; + } + int timeOut; + if (diff < 55 && tps32 > 608) { + OFFSET = 8; + timeOut = 2; + } else { + int tpsSqr = tps32 * tps32; + OFFSET = 1 + (tps32 / 102400); + timeOut = 162 - (tps32 / 2560); + } + if (entrySet == null || !entrySet.hasNext()) { + entrySet = views.entrySet().iterator(); + } + int nowTick = (int) (Fawe.get().getTimer().getTick()); + while (entrySet.hasNext()) { + Map.Entry entry = entrySet.next(); + Player player = Bukkit.getPlayer(entry.getKey()); + if (player != null) { + int[] value = entry.getValue(); + if (nowTick - value[1] >= timeOut) { + value[1] = nowTick + 1; + setViewDistance(player, Math.max(4, value[0] + 1)); + long spent = System.currentTimeMillis() - now; + if (spent > 5) { + if (spent > 10) + value[1] = nowTick + 20; + return; + } + } + } + } + } + }, 1); + } + + public void setViewDistance(Player player, int value) { + UUID uuid = player.getUniqueId(); + if (value == Settings.IMP.EXPERIMENTAL.DYNAMIC_CHUNK_RENDERING) { + views.remove(uuid); + } else { + int[] val = views.get(uuid); + if (val == null) { + val = new int[] {value, (int) Fawe.get().getTimer().getTick()}; + UUID uid = player.getUniqueId(); + views.put(uid, val); + } else { + if (value <= val[0]) { + val[1] = (int) Fawe.get().getTimer().getTick(); + } + if (val[0] == value) { + return; + } else { + val[0] = value; + } + } + } + player.setViewDistance(value); + } + + public int getViewDistance(Player player) { + int[] value = views.get(player.getUniqueId()); + return value == null ? Settings.IMP.EXPERIMENTAL.DYNAMIC_CHUNK_RENDERING : value[0]; + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onPlayerTeleport(PlayerTeleportEvent event) { + setViewDistance(event.getPlayer(), 1); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onPlayerMove(PlayerMoveEvent event) { + Location from = event.getFrom(); + Location to = event.getTo(); + if (from.getBlockX() >> OFFSET != to.getBlockX() >> OFFSET || from.getBlockZ() >> OFFSET != to.getBlockZ() >> OFFSET) { + Player player = event.getPlayer(); + int currentView = getViewDistance(player); + setViewDistance(player, Math.max(currentView - 1, 1)); + } + } + + @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) + public void onPlayerJoin(org.bukkit.event.player.PlayerJoinEvent event) { + Player player = event.getPlayer(); + setViewDistance(player, 1); + FawePlayer fp = FawePlayer.wrap(player); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onPlayerLeave(org.bukkit.event.player.PlayerQuitEvent event) { + Player player = event.getPlayer(); + UUID uid = player.getUniqueId(); + views.remove(uid); + } +} \ No newline at end of file diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/regions/ASkyBlockHook.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/regions/ASkyBlockHook.java new file mode 100644 index 000000000..c875f6812 --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/regions/ASkyBlockHook.java @@ -0,0 +1,55 @@ +package com.boydti.fawe.bukkit.regions; + +import com.boydti.fawe.bukkit.FaweBukkit; +import com.boydti.fawe.object.FawePlayer; +import com.boydti.fawe.regions.FaweMask; +import com.wasteofplastic.askyblock.ASkyBlockAPI; +import com.wasteofplastic.askyblock.Island; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.bukkit.event.Listener; +import org.bukkit.plugin.Plugin; + +public class ASkyBlockHook extends BukkitMaskManager implements Listener { + FaweBukkit plugin; + Plugin aSkyBlock; + + public ASkyBlockHook(final Plugin aSkyBlock, final FaweBukkit p3) { + super(aSkyBlock.getName()); + this.aSkyBlock = aSkyBlock; + this.plugin = p3; + + } + + public boolean isAllowed(Player player, Island island, MaskType type) { + return island != null && (player.getUniqueId().equals(island.getOwner()) || (type == MaskType.MEMBER && island.getMembers().contains(player.getUniqueId()) && hasMemberPermission(player))); + } + + @Override + public FaweMask getMask(final FawePlayer fp, MaskType type) { + final Player player = fp.parent; + final Location location = player.getLocation(); + + Island island = ASkyBlockAPI.getInstance().getIslandAt(location); + if (island != null && isAllowed(player, island, type)) { + int minX = island.getMinProtectedX(); + int minZ = island.getMinProtectedZ(); + + World world = location.getWorld(); + Location center = island.getCenter(); + Location pos1 = new Location(world, island.getMinProtectedX(), 0, island.getMinProtectedZ()); + Location pos2 = center.add(center.subtract(pos1)); + pos2.setY(255); + + return new BukkitMask(pos1, pos2, "ISLAND: " + minX + "," + minZ) { + @Override + public boolean isValid(FawePlayer player, MaskType type) { + return isAllowed((Player) player.parent, island, type); + } + }; + } + + return null; + } +} \ No newline at end of file diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/regions/BukkitMask.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/regions/BukkitMask.java new file mode 100644 index 000000000..701cd3e30 --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/regions/BukkitMask.java @@ -0,0 +1,16 @@ +package com.boydti.fawe.bukkit.regions; + +import com.boydti.fawe.regions.FaweMask; +import com.sk89q.worldedit.BlockVector; +import org.bukkit.Location; + +public class BukkitMask extends FaweMask { + + public BukkitMask(Location pos1, Location pos2) { + this(pos1, pos2, null); + } + + public BukkitMask(Location pos1, Location pos2, String name) { + super(new BlockVector(pos1.getBlockX(), pos1.getBlockY(), pos1.getBlockZ()), new BlockVector(pos2.getBlockX(), pos2.getBlockY(), pos2.getBlockZ()), name); + } +} \ No newline at end of file diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/regions/BukkitMaskManager.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/regions/BukkitMaskManager.java new file mode 100644 index 000000000..abd616603 --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/regions/BukkitMaskManager.java @@ -0,0 +1,15 @@ +package com.boydti.fawe.bukkit.regions; + +import com.boydti.fawe.regions.FaweMaskManager; +import org.bukkit.entity.Player; + +public abstract class BukkitMaskManager extends FaweMaskManager { + + public BukkitMaskManager(final String plugin) { + super(plugin); + } + + public boolean hasMemberPermission(Player player) { + return player.hasPermission("fawe." + getKey() + ".member"); + } +} diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/regions/FactionsFeature.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/regions/FactionsFeature.java new file mode 100644 index 000000000..f460862f7 --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/regions/FactionsFeature.java @@ -0,0 +1,63 @@ +package com.boydti.fawe.bukkit.regions; + +import com.boydti.fawe.bukkit.FaweBukkit; +import com.boydti.fawe.object.FawePlayer; +import com.massivecraft.factions.entity.BoardColl; +import com.massivecraft.factions.entity.Faction; +import com.massivecraft.factions.entity.MPlayer; +import com.massivecraft.massivecore.ps.PS; +import org.bukkit.Chunk; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.event.Listener; +import org.bukkit.plugin.Plugin; + +public class FactionsFeature extends BukkitMaskManager implements Listener { + FaweBukkit plugin; + Plugin factions; + + public FactionsFeature(final Plugin factionsPlugin, final FaweBukkit p3) { + super(factionsPlugin.getName()); + this.factions = factionsPlugin; + this.plugin = p3; + BoardColl.get(); + } + + @Override + public BukkitMask getMask(final FawePlayer fp, MaskType type) { + final Player player = fp.parent; + final Location loc = player.getLocation(); + final PS ps = PS.valueOf(loc); + final Faction fac = BoardColl.get().getFactionAt(ps); + if (fac != null) { + if (type == MaskType.OWNER) { + MPlayer leader = fac.getLeader(); + if (leader != null && fp.getUUID().equals(leader.getUuid())) { + final Chunk chunk = loc.getChunk(); + final Location pos1 = new Location(loc.getWorld(), chunk.getX() * 16, 0, chunk.getZ() * 16); + final Location pos2 = new Location(loc.getWorld(), (chunk.getX() * 16) + 15, 156, (chunk.getZ() * 16) + 15); + return new BukkitMask(pos1, pos2) { + @Override + public String getName() { + return "CHUNK:" + loc.getChunk().getX() + "," + loc.getChunk().getZ(); + } + }; + } + } + else if (fac.getOnlinePlayers().contains(player)) { + if (fac.getComparisonName().equals("wilderness") == false) { + final Chunk chunk = loc.getChunk(); + final Location pos1 = new Location(loc.getWorld(), chunk.getX() * 16, 0, chunk.getZ() * 16); + final Location pos2 = new Location(loc.getWorld(), (chunk.getX() * 16) + 15, 156, (chunk.getZ() * 16) + 15); + return new BukkitMask(pos1, pos2) { + @Override + public String getName() { + return "CHUNK:" + loc.getChunk().getX() + "," + loc.getChunk().getZ(); + } + }; + } + } + } + return null; + } +} diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/regions/FactionsOneFeature.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/regions/FactionsOneFeature.java new file mode 100644 index 000000000..4eab92792 --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/regions/FactionsOneFeature.java @@ -0,0 +1,119 @@ +package com.boydti.fawe.bukkit.regions; + +import com.boydti.fawe.bukkit.FaweBukkit; +import com.boydti.fawe.object.FawePlayer; +import com.boydti.fawe.object.RegionWrapper; +import com.boydti.fawe.util.MainUtil; +import com.boydti.fawe.util.Perm; +import com.massivecraft.factions.FLocation; +import java.lang.reflect.Method; +import java.util.List; +import org.bukkit.Chunk; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.bukkit.event.Listener; +import org.bukkit.plugin.Plugin; + +public class FactionsOneFeature extends BukkitMaskManager implements Listener { + + private final Class clazzBoard; + private final Method methodGetFactionAt; + + public FactionsOneFeature(final Plugin factionsPlugin, final FaweBukkit p3) throws Throwable { + super(factionsPlugin.getName()); + this.clazzBoard = Class.forName("com.massivecraft.factions.Board"); + this.methodGetFactionAt = clazzBoard.getDeclaredMethod("getFactionAt", FLocation.class); + } + + @Override + public BukkitMask getMask(final FawePlayer fp, MaskType type) { + final Player player = fp.parent; + final Chunk chunk = player.getLocation().getChunk(); + final boolean perm = Perm.hasPermission(FawePlayer.wrap(player), "fawe.factions.wilderness"); + final World world = player.getWorld(); + + RegionWrapper locs = new RegionWrapper(chunk.getX(), chunk.getX(), chunk.getZ(), chunk.getZ()); + + int count = 32; + + if (this.isAdded(locs, world, player, perm, type)) { + boolean hasPerm = true; + + RegionWrapper chunkSelection; + while (hasPerm && (count > 0)) { + count--; + + hasPerm = false; + + chunkSelection = new RegionWrapper(locs.maxX + 1, locs.maxX + 1, locs.minZ, locs.maxZ); + + if (this.isAdded(chunkSelection, world, player, perm, type)) { + locs = new RegionWrapper(locs.minX, locs.maxX + 1, locs.minZ, locs.maxZ); + hasPerm = true; + } + + chunkSelection = new RegionWrapper(locs.minX - 1, locs.minX - 1, locs.minZ, locs.maxZ); + + if (this.isAdded(chunkSelection, world, player, perm, type)) { + locs = new RegionWrapper(locs.minX - 1, locs.maxX, locs.minZ, locs.maxZ); + hasPerm = true; + } + + chunkSelection = new RegionWrapper(locs.minX, locs.maxX, locs.maxZ + 1, locs.maxZ + 1); + + if (this.isAdded(chunkSelection, world, player, perm, type)) { + locs = new RegionWrapper(locs.minX, locs.maxX, locs.minZ, locs.maxZ + 1); + hasPerm = true; + } + + chunkSelection = new RegionWrapper(locs.minX, locs.maxX, locs.minZ - 1, locs.minZ - 1); + + if (this.isAdded(chunkSelection, world, player, perm, type)) { + locs = new RegionWrapper(locs.minX, locs.maxX, locs.minZ - 1, locs.maxZ); + hasPerm = true; + } + } + + final Location pos1 = new Location(world, locs.minX << 4, 1, locs.minZ << 4); + final Location pos2 = new Location(world, 15 + (locs.maxX << 4), 256, 15 + (locs.maxZ << 4)); + return new BukkitMask(pos1, pos2) { + @Override + public String getName() { + return "CHUNK:" + pos1.getChunk().getX() + "," + pos1.getChunk().getZ(); + } + }; + } + return null; + } + + public boolean isAdded(final RegionWrapper locs, final World world, final Player player, final boolean perm, MaskType type) { + try { + for (int x = locs.minX; x <= locs.maxX; x++) { + for (int z = locs.minZ; z <= locs.maxZ; z++) { + final Object fac = methodGetFactionAt.invoke(null, new FLocation(world.getName(), x, z)); + if (fac == null) { + return false; + } + if (type == MaskType.OWNER) { + Object leader = fac.getClass().getDeclaredMethod("getFPlayerLeader").invoke(fac); + return player.getName().equals(leader.getClass().getDeclaredMethod("getName").invoke(leader)); + } + Method methodGetOnlinePlayers = fac.getClass().getDeclaredMethod("getOnlinePlayers"); + List players = (List) methodGetOnlinePlayers.invoke(fac); + if (!players.contains(player)) { + return false; + } + Method isNone = fac.getClass().getDeclaredMethod("isNone"); + if ((boolean) isNone.invoke(fac)) { + return false; + } + } + } + return true; + } catch (Throwable e) { + MainUtil.handleError(e); + return false; + } + } +} diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/regions/FactionsUUIDFeature.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/regions/FactionsUUIDFeature.java new file mode 100644 index 000000000..29451faa5 --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/regions/FactionsUUIDFeature.java @@ -0,0 +1,104 @@ +package com.boydti.fawe.bukkit.regions; + +import com.boydti.fawe.bukkit.FaweBukkit; +import com.boydti.fawe.object.FawePlayer; +import com.boydti.fawe.object.RegionWrapper; +import com.boydti.fawe.util.Perm; +import com.massivecraft.factions.Board; +import com.massivecraft.factions.FLocation; +import com.massivecraft.factions.Faction; +import org.bukkit.Chunk; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.bukkit.event.Listener; +import org.bukkit.plugin.Plugin; + +public class FactionsUUIDFeature extends BukkitMaskManager implements Listener { + private final Board instance; + + public FactionsUUIDFeature(final Plugin factionsPlugin, final FaweBukkit p3) { + super(factionsPlugin.getName()); + this.instance = Board.getInstance(); + } + + @Override + public BukkitMask getMask(final FawePlayer fp, MaskType type) { + final Player player = fp.parent; + final Chunk chunk = player.getLocation().getChunk(); + final boolean perm = Perm.hasPermission(FawePlayer.wrap(player), "fawe.factions.wilderness"); + final World world = player.getWorld(); + + RegionWrapper locs = new RegionWrapper(chunk.getX(), chunk.getX(), chunk.getZ(), chunk.getZ()); + + int count = 32; + + if (this.isAdded(locs, world, player, perm, type)) { + boolean hasPerm = true; + + RegionWrapper chunkSelection; + while (hasPerm && (count > 0)) { + count--; + + hasPerm = false; + + chunkSelection = new RegionWrapper(locs.maxX + 1, locs.maxX + 1, locs.minZ, locs.maxZ); + + if (this.isAdded(chunkSelection, world, player, perm, type)) { + locs = new RegionWrapper(locs.minX, locs.maxX + 1, locs.minZ, locs.maxZ); + hasPerm = true; + } + + chunkSelection = new RegionWrapper(locs.minX - 1, locs.minX - 1, locs.minZ, locs.maxZ); + + if (this.isAdded(chunkSelection, world, player, perm, type)) { + locs = new RegionWrapper(locs.minX - 1, locs.maxX, locs.minZ, locs.maxZ); + hasPerm = true; + } + + chunkSelection = new RegionWrapper(locs.minX, locs.maxX, locs.maxZ + 1, locs.maxZ + 1); + + if (this.isAdded(chunkSelection, world, player, perm, type)) { + locs = new RegionWrapper(locs.minX, locs.maxX, locs.minZ, locs.maxZ + 1); + hasPerm = true; + } + + chunkSelection = new RegionWrapper(locs.minX, locs.maxX, locs.minZ - 1, locs.minZ - 1); + + if (this.isAdded(chunkSelection, world, player, perm, type)) { + locs = new RegionWrapper(locs.minX, locs.maxX, locs.minZ - 1, locs.maxZ); + hasPerm = true; + } + } + + final Location pos1 = new Location(world, locs.minX << 4, 1, locs.minZ << 4); + final Location pos2 = new Location(world, 15 + (locs.maxX << 4), 256, 15 + (locs.maxZ << 4)); + return new BukkitMask(pos1, pos2) { + @Override + public String getName() { + return "CHUNK:" + pos1.getChunk().getX() + "," + pos1.getChunk().getZ(); + } + }; + } + return null; + } + + public boolean isAdded(final RegionWrapper locs, final World world, final Player player, final boolean perm, MaskType type) { + for (int x = locs.minX; x <= locs.maxX; x++) { + for (int z = locs.minZ; z <= locs.maxZ; z++) { + final Faction fac = this.instance.getFactionAt(new FLocation(world.getName(), x, z)); + if (fac == null) { + return false; + } + // TODO types + if (!fac.getOnlinePlayers().contains(player)) { + return false; + } + if (fac.isWilderness() && !perm) { + return false; + } + } + } + return true; + } +} diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/regions/GriefPreventionFeature.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/regions/GriefPreventionFeature.java new file mode 100644 index 000000000..8832ec90b --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/regions/GriefPreventionFeature.java @@ -0,0 +1,61 @@ +package com.boydti.fawe.bukkit.regions; + +import com.boydti.fawe.bukkit.FaweBukkit; +import com.boydti.fawe.bukkit.filter.GriefPreventionFilter; +import com.boydti.fawe.object.FawePlayer; +import com.boydti.fawe.regions.general.RegionFilter; +import me.ryanhamshire.GriefPrevention.Claim; +import me.ryanhamshire.GriefPrevention.GriefPrevention; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.Listener; +import org.bukkit.plugin.Plugin; + +public class GriefPreventionFeature extends BukkitMaskManager implements Listener { + FaweBukkit plugin; + Plugin griefprevention; + + public GriefPreventionFeature(final Plugin griefpreventionPlugin, final FaweBukkit p3) { + super(griefpreventionPlugin.getName()); + this.griefprevention = griefpreventionPlugin; + this.plugin = p3; + } + + public boolean isAllowed(Player player, Claim claim, MaskType type) { + return claim != null && (claim.getOwnerName().equalsIgnoreCase(player.getName()) || claim.getOwnerName().equals(player.getUniqueId()) || (type == MaskType.MEMBER && (claim.allowBuild(player, Material.AIR) == null))); + } + + @Override + public BukkitMask getMask(final FawePlayer fp, MaskType type) { + final Player player = fp.parent; + final Location location = player.getLocation(); + final Claim claim = GriefPrevention.instance.dataStore.getClaimAt(location, true, null); + if (claim != null) { + final String uuid = player.getUniqueId().toString(); + if (isAllowed(player, claim, type)) { + claim.getGreaterBoundaryCorner().getBlockX(); + final Location pos1 = new Location(location.getWorld(), claim.getLesserBoundaryCorner().getBlockX(), 0, claim.getLesserBoundaryCorner().getBlockZ()); + final Location pos2 = new Location(location.getWorld(), claim.getGreaterBoundaryCorner().getBlockX(), 256, claim.getGreaterBoundaryCorner().getBlockZ()); + return new BukkitMask(pos1, pos2) { + @Override + public String getName() { + return "CLAIM:" + claim.toString(); + } + + @Override + public boolean isValid(FawePlayer player, MaskType type) { + return isAllowed((Player) player.parent, claim, type); + } + }; + } + } + return null; + } + + @Override + public RegionFilter getFilter(String world) { + return new GriefPreventionFilter(Bukkit.getWorld(world)); + } +} diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/regions/PlotMeFeature.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/regions/PlotMeFeature.java new file mode 100644 index 000000000..ee57961a9 --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/regions/PlotMeFeature.java @@ -0,0 +1,56 @@ +package com.boydti.fawe.bukkit.regions; + +import com.boydti.fawe.bukkit.FaweBukkit; +import com.boydti.fawe.object.FawePlayer; +import com.worldcretornica.plotme_core.Plot; +import com.worldcretornica.plotme_core.PlotMe_Core; +import com.worldcretornica.plotme_core.bukkit.PlotMe_CorePlugin; +import com.worldcretornica.plotme_core.bukkit.api.BukkitPlayer; +import com.worldcretornica.plotme_core.bukkit.api.BukkitWorld; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.event.Listener; +import org.bukkit.plugin.Plugin; + +public class PlotMeFeature extends BukkitMaskManager implements Listener { + FaweBukkit plugin; + PlotMe_Core plotme; + + public PlotMeFeature(final Plugin plotmePlugin, final FaweBukkit p3) { + super(plotmePlugin.getName()); + this.plotme = ((PlotMe_CorePlugin) plotmePlugin).getAPI(); + this.plugin = p3; + + } + public boolean isAllowed(Player player, Plot plot, MaskType type) { + return plot != null && type == MaskType.MEMBER ? plot.isAllowed(player.getUniqueId()) : player.getUniqueId().equals(plot.getOwnerId()); + } + + @Override + public BukkitMask getMask(final FawePlayer fp, MaskType type) { + final Player player = fp.parent; + final Location location = player.getLocation(); + final Plot plot = this.plotme.getPlotMeCoreManager().getPlotById(new BukkitPlayer(player)); + if (plot == null) { + return null; + } + if (isAllowed(player, plot, type)) { + final Location pos1 = new Location(location.getWorld(), this.plotme.getGenManager(player.getWorld().getName()).bottomX(plot.getId(), new BukkitWorld(player.getWorld())), 0, this.plotme + .getGenManager(player.getWorld().getName()).bottomZ(plot.getId(), new BukkitWorld(player.getWorld()))); + final Location pos2 = new Location(location.getWorld(), this.plotme.getGenManager(player.getWorld().getName()).topX(plot.getId(), new BukkitWorld(player.getWorld())), 256, this.plotme + .getGenManager(player.getWorld().getName()).topZ(plot.getId(), new BukkitWorld(player.getWorld()))); + return new BukkitMask(pos1, pos2) { + @Override + public String getName() { + return plot.getId(); + } + + @Override + public boolean isValid(FawePlayer player, MaskType type) { + return isAllowed((Player) player.parent, plot, type); + } + }; + } + return null; + } +} diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/regions/PreciousStonesFeature.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/regions/PreciousStonesFeature.java new file mode 100644 index 000000000..dccb3952a --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/regions/PreciousStonesFeature.java @@ -0,0 +1,55 @@ +package com.boydti.fawe.bukkit.regions; + +import com.boydti.fawe.bukkit.FaweBukkit; +import com.boydti.fawe.object.FawePlayer; +import com.boydti.fawe.regions.FaweMask; +import com.sk89q.worldedit.BlockVector; +import java.util.List; +import net.sacredlabyrinth.Phaed.PreciousStones.PreciousStones; +import net.sacredlabyrinth.Phaed.PreciousStones.field.Field; +import net.sacredlabyrinth.Phaed.PreciousStones.field.FieldFlag; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.event.Listener; +import org.bukkit.plugin.Plugin; + +public class PreciousStonesFeature extends BukkitMaskManager implements Listener { + FaweBukkit plugin; + Plugin preciousstones; + + public PreciousStonesFeature(final Plugin preciousstonesPlugin, final FaweBukkit p3) { + super(preciousstonesPlugin.getName()); + this.preciousstones = preciousstonesPlugin; + this.plugin = p3; + + } + + public boolean isAllowed(Player player, Field field, MaskType type, boolean allowMember) { + return field != null && (field.isOwner(player.getName()) || (type == MaskType.MEMBER && allowMember && field.getAllAllowed().contains(player.getName()))); + } + + @Override + public FaweMask getMask(final FawePlayer fp, MaskType type) { + final Player player = fp.parent; + final Location location = player.getLocation(); + final List fields = PreciousStones.API().getFieldsProtectingArea(FieldFlag.ALL, location); + if (fields.isEmpty()) { + return null; + } + String name = player.getName(); + boolean member = fp.hasPermission("fawe.preciousstones.member"); + for (final Field myField : fields) { + if (isAllowed(player, myField, type, member)) { + BlockVector pos1 = new BlockVector(myField.getMinx(), myField.getMiny(), myField.getMinz()); + BlockVector pos2 = new BlockVector(myField.getMaxx(), myField.getMaxy(), myField.getMaxz()); + return new FaweMask(pos1, pos2, "FIELD: " + myField) { + @Override + public boolean isValid(FawePlayer player, MaskType type) { + return isAllowed((Player) player.parent, myField, type, fp.hasPermission("fawe.preciousstones.member")); + } + }; + } + } + return null; + } +} diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/regions/ResidenceFeature.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/regions/ResidenceFeature.java new file mode 100644 index 000000000..024fa92f2 --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/regions/ResidenceFeature.java @@ -0,0 +1,58 @@ +package com.boydti.fawe.bukkit.regions; + +import com.bekvon.bukkit.residence.Residence; +import com.bekvon.bukkit.residence.protection.ClaimedResidence; +import com.bekvon.bukkit.residence.protection.CuboidArea; +import com.boydti.fawe.bukkit.FaweBukkit; +import com.boydti.fawe.object.FawePlayer; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.event.Listener; +import org.bukkit.plugin.Plugin; + +public class ResidenceFeature extends BukkitMaskManager implements Listener { + FaweBukkit plugin; + Plugin residence; + + public ResidenceFeature(final Plugin residencePlugin, final FaweBukkit p3) { + super(residencePlugin.getName()); + this.residence = residencePlugin; + this.plugin = p3; + + } + + public boolean isAllowed(Player player, ClaimedResidence residence, MaskType type) { + return residence != null && (residence.getOwner().equals(player.getName()) || residence.getOwner().equals(player.getUniqueId().toString()) || type == MaskType.MEMBER && residence.getPermissions().playerHas(player, "build", false)); + } + + @Override + public BukkitMask getMask(final FawePlayer fp, final MaskType type) { + final Player player = fp.parent; + final Location location = player.getLocation(); + ClaimedResidence residence = Residence.getInstance().getResidenceManager().getByLoc(location); + if (residence != null) { + boolean isAllowed; + while (!(isAllowed = isAllowed(player, residence, type)) && residence != null) { + residence = residence.getSubzoneByLoc(location); + } + if (isAllowed) { + final CuboidArea area = residence.getAreaArray()[0]; + final Location pos1 = area.getLowLoc(); + final Location pos2 = area.getHighLoc(); + final ClaimedResidence finalResidence = residence; + return new BukkitMask(pos1, pos2) { + @Override + public String getName() { + return "RESIDENCE: " + finalResidence.getName(); + } + + @Override + public boolean isValid(FawePlayer player, MaskType type) { + return isAllowed((Player) player.parent, finalResidence, type); + } + }; + } + } + return null; + } +} diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/regions/TownyFeature.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/regions/TownyFeature.java new file mode 100644 index 000000000..c3a5bdad2 --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/regions/TownyFeature.java @@ -0,0 +1,96 @@ +package com.boydti.fawe.bukkit.regions; + +import com.boydti.fawe.bukkit.FaweBukkit; +import com.boydti.fawe.object.FawePlayer; +import com.palmergames.bukkit.towny.Towny; +import com.palmergames.bukkit.towny.exceptions.NotRegisteredException; +import com.palmergames.bukkit.towny.object.PlayerCache; +import com.palmergames.bukkit.towny.object.TownBlock; +import com.palmergames.bukkit.towny.object.TownyUniverse; +import com.palmergames.bukkit.towny.object.WorldCoord; +import org.bukkit.Chunk; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.event.Listener; +import org.bukkit.plugin.Plugin; + +public class TownyFeature extends BukkitMaskManager implements Listener { + FaweBukkit plugin; + Plugin towny; + + public TownyFeature(final Plugin townyPlugin, final FaweBukkit p3) { + super(townyPlugin.getName()); + this.towny = townyPlugin; + this.plugin = p3; + } + + public boolean isAllowed(Player player, TownBlock block) { + if (block == null) { + return false; + } + try { + if (block.getResident().getName().equals(player.getName())) { + return true; + } + } catch (final Exception ignore) {} + if (player.hasPermission("fawe.towny.*")) { + return true; + } else try { + if (block.getTown().isMayor(TownyUniverse.getDataSource().getResident(player.getName()))) { + return true; + } + } catch (NotRegisteredException ignore) {} + return false; + } + + @Override + public BukkitMask getMask(final FawePlayer fp) { + final Player player = fp.parent; + final Location location = player.getLocation(); + try { + final PlayerCache cache = ((Towny) this.towny).getCache(player); + final WorldCoord mycoord = cache.getLastTownBlock(); + if (mycoord == null) { + return null; + } else { + final TownBlock myplot = mycoord.getTownBlock(); + if (myplot == null) { + return null; + } else { + boolean isMember = false; + try { + if (myplot.getResident().getName().equals(player.getName())) { + isMember = true; + } + } catch (final Exception e) { + + } + if (!isMember) { + if (player.hasPermission("fawe.towny.*")) { + isMember = true; + } else if (myplot.getTown().isMayor(TownyUniverse.getDataSource().getResident(player.getName()))) { + isMember = true; + } + } + if (isMember) { + final Chunk chunk = location.getChunk(); + final Location pos1 = new Location(location.getWorld(), chunk.getX() * 16, 0, chunk.getZ() * 16); + final Location pos2 = new Location(location.getWorld(), (chunk.getX() * 16) + 15, 156, (chunk.getZ() * 16) + 15); + return new BukkitMask(pos1, pos2) { + @Override + public String getName() { + return "PLOT:" + location.getChunk().getX() + "," + location.getChunk().getZ(); + } + + @Override + public boolean isValid(FawePlayer player, MaskType type) { + return isAllowed((Player) player.parent, myplot); + } + }; + } + } + } + } catch (final Exception e) {} + return null; + } +} diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/regions/Worldguard.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/regions/Worldguard.java new file mode 100644 index 000000000..941a234dd --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/regions/Worldguard.java @@ -0,0 +1,188 @@ +package com.boydti.fawe.bukkit.regions; + +import com.boydti.fawe.bukkit.FaweBukkit; +import com.boydti.fawe.bukkit.filter.WorldGuardFilter; +import com.boydti.fawe.object.FawePlayer; +import com.boydti.fawe.object.RegionWrapper; +import com.boydti.fawe.regions.FaweMask; +import com.boydti.fawe.regions.general.RegionFilter; +import com.sk89q.worldedit.BlockVector; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.regions.AbstractRegion; +import com.sk89q.worldedit.regions.CuboidRegion; +import com.sk89q.worldedit.regions.Polygonal2DRegion; +import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldguard.LocalPlayer; +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.GlobalProtectedRegion; +import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion; +import com.sk89q.worldguard.protection.regions.ProtectedPolygonalRegion; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.event.Listener; +import org.bukkit.plugin.Plugin; + +public class Worldguard extends BukkitMaskManager implements Listener { + WorldGuardPlugin worldguard; + FaweBukkit plugin; + + private WorldGuardPlugin getWorldGuard() { + final Plugin plugin = Bukkit.getPluginManager().getPlugin("WorldGuard"); + + // WorldGuard may not be loaded + if ((plugin == null) || !(plugin instanceof WorldGuardPlugin)) { + return null; // Maybe you want throw an exception instead + } + + return (WorldGuardPlugin) plugin; + } + + public Worldguard(final Plugin p2, final FaweBukkit p3) { + super(p2.getName()); + this.worldguard = this.getWorldGuard(); + this.plugin = p3; + } + + public ProtectedRegion getRegion(final com.sk89q.worldguard.LocalPlayer player, final Location loc) { + RegionManager manager = this.worldguard.getRegionManager(loc.getWorld()); + if (manager == null) { + if (this.worldguard.getGlobalStateManager().get(loc.getWorld()).useRegions) { + System.out.println("Region capability is not enabled for WorldGuard."); + } else { + System.out.println("WorldGuard is not enabled for that world."); + } + return null; + } + final ProtectedRegion global = manager.getRegion("__global__"); + if (global != null && isAllowed(player, global)) { + return global; + } + final ApplicableRegionSet regions = manager.getApplicableRegions(loc); + for (final ProtectedRegion region : regions) { + if (isAllowed(player, region)) { + return region; + } + } + return null; + } + + public boolean isAllowed(LocalPlayer localplayer, ProtectedRegion region) { + if (region.isOwner(localplayer) || region.isOwner(localplayer.getName())) { + return true; + } else if (region.getId().toLowerCase().equals(localplayer.getName().toLowerCase())) { + return true; + } else if (region.getId().toLowerCase().contains(localplayer.getName().toLowerCase() + "//")) { + return true; + } else if (region.isOwner("*")) { + return true; + } + if (localplayer.hasPermission("fawe.worldguard.member")) { + if (region.isMember(localplayer) || region.isMember(localplayer.getName())) { + return true; + } else if (region.isMember("*")) { + return true; + } + } + return false; + } + + @Override + public FaweMask getMask(FawePlayer fp, MaskType type) { + final Player player = fp.parent; + final com.sk89q.worldguard.LocalPlayer localplayer = this.worldguard.wrapPlayer(player); + final Location location = player.getLocation(); + final ProtectedRegion myregion = this.getRegion(localplayer, location); + if (myregion != null) { + final Location pos1; + final Location pos2; + if (myregion.getId().equals("__global__")) { + pos1 = new Location(location.getWorld(), Integer.MIN_VALUE, 0, Integer.MIN_VALUE); + pos2 = new Location(location.getWorld(), Integer.MAX_VALUE, 255, Integer.MAX_VALUE); + } else { + if (myregion instanceof ProtectedCuboidRegion) { + pos1 = new Location(location.getWorld(), myregion.getMinimumPoint().getBlockX(), myregion.getMinimumPoint().getBlockY(), myregion.getMinimumPoint().getBlockZ()); + pos2 = new Location(location.getWorld(), myregion.getMaximumPoint().getBlockX(), myregion.getMaximumPoint().getBlockY(), myregion.getMaximumPoint().getBlockZ()); + } else { + return new FaweMask(adapt(myregion), myregion.getId()) { + @Override + public boolean isValid(FawePlayer player, MaskType type) { + return isAllowed(worldguard.wrapPlayer((Player) player.parent), myregion); + } + }; + } + } + return new BukkitMask(pos1, pos2) { + @Override + public String getName() { + return myregion.getId(); + } + + @Override + public boolean isValid(FawePlayer player, MaskType type) { + return isAllowed(worldguard.wrapPlayer((Player) player.parent), myregion); + } + }; + } else { + return null; + } + } + + @Override + public RegionFilter getFilter(String world) { + return new WorldGuardFilter(Bukkit.getWorld(world)); + } + + private static class AdaptedRegion extends AbstractRegion { + private final ProtectedRegion region; + + public AdaptedRegion(ProtectedRegion region) { + super(null); + this.region = region; + } + + @Override + public Vector getMinimumPoint() { + return region.getMinimumPoint(); + } + + @Override + public Vector getMaximumPoint() { + return region.getMaximumPoint(); + } + + @Override + public void expand(Vector... changes) { + throw new UnsupportedOperationException("Region is immutable"); + } + + @Override + public void contract(Vector... changes) { + throw new UnsupportedOperationException("Region is immutable"); + } + + @Override + public boolean contains(Vector position) { + return region.contains(position); + } + } + + private static Region adapt(ProtectedRegion region) { + if (region instanceof ProtectedCuboidRegion) { + return new CuboidRegion(region.getMinimumPoint(), region.getMaximumPoint()); + } + if (region instanceof GlobalProtectedRegion) { + return RegionWrapper.GLOBAL(); + } + if (region instanceof ProtectedPolygonalRegion) { + ProtectedPolygonalRegion casted = (ProtectedPolygonalRegion) region; + BlockVector max = region.getMaximumPoint(); + BlockVector min = region.getMinimumPoint(); + return new Polygonal2DRegion(null, casted.getPoints(), min.getBlockY(), max.getBlockY()); + } + return new AdaptedRegion(region); + } +} diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/util/BukkitReflectionUtils.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/util/BukkitReflectionUtils.java new file mode 100644 index 000000000..1079f8c2d --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/util/BukkitReflectionUtils.java @@ -0,0 +1,109 @@ +package com.boydti.fawe.bukkit.util; + +import com.boydti.fawe.util.MainUtil; +import com.boydti.fawe.util.ReflectionUtils; +import java.lang.reflect.Method; +import org.bukkit.Bukkit; +import org.bukkit.Server; + +public class BukkitReflectionUtils { + /** + * prefix of bukkit classes + */ + private static volatile String preClassB = null; + /** + * prefix of minecraft classes + */ + private static volatile String preClassM = null; + /** + * boolean value, TRUE if server uses forge or MCPC+ + */ + private static boolean forge = false; + + /** + * check server version and class names + */ + public static void init() { + if (Bukkit.getServer() != null) { + if (Bukkit.getVersion().contains("MCPC") || Bukkit.getVersion().contains("Forge")) { + forge = true; + } + final Server server = Bukkit.getServer(); + final Class bukkitServerClass = server.getClass(); + String[] pas = bukkitServerClass.getName().split("\\."); + if (pas.length == 5) { + final String verB = pas[3]; + preClassB = "org.bukkit.craftbukkit." + verB; + } + try { + final Method getHandle = bukkitServerClass.getDeclaredMethod("getHandle"); + final Object handle = getHandle.invoke(server); + final Class handleServerClass = handle.getClass(); + pas = handleServerClass.getName().split("\\."); + if (pas.length == 5) { + final String verM = pas[3]; + preClassM = "net.minecraft.server." + verM; + } + } catch (final Exception ignored) { + MainUtil.handleError(ignored); + } + } + } + + + /** + * @return true if server has forge classes + */ + public static boolean isForge() { + return forge; + } + + /** + * Get class for name. Replace {nms} to net.minecraft.server.V*. Replace {cb} to org.bukkit.craftbukkit.V*. Replace + * {nm} to net.minecraft + * + * @param classes possible class paths + * @return RefClass object + * @throws RuntimeException if no class found + */ + public static ReflectionUtils.RefClass getRefClass(final String... classes) throws RuntimeException { + if (preClassM == null) { + init(); + } + for (String className : classes) { + try { + className = className.replace("{cb}", preClassB).replace("{nms}", preClassM).replace("{nm}", "net.minecraft"); + return ReflectionUtils.getRefClass(Class.forName(className)); + } catch (final ClassNotFoundException ignored) { + } + } + throw new RuntimeException("no class found: " + classes[0].replace("{cb}", preClassB).replace("{nms}", preClassM).replace("{nm}", "net.minecraft")); + } + + public static Class getNmsClass(final String name) { + final String className = "net.minecraft.server." + getVersion() + "." + name; + return ReflectionUtils.getClass(className); + } + + public static Class getCbClass(final String name) { + final String className = "org.bukkit.craftbukkit." + getVersion() + "." + name; + return ReflectionUtils.getClass(className); + } + + public static Class getUtilClass(final String name) { + try { + return Class.forName(name); //Try before 1.8 first + } catch (final ClassNotFoundException ex) { + try { + return Class.forName("net.minecraft.util." + name); //Not 1.8 + } catch (final ClassNotFoundException ex2) { + return null; + } + } + } + + public static String getVersion() { + final String packageName = Bukkit.getServer().getClass().getPackage().getName(); + return packageName.substring(packageName.lastIndexOf('.') + 1); + } +} diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/util/BukkitTaskMan.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/util/BukkitTaskMan.java new file mode 100644 index 000000000..12de1f9ec --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/util/BukkitTaskMan.java @@ -0,0 +1,63 @@ +package com.boydti.fawe.bukkit.util; + +import com.boydti.fawe.util.TaskManager; +import org.apache.commons.lang.mutable.MutableInt; +import org.bukkit.Bukkit; +import org.bukkit.plugin.Plugin; + +public class BukkitTaskMan extends TaskManager { + + private final Plugin plugin; + + public BukkitTaskMan(final Plugin plugin) { + this.plugin = plugin; + } + + @Override + public int repeat(final Runnable r, final int interval) { + return this.plugin.getServer().getScheduler().scheduleSyncRepeatingTask(this.plugin, r, interval, interval); + } + + @Override + public int repeatAsync(final Runnable r, final int interval) { + return this.plugin.getServer().getScheduler().scheduleAsyncRepeatingTask(this.plugin, r, interval, interval); + } + + public MutableInt index = new MutableInt(0); + + @Override + public void async(final Runnable r) { + if (r == null) { + return; + } + this.plugin.getServer().getScheduler().runTaskAsynchronously(this.plugin, r).getTaskId(); + } + + @Override + public void task(final Runnable r) { + if (r == null) { + return; + } + this.plugin.getServer().getScheduler().runTask(this.plugin, r).getTaskId(); + } + + @Override + public void later(final Runnable r, final int delay) { + if (r == null) { + return; + } + this.plugin.getServer().getScheduler().runTaskLater(this.plugin, r, delay).getTaskId(); + } + + @Override + public void laterAsync(final Runnable r, final int delay) { + this.plugin.getServer().getScheduler().runTaskLaterAsynchronously(this.plugin, r, delay); + } + + @Override + public void cancel(final int task) { + if (task != -1) { + Bukkit.getScheduler().cancelTask(task); + } + } +} diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/util/ItemUtil.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/util/ItemUtil.java new file mode 100644 index 000000000..77cc84abb --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/util/ItemUtil.java @@ -0,0 +1,96 @@ +package com.boydti.fawe.bukkit.util; + +import com.boydti.fawe.bukkit.v0.BukkitQueue_0; +import com.boydti.fawe.util.ReflectionUtils; +import com.sk89q.jnbt.CompoundTag; +import com.sk89q.jnbt.Tag; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import java.lang.ref.SoftReference; +import java.lang.ref.WeakReference; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import org.bukkit.inventory.ItemStack; + +public class ItemUtil { + + private final Class classCraftItemStack; + private final Method methodAsNMSCopy; + private final Class classNMSItem; + private final Method methodGetTag; + private final Method methodHasTag; + private final Method methodSetTag; + private final Method methodAsBukkitCopy; + private final Field fieldHandle; + + private SoftReference>> hashToNMSTag = new SoftReference(new Int2ObjectOpenHashMap<>()); + + public ItemUtil() throws Exception { + this.classCraftItemStack = BukkitReflectionUtils.getCbClass("inventory.CraftItemStack"); + this.classNMSItem = BukkitReflectionUtils.getNmsClass("ItemStack"); + this.methodAsNMSCopy = ReflectionUtils.setAccessible(classCraftItemStack.getDeclaredMethod("asNMSCopy", ItemStack.class)); + this.methodHasTag = ReflectionUtils.setAccessible(classNMSItem.getDeclaredMethod("hasTag")); + this.methodGetTag = ReflectionUtils.setAccessible(classNMSItem.getDeclaredMethod("getTag")); + this.fieldHandle = ReflectionUtils.setAccessible(classCraftItemStack.getDeclaredField("handle")); + Class classNBTTagCompound = BukkitReflectionUtils.getNmsClass("NBTTagCompound"); + this.methodSetTag = ReflectionUtils.setAccessible(classNMSItem.getDeclaredMethod("setTag", classNBTTagCompound)); + this.methodAsBukkitCopy = ReflectionUtils.setAccessible(classCraftItemStack.getDeclaredMethod("asBukkitCopy", classNMSItem)); + } + + public Object getNMSItem(ItemStack item) { + try { + Object nmsItem = fieldHandle.get(item); + if (nmsItem == null) nmsItem = methodAsNMSCopy.invoke(null, item); + return nmsItem; + } catch (Throwable e) { + e.printStackTrace(); + } + return null; + } + + public CompoundTag getNBT(ItemStack item) { + try { + if (!item.hasItemMeta()) return null; + Object nmsItem = fieldHandle.get(item); + if (nmsItem == null) nmsItem = methodAsNMSCopy.invoke(null, item); + if (methodHasTag.invoke(nmsItem).equals(true)) { + Object nmsTag = methodGetTag.invoke(nmsItem); + if (nmsTag == null) return null; + + Int2ObjectOpenHashMap> map = hashToNMSTag.get(); + if (map == null) { + map = new Int2ObjectOpenHashMap<>(); + hashToNMSTag = new SoftReference(new Int2ObjectOpenHashMap<>(map)); + } + WeakReference nativeTagRef = map.get(nmsTag.hashCode()); + if (nativeTagRef != null) { + Tag nativeTag = nativeTagRef.get(); + if (nativeTag != null) return (CompoundTag) nativeTag; + } + Tag nativeTag = BukkitQueue_0.toNative(nmsTag); + map.put(nmsTag.hashCode(), new WeakReference(nativeTag)); + return null; + } + } catch (Throwable e) { + e.printStackTrace(); + } + return null; + } + + public ItemStack setNBT(ItemStack item, CompoundTag tag) { + try { + Object nmsItem = fieldHandle.get(item); + boolean copy = false; + if (nmsItem == null) { + copy = true; + nmsItem = methodAsNMSCopy.invoke(null, item); + } + Object nmsTag = BukkitQueue_0.fromNative(tag); + methodSetTag.invoke(nmsItem, nmsTag); + if (copy) return (ItemStack) methodAsBukkitCopy.invoke(null, nmsItem); + return item; + } catch (Throwable e) { + e.printStackTrace(); + } + return null; + } +} diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/util/VaultUtil.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/util/VaultUtil.java new file mode 100644 index 000000000..dfc2cde83 --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/util/VaultUtil.java @@ -0,0 +1,18 @@ +package com.boydti.fawe.bukkit.util; + +import net.milkbowl.vault.permission.Permission; +import org.bukkit.Bukkit; +import org.bukkit.plugin.RegisteredServiceProvider; + +public class VaultUtil { + public final Permission permission; + + public VaultUtil() { + final RegisteredServiceProvider permissionProvider = Bukkit.getServer().getServicesManager().getRegistration(net.milkbowl.vault.permission.Permission.class); + if (permissionProvider != null) { + this.permission = permissionProvider.getProvider(); + } else { + this.permission = null; + } + } +} diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/util/cui/CUIListener.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/util/cui/CUIListener.java new file mode 100644 index 000000000..2101d64b8 --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/util/cui/CUIListener.java @@ -0,0 +1,31 @@ +package com.boydti.fawe.bukkit.util.cui; + +import com.boydti.fawe.object.FawePlayer; +import com.boydti.fawe.util.cui.CUI; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerMoveEvent; +import org.bukkit.plugin.Plugin; + +public class CUIListener implements Listener { + + public CUIListener(Plugin plugin) { + Bukkit.getPluginManager().registerEvents(this, plugin); + } + + @EventHandler + public void onPlayerMove(PlayerMoveEvent event) { + Location from = event.getFrom(); + Location to = event.getTo(); + if ((int) from.getX() >> 2 != (int) to.getX() >> 2 || (int) from.getZ() >> 2 != (int) to.getZ() >> 2 || (int) from.getY() >> 2 != (int) to.getY() >> 2) { + FawePlayer player = FawePlayer.wrap(event.getPlayer()); + CUI cui = player.getMeta("CUI"); + if (cui instanceof StructureCUI) { + StructureCUI sCui = (StructureCUI) cui; + sCui.update(); + } + } + } +} \ No newline at end of file diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/util/cui/StructureCUI.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/util/cui/StructureCUI.java new file mode 100644 index 000000000..37a57d0ff --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/util/cui/StructureCUI.java @@ -0,0 +1,197 @@ +package com.boydti.fawe.bukkit.util.cui; + +import com.boydti.fawe.FaweCache; +import com.boydti.fawe.bukkit.v0.BukkitQueue_0; +import com.boydti.fawe.object.FawePlayer; +import com.boydti.fawe.util.cui.CUI; +import com.comphenix.protocol.PacketType; +import com.comphenix.protocol.ProtocolLibrary; +import com.comphenix.protocol.ProtocolManager; +import com.comphenix.protocol.events.PacketContainer; +import com.comphenix.protocol.injector.PacketConstructor; +import com.comphenix.protocol.wrappers.BlockPosition; +import com.comphenix.protocol.wrappers.nbt.NbtBase; +import com.comphenix.protocol.wrappers.nbt.NbtCompound; +import com.comphenix.protocol.wrappers.nbt.NbtFactory; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.internal.cui.CUIEvent; +import com.sk89q.worldedit.internal.cui.SelectionPointEvent; +import com.sk89q.worldedit.internal.cui.SelectionShapeEvent; +import java.lang.reflect.InvocationTargetException; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; + +public class StructureCUI extends CUI { + private boolean cuboid = true; + + private Vector pos1; + private Vector pos2; + + private Vector remove; + private NbtCompound removeTag; + private int combined; + + public StructureCUI(FawePlayer player) { + super(player); + } + + @Override + public void dispatchCUIEvent(CUIEvent event) { + if (event instanceof SelectionShapeEvent) { + clear(); + this.cuboid = event.getParameters()[0].equalsIgnoreCase("cuboid"); + } else if (cuboid && event instanceof SelectionPointEvent) { + SelectionPointEvent spe = (SelectionPointEvent) event; + String[] param = spe.getParameters(); + int id = Integer.parseInt(param[0]); + int x = Integer.parseInt(param[1]); + int y = Integer.parseInt(param[2]); + int z = Integer.parseInt(param[3]); + Vector pos = new Vector(x, y, z); + if (id == 0) { + pos1 = pos; + } else { + pos2 = pos; + } + update(); + } + } + + private int viewDistance() { + Player player = this.getPlayer().parent; + if (Bukkit.getVersion().contains("paper")) { + return player.getViewDistance(); + } else { + return Bukkit.getViewDistance(); + } + } + + public void clear() { + pos1 = null; + pos2 = null; + update(); + } + + private NbtCompound constructStructureNbt(int x, int y, int z, int posX, int posY, int posZ, int sizeX, int sizeY, int sizeZ) { + HashMap tag = new HashMap<>(); + tag.put("name", UUID.randomUUID().toString()); + tag.put("author", "Empire92"); // :D + tag.put("metadata", ""); + tag.put("x", x); + tag.put("y", y); + tag.put("z", z); + tag.put("posX", posX); + tag.put("posY", posY); + tag.put("posZ", posZ); + tag.put("sizeX", sizeX); + tag.put("sizeY", sizeY); + tag.put("sizeZ", sizeZ); + tag.put("rotation", "NONE"); + tag.put("mirror", "NONE"); + tag.put("mode", "SAVE"); + tag.put("ignoreEntities", true); + tag.put("powered", false); + tag.put("showair", false); + tag.put("showboundingbox", true); + tag.put("integrity", 1.0f); + tag.put("seed", 0); + tag.put("id", "minecraft:structure_block"); + Object nmsTag = BukkitQueue_0.fromNative(FaweCache.asTag(tag)); + return NbtFactory.fromNMSCompound(nmsTag); + } + + private void sendOp() { + Player player = this.getPlayer().parent; + ProtocolManager manager = ProtocolLibrary.getProtocolManager(); + + PacketConstructor statusCtr = manager.createPacketConstructor(PacketType.Play.Server.ENTITY_STATUS, player, (byte) 28); + PacketContainer status = statusCtr.createPacket(player, (byte) 28); + + try { + manager.sendServerPacket(player, status); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } + } + + private void sendNbt(Vector pos, NbtCompound compound) { + Player player = this.getPlayer().parent; + ProtocolManager manager = ProtocolLibrary.getProtocolManager(); + + PacketContainer blockNbt = new PacketContainer(PacketType.Play.Server.TILE_ENTITY_DATA); + blockNbt.getBlockPositionModifier().write(0, new BlockPosition(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ())); + blockNbt.getIntegers().write(0, 7); + blockNbt.getNbtModifier().write(0, compound); + + + try { + manager.sendServerPacket(player, blockNbt); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } + } + + public synchronized void update() { + Player player = this.getPlayer().parent; + Location playerLoc = player.getLocation(); + boolean setOp = remove == null && !player.isOp(); + if (remove != null) { + int cx = playerLoc.getBlockX() >> 4; + int cz = playerLoc.getBlockZ() >> 4; + int viewDistance = viewDistance(); + if (Math.abs(cx - (remove.getBlockX() >> 4)) <= viewDistance && Math.abs(cz - (remove.getBlockZ() >> 4)) <= viewDistance) { + Map> map = removeTag.getValue(); + map.put("sizeX", NbtFactory.of("sizeX", 0)); + sendNbt(remove, removeTag); + Location removeLoc = new Location(player.getWorld(), remove.getX(), remove.getY(), remove.getZ()); + player.sendBlockChange(removeLoc, BukkitAdapter.getBlockData(combined)); + } + remove = null; + } + if (pos1 == null || pos2 == null) return; + Vector min = Vector.getMinimum(pos1, pos2); + Vector max = Vector.getMaximum(pos1, pos2); + + // Position + double rotX = playerLoc.getYaw(); + double rotY = playerLoc.getPitch(); + double xz = Math.cos(Math.toRadians(rotY)); + int x = (int) (playerLoc.getX() - (-xz * Math.sin(Math.toRadians(rotX))) * 12); + int z = (int) (playerLoc.getZ() - (xz * Math.cos(Math.toRadians(rotX))) * 12); + int y = Math.max(0, Math.min(Math.min(255, max.getBlockY() + 32), playerLoc.getBlockY() + 3)); + int minX = Math.max(Math.min(32, min.getBlockX() - x), -32); + int maxX = Math.max(Math.min(32, max.getBlockX() - x + 1), -32); + int minY = Math.max(Math.min(32, min.getBlockY() - y), -32); + int maxY = Math.max(Math.min(32, max.getBlockY() - y + 1), -32); + int minZ = Math.max(Math.min(32, min.getBlockZ() - z), -32); + int maxZ = Math.max(Math.min(32, max.getBlockZ() - z + 1), -32); + int sizeX = Math.min(32, maxX - minX); + int sizeY = Math.min(32, maxY - minY); + int sizeZ = Math.min(32, maxZ - minZ); + if (sizeX == 0 || sizeY == 0 || sizeZ == 0) return; + // maxX - 32; + int posX = Math.max(minX, Math.min(16, maxX) - 32); + int posY = Math.max(minY, Math.min(16, maxY) - 32); + int posZ = Math.max(minZ, Math.min(16, maxZ) - 32); + + // NBT + NbtCompound compound = constructStructureNbt(x, y, z, posX, posY, posZ, sizeX, sizeY, sizeZ); + + Block block = player.getWorld().getBlockAt(x, y, z); + remove = new Vector(x, y, z); + combined = BukkitAdapter.adapt(block.getBlockData()).getInternalId(); + removeTag = compound; + + Location blockLoc = new Location(player.getWorld(), x, y, z); + player.sendBlockChange(blockLoc, Material.STRUCTURE_BLOCK, (byte) 0); + if (setOp) sendOp(); + sendNbt(remove, compound); + } +} diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/util/image/BukkitImageViewer.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/util/image/BukkitImageViewer.java new file mode 100644 index 000000000..73a0877ba --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/util/image/BukkitImageViewer.java @@ -0,0 +1,191 @@ +package com.boydti.fawe.bukkit.util.image; + +import com.boydti.fawe.util.image.Drawable; +import com.boydti.fawe.util.image.ImageUtil; +import com.boydti.fawe.util.image.ImageViewer; +import java.awt.RenderingHints; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.util.Collection; +import javax.annotation.Nullable; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Rotation; +import org.bukkit.World; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Entity; +import org.bukkit.entity.ItemFrame; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; +//import org.inventivetalent.mapmanager.MapManagerPlugin; +//import org.inventivetalent.mapmanager.controller.MapController; +//import org.inventivetalent.mapmanager.controller.MultiMapController; +//import org.inventivetalent.mapmanager.manager.MapManager; +//import org.inventivetalent.mapmanager.wrapper.MapWrapper; + +public class BukkitImageViewer implements ImageViewer { +// private final MapManager mapManager; +// private final Player player; +// private BufferedImage last; + private ItemFrame[][] frames; +// private boolean reverse; + + public BukkitImageViewer(Player player) { +// mapManager = ((MapManagerPlugin) Bukkit.getPluginManager().getPlugin("MapManager")).getMapManager(); +// this.player = player; + } +// + public void selectFrame(ItemFrame start) { +// Location pos1 = start.getLocation().clone(); +// Location pos2 = start.getLocation().clone(); +// +// BlockFace facing = start.getFacing(); +// int planeX = facing.getModX() == 0 ? 1 : 0; +// int planeY = facing.getModY() == 0 ? 1 : 0; +// int planeZ = facing.getModZ() == 0 ? 1 : 0; +// +// ItemFrame[][] res = find(pos1, pos2, facing); +// Location tmp; +// while (true) { +// if (res != null) { +// frames = res; +// } +// tmp = pos1.clone().subtract(planeX, planeY, planeZ); +// if ((res = find(tmp, pos2, facing)) != null) { +// pos1 = tmp; +// continue; +// } +// tmp = pos2.clone().add(planeX, planeY, planeZ); +// if ((res = find(pos1, tmp, facing)) != null) { +// pos2 = tmp; +// continue; +// } +// tmp = pos1.clone().subtract(planeX, 0, planeZ); +// if ((res = find(tmp, pos2, facing)) != null) { +// pos1 = tmp; +// continue; +// } +// tmp = pos2.clone().add(planeX, 0, planeZ); +// if ((res = find(pos1, tmp, facing)) != null) { +// pos2 = tmp; +// continue; +// } +// tmp = pos1.clone().subtract(0, 1, 0); +// if ((res = find(tmp, pos2, facing)) != null) { +// pos1 = tmp; +// continue; +// } +// tmp = pos2.clone().add(0, 1, 0); +// if ((res = find(pos1, tmp, facing)) != null) { +// pos2 = tmp; +// continue; +// } +// break; +// } + } +// + public ItemFrame[][] getItemFrames() { + return frames; + } +// +// private ItemFrame[][] find(Location pos1, Location pos2, BlockFace facing) { +// try { +// Location distance = pos2.clone().subtract(pos1).add(1, 1, 1); +// int width = Math.max(distance.getBlockX(), distance.getBlockZ()); +// ItemFrame[][] frames = new ItemFrame[width][distance.getBlockY()]; +// +// World world = pos1.getWorld(); +// +// this.reverse = (facing == BlockFace.NORTH || facing == BlockFace.EAST); +// int v = 0; +// for (double y = pos1.getY(); y <= pos2.getY(); y++, v++) { +// int h = 0; +// for (double z = pos1.getZ(); z <= pos2.getZ(); z++) { +// for (double x = pos1.getX(); x <= pos2.getX(); x++, h++) { +// Location pos = new Location(world, x, y, z); +// Collection entities = world.getNearbyEntities(pos, 0.1, 0.1, 0.1); +// boolean contains = false; +// for (Entity ent : entities) { +// if (ent instanceof ItemFrame && ((ItemFrame) ent).getFacing() == facing) { +// ItemFrame itemFrame = (ItemFrame) ent; +// itemFrame.setRotation(Rotation.NONE); +// contains = true; +// frames[reverse ? width - 1 - h : h][v] = (ItemFrame) ent; +// break; +// } +// } +// if (!contains) return null; +// } +// } +// } +// return frames; +// } catch (Throwable e) { +// e.printStackTrace(); +// } +// return null; +// } + + @Override + public void view(Drawable drawable) { +// view(null, drawable); + } +// +// private void view(@Nullable BufferedImage image, @Nullable Drawable drawable) { +// if (image == null && drawable == null) throw new IllegalArgumentException("An image or drawable must be provided. Both cannot be null"); +// boolean initializing = last == null; +// +// if (this.frames != null) { +// if (image == null && drawable != null) image = drawable.draw(); +// last = image; +// int width = frames.length; +// int height = frames[0].length; +// BufferedImage scaled = ImageUtil.getScaledInstance(image, 128 * width, 128 * height, RenderingHints.VALUE_INTERPOLATION_BILINEAR, false); +// MapWrapper mapWrapper = mapManager.wrapMultiImage(scaled, width, height); +// MultiMapController controller = (MultiMapController) mapWrapper.getController(); +// controller.addViewer(player); +// controller.sendContent(player); +// controller.showInFrames(player, frames, true); +// } else { +// int slot = getMapSlot(player); +// if (slot == -1) { +// if (initializing) { +// player.getInventory().setItemInMainHand(new ItemStack(Material.MAP)); +// } else { +// return; +// } +// } else if (player.getInventory().getHeldItemSlot() != slot) { +// player.getInventory().setHeldItemSlot(slot); +// } +// if (image == null && drawable != null) image = drawable.draw(); +// last = image; +// BufferedImage scaled = ImageUtil.getScaledInstance(image, 128, 128, RenderingHints.VALUE_INTERPOLATION_BILINEAR, false); +// MapWrapper mapWrapper = mapManager.wrapImage(scaled); +// MapController controller = mapWrapper.getController(); +// controller.addViewer(player); +// controller.sendContent(player); +// controller.showInHand(player, true); +// } +// } +// +// private int getMapSlot(Player player) { +// PlayerInventory inventory = player.getInventory(); +// for (int i = 0; i < 9; i++) { +// ItemStack item = inventory.getItem(i); +// if (item != null && item.getType() == Material.MAP) { +// return i; +// } +// } +// return -1; +// } +// + public void refresh() { +// if (last != null) view(last, null); + } + + @Override + public void close() throws IOException { +// last = null; + } +} \ No newline at end of file diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/v0/BukkitChunk_All.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/v0/BukkitChunk_All.java new file mode 100644 index 000000000..855dde5c6 --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/v0/BukkitChunk_All.java @@ -0,0 +1,378 @@ +package com.boydti.fawe.bukkit.v0; + +import com.boydti.fawe.Fawe; +import com.boydti.fawe.FaweCache; +import com.boydti.fawe.example.IntFaweChunk; +import com.boydti.fawe.object.FaweChunk; +import com.boydti.fawe.object.FaweQueue; +import com.boydti.fawe.object.RunnableVal2; +import com.boydti.fawe.util.MainUtil; +import com.boydti.fawe.util.ReflectionUtils; +import com.sk89q.jnbt.CompoundTag; +import com.sk89q.jnbt.ListTag; +import com.sk89q.jnbt.LongTag; +import com.sk89q.jnbt.Tag; +import com.sk89q.worldedit.blocks.BaseBlock; +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; +import com.sk89q.worldedit.entity.BaseEntity; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import com.sk89q.worldedit.world.block.BlockStateHolder; +import com.sk89q.worldedit.world.block.BlockType; +import com.sk89q.worldedit.world.block.BlockTypes; +import com.sk89q.worldedit.world.entity.EntityTypes; +import org.bukkit.Bukkit; +import org.bukkit.Chunk; +import org.bukkit.ChunkSnapshot; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.block.Biome; +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.entity.Entity; + +public class BukkitChunk_All extends IntFaweChunk { + + /** + * A FaweSections object represents a chunk and the blocks that you wish to change in it. + * + * @param parent + * @param x + * @param z + */ + public BukkitChunk_All(FaweQueue parent, int x, int z) { + super(parent, x, z); + } + + public BukkitChunk_All(FaweQueue parent, int x, int z, int[][] ids, short[] count, short[] air, byte[] heightMap) { + super(parent, x, z, ids, count, air, heightMap); + } + + @Override + public IntFaweChunk copy(boolean shallow) { + BukkitChunk_All copy; + if (shallow) { + copy = new BukkitChunk_All(getParent(), getX(), getZ(), ids, count, air, heightMap); + copy.biomes = biomes; + } else { + copy = new BukkitChunk_All(getParent(), getX(), getZ(), (int[][]) MainUtil.copyNd(ids), count.clone(), air.clone(), heightMap.clone()); + copy.biomes = biomes != null ? biomes.clone() : null; + } + copy.chunk = chunk; + return copy; + } + + @Override + public Chunk getNewChunk() { + return Bukkit.getWorld(getParent().getWorldName()).getChunkAt(getX(), getZ()); + } + + private int layer = -1; + private int index; + private boolean place = true; + + @Override + public void start() { + getChunk().load(true); + } + + private static boolean canTick(BlockType type) { + return type.getMaterial().isTicksRandomly(); + } + + /** + * + * @return + */ + @Override + public FaweChunk call() { + long start = System.currentTimeMillis(); + int recommended = 25 + BukkitQueue_All.ALLOCATE; + boolean more = true; + final BukkitQueue_All parent = (BukkitQueue_All) getParent(); + BukkitImplAdapter adapter = BukkitQueue_0.getAdapter(); + + final Chunk chunk = getChunk(); + Object[] disableResult = parent.disableLighting(chunk); + final World world = chunk.getWorld(); + int[][] sections = getCombinedIdArrays(); + final int bx = getX() << 4; + final int bz = getZ() << 4; + if (layer == -1) { + if (adapter != null) + { + // Run change task + RunnableVal2 task = parent.getChangeTask(); + BukkitChunk_All_ReadonlySnapshot previous; + if (task != null){ + ChunkSnapshot snapshot = parent.ensureChunkLoaded(getX(), getZ()); + previous = new BukkitChunk_All_ReadonlySnapshot(parent, this, snapshot, biomes != null); + for (BlockState tile : chunk.getTileEntities()) { + int x = tile.getX(); + int y = tile.getY(); + int z = tile.getZ(); + if (getBlockCombinedId(x & 15, y, z & 15) != 0) { + CompoundTag nbt = adapter.getBlock(new Location(world, x, y, z)).getNbtData(); + if (nbt != null) { + previous.setTile(x & 15, y, z & 15, nbt); + } + } + } + } else { + previous = null; + } + // Set entities + if (adapter != null) { + Set entitiesToSpawn = this.getEntities(); + if (!entitiesToSpawn.isEmpty()) { + for (CompoundTag tag : entitiesToSpawn) { + String id = tag.getString("Id"); + ListTag posTag = tag.getListTag("Pos"); + ListTag rotTag = tag.getListTag("Rotation"); + if (id == null || posTag == null || rotTag == null) { + Fawe.debug("Unknown entity tag: " + tag); + continue; + } + double x = posTag.getDouble(0); + double y = posTag.getDouble(1); + double z = posTag.getDouble(2); + float yaw = rotTag.getFloat(0); + float pitch = rotTag.getFloat(1); + Location loc = new Location(world, x, y, z, yaw, pitch); + Entity created = adapter.createEntity(loc, new BaseEntity(EntityTypes.get(id), tag)); + if (previous != null) { + UUID uuid = created.getUniqueId(); + Map map = ReflectionUtils.getMap(tag.getValue()); + map.put("UUIDLeast", new LongTag(uuid.getLeastSignificantBits())); + map.put("UUIDMost", new LongTag(uuid.getMostSignificantBits())); + } + } + } + HashSet entsToRemove = this.getEntityRemoves(); + if (!entsToRemove.isEmpty()) { + for (Entity entity : chunk.getEntities()) { + if (entsToRemove.contains(entity.getUniqueId())) { + entity.remove(); + } + } + } + } + if (previous != null) { + task.run(previous, this); + } + + // Biomes + if (layer == 0) { + final byte[] biomes = getBiomeArray(); + if (biomes != null) { + int index = 0; + for (int z = 0; z < 16; z++) { + int zz = bx + z; + for (int x = 0; x < 16; x++) { + int xx = bz + x; + Biome bukkitBiome = adapter.getBiome(biomes[index++] & 0xFF); + world.setBiome(xx, zz, bukkitBiome); + } + } + } + } + } + + + } else if (index != 0) { + if (place) { + layer--; + } else { + layer++; + } + } + mainloop: + do { + if (place) { + if (++layer >= sections.length) { + place = false; + layer = sections.length - 1; + } + } else if (--layer < 0) { + more = false; + break; + } + try { + // Efficiently merge sections + int changes = getCount(layer); + if (changes == 0) { + continue; + } + final int[] newArray = sections[layer]; + if (newArray == null) { + continue; + } + final byte[] cacheX = FaweCache.CACHE_X[layer]; + final short[] cacheY = FaweCache.CACHE_Y[layer]; + final byte[] cacheZ = FaweCache.CACHE_Z[layer]; + boolean checkTime = !((getAir(layer) == 4096 || (getCount(layer) == 4096 && getAir(layer) == 0) || (getCount(layer) == getAir(layer)))); + + Location mutableLoc = new Location(world, 0, 0, 0); + + if (!checkTime) { + System.out.println("Set " + layer); + int index = 0; + for (int y = 0; y < 16; y++) { + int yy = (layer << 4) + y; + for (int z = 0; z < 16; z++) { + int zz = bz + z; + for (int x = 0; x < 16; x++, index++) { + int combined = newArray[index]; + if (combined == 0) continue; + int xx = bx + x; + + BlockTypes type = BlockTypes.getFromStateId(combined); + switch (type) { + case __RESERVED__: + continue; + case AIR: + case CAVE_AIR: + case VOID_AIR: + if (!place) { + mutableLoc.setX(xx); + mutableLoc.setY(yy); + mutableLoc.setZ(zz); + setBlock(adapter, mutableLoc, combined); + } + continue; + default: + if (place) { + if (type.getMaterial().hasContainer() && adapter != null) { + CompoundTag nbt = getTile(x, yy, z); + if (nbt != null) { + mutableLoc.setX(xx); + mutableLoc.setY(yy); + mutableLoc.setZ(zz); + synchronized (BukkitChunk_All.this) { + BaseBlock state = BaseBlock.getFromInternalId(combined, nbt); + adapter.setBlock(mutableLoc, state, false); + } + continue; + } + } + if (type.getMaterial().isTicksRandomly()) { + synchronized (BukkitChunk_All.this) { + setBlock(adapter, mutableLoc, combined); + } + } else { + mutableLoc.setX(xx); + mutableLoc.setY(yy); + mutableLoc.setZ(zz); + setBlock(adapter, mutableLoc, combined); + } + } + continue; + } + } + } + } + } else { + for (;index < 4096; index++) { + int j = place ? index : 4095 - index; + int combined = newArray[j]; + BlockTypes type = BlockTypes.getFromStateId(combined); + switch (type) { + case __RESERVED__: + continue; + case AIR: + case CAVE_AIR: + case VOID_AIR: + if (!place) { + int x = cacheX[j]; + int z = cacheZ[j]; + int y = cacheY[j]; + mutableLoc.setX(bx + x); + mutableLoc.setY(y); + mutableLoc.setZ(bz + z); + setBlock(adapter, mutableLoc, combined); + } + break; + default: + boolean light = type.getMaterial().getLightValue() > 0; + if (light) { + if (place) { + continue; + } + light = light && getParent().getSettings().LIGHTING.MODE != 0; + if (light) { + parent.enableLighting(disableResult); + } + } else if (!place) { + continue; + } + int x = cacheX[j]; + int z = cacheZ[j]; + int y = cacheY[j]; + if (type.getMaterial().hasContainer() && adapter != null) { + CompoundTag tile = getTile(x, y, z); + if (tile != null) { + synchronized (BukkitChunk_All.this) { + BaseBlock state = BaseBlock.getFromInternalId(combined, tile); + adapter.setBlock(new Location(world, bx + x, y, bz + z), state, false); + } + break; + } + } + if (type.getMaterial().isTicksRandomly()) { + synchronized (BukkitChunk_All.this) { + mutableLoc.setX(bx + x); + mutableLoc.setY(y); + mutableLoc.setZ(bz + z); + setBlock(adapter, mutableLoc, combined); + } + } else { + mutableLoc.setX(bx + x); + mutableLoc.setY(y); + mutableLoc.setZ(bz + z); + setBlock(adapter, mutableLoc, combined); + } + if (light) { + parent.disableLighting(disableResult); + } + break; + } + if (checkTime && System.currentTimeMillis() - start > recommended) { + index++; + break mainloop; + } + } + index = 0; + } + } catch (final Throwable e) { + MainUtil.handleError(e); + } + } while (System.currentTimeMillis() - start < recommended); + if (more || place) { + this.addToQueue(); + } + parent.resetLighting(disableResult); + return this; + } + + public void setBlock(BukkitImplAdapter adapter, Block block, BlockStateHolder state) { + if (adapter != null) { + adapter.setBlock(block.getLocation(), state, false); + } else { + block.setBlockData(BukkitAdapter.adapt(state), false); + } + } + + public void setBlock(BukkitImplAdapter adapter, Location location, int combinedId) { + if (adapter != null) { + adapter.setBlock(location, com.sk89q.worldedit.world.block.BlockState.get(combinedId), false); + } else { + Block block = location.getWorld().getBlockAt(location); + block.setBlockData(BukkitAdapter.getBlockData(combinedId), false); + } + } +} diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/v0/BukkitChunk_All_ReadonlySnapshot.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/v0/BukkitChunk_All_ReadonlySnapshot.java new file mode 100644 index 000000000..d8c8accfe --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/v0/BukkitChunk_All_ReadonlySnapshot.java @@ -0,0 +1,156 @@ +package com.boydti.fawe.bukkit.v0; + +import com.boydti.fawe.FaweCache; +import com.boydti.fawe.object.FaweChunk; +import com.boydti.fawe.util.MathMan; +import com.sk89q.jnbt.CompoundTag; +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; + +import java.util.*; + +import com.sk89q.worldedit.world.block.BlockTypes; +import org.bukkit.ChunkSnapshot; +import org.bukkit.block.Biome; +import org.bukkit.block.data.BlockData; + +import javax.annotation.Nullable; + +public class BukkitChunk_All_ReadonlySnapshot extends FaweChunk { + private final ChunkSnapshot snapshot; + private final boolean hasBiomes; + private final BukkitChunk_All next; + private Set entities = new HashSet<>(); + private Map tiles = new HashMap<>(); + + public BukkitChunk_All_ReadonlySnapshot(BukkitQueue_All parent, BukkitChunk_All next, ChunkSnapshot snapshot, boolean biomes) { + super(parent, snapshot.getX(), snapshot.getZ()); + this.next = next; + this.snapshot = snapshot; + this.hasBiomes = biomes; + } + + public void setTiles(Map tiles) { + this.tiles = tiles; + } + + public void setEntities(Set entities) { + this.entities = entities; + } + + @Override + public BukkitQueue_All getParent() { + return (BukkitQueue_All) super.getParent(); + } + + @Override + public int getBitMask() { + return Character.MAX_VALUE; + } + + @Override + public int getBlockCombinedId(int x, int y, int z) { + BlockData blockData = snapshot.getBlockData(x, y, z); + return BukkitAdapter.adapt(blockData).getInternalId(); + } + + @Override + public byte[] getBiomeArray() { + if (!hasBiomes || next.biomes == null) return null; + BukkitImplAdapter adapter = getParent().getAdapter(); + byte[] biomes = Arrays.copyOf(next.biomes, next.biomes.length); + int index = 0; + for (int z = 0; z < 16; z++) { + for (int x = 0; x < 16; x++, index++) { + if (biomes[index] != 0) { + Biome biome = snapshot.getBiome(x, z); + biomes[index] = (byte) adapter.getBiomeId(biome); + } + } + } + return biomes; + } + + @Nullable + @Override + public int[] getIdArray(int layer) { + int[] nextLayer = next.ids[layer]; + if (nextLayer == null) return null; + int[] ids = Arrays.copyOf(nextLayer, nextLayer.length); + int index = 0; + int by = layer << 4; + for (int y = 0; y < 16; y++) { + int yy = by + y; + for (int z = 0; z < 16; z++) { + for (int x = 0; x < 16; x++, index++) { + if (ids[index] != 0) { + ids[index] = getBlockCombinedId(x, yy, z); + } + } + } + } + return ids; + } + + @Override + public Object getChunk() { + return snapshot; + } + + @Override + public void setTile(int x, int y, int z, CompoundTag tile) { + tiles.put(MathMan.tripleBlockCoord(x, y, z), tile); + } + + @Override + public void setEntity(CompoundTag entity) { + entities.add(entity); + } + + @Override + public void removeEntity(UUID uuid) { + throw new UnsupportedOperationException("Read only"); + } + + @Override + public void setBlock(int x, int y, int z, int combinedId) { + throw new UnsupportedOperationException("Read only"); + } + + @Override + public Set getEntities() { + return entities; + } + + @Override + public Set getEntityRemoves() { + throw new UnsupportedOperationException("Read only"); + } + + @Override + public Map getTiles() { + return tiles; + } + + @Override + public CompoundTag getTile(int x, int y, int z) { + if (tiles == null) return null; + short pair = MathMan.tripleBlockCoord(x, y, z); + return tiles.get(pair); + } + + @Override + public void setBiome(int x, int z, byte biome) { + throw new UnsupportedOperationException("Read only"); + } + + @Override + public FaweChunk copy(boolean shallow) { + return null; + } + + @Override + public FaweChunk call() { + return null; + } +} diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/v0/BukkitQueue_0.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/v0/BukkitQueue_0.java new file mode 100644 index 000000000..5fe5a4daf --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/v0/BukkitQueue_0.java @@ -0,0 +1,401 @@ +package com.boydti.fawe.bukkit.v0; + +import com.boydti.fawe.Fawe; +import com.boydti.fawe.bukkit.BukkitPlayer; +import com.boydti.fawe.bukkit.FaweBukkit; +import com.boydti.fawe.bukkit.util.BukkitReflectionUtils; +import com.boydti.fawe.example.IntFaweChunk; +import com.boydti.fawe.example.NMSMappedFaweQueue; +import com.boydti.fawe.object.FaweChunk; +import com.boydti.fawe.object.FawePlayer; +import com.boydti.fawe.object.RunnableVal; +import com.boydti.fawe.object.queue.LazyFaweChunk; +import com.boydti.fawe.object.visitor.FaweChunkVisitor; +import com.boydti.fawe.util.MathMan; +import com.boydti.fawe.util.ReflectionUtils; +import com.boydti.fawe.util.TaskManager; +import com.comphenix.protocol.ProtocolLibrary; +import com.comphenix.protocol.ProtocolManager; +import com.comphenix.protocol.injector.netty.WirePacket; +import com.sk89q.jnbt.Tag; +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.bukkit.WorldEditPlugin; +import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; +import com.sk89q.worldedit.world.biome.BaseBiome; +import java.io.File; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.block.BlockStateHolder; +import org.bukkit.Bukkit; +import org.bukkit.Chunk; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.WorldCreator; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.world.ChunkLoadEvent; +import org.bukkit.event.world.ChunkUnloadEvent; +import org.bukkit.event.world.WorldInitEvent; +import org.bukkit.plugin.Plugin; + +public abstract class BukkitQueue_0 extends NMSMappedFaweQueue implements Listener { + + protected static boolean PAPER = true; + private static BukkitImplAdapter adapter = WorldEditPlugin.getInstance().getBukkitImplAdapter(); + private static Method methodGetHandle; + + static { + Class classCraftChunk = BukkitReflectionUtils.getCbClass("CraftChunk"); + try { + methodGetHandle = ReflectionUtils.setAccessible(classCraftChunk.getDeclaredMethod("getHandle")); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } + } + + public BukkitQueue_0(final com.sk89q.worldedit.world.World world) { + super(world); + if (!registered) { + registered = true; + Bukkit.getServer().getPluginManager().registerEvents(this, ((FaweBukkit) Fawe.imp()).getPlugin()); + } + } + + public BukkitQueue_0(String world) { + super(world); + if (!registered) { + registered = true; + Bukkit.getServer().getPluginManager().registerEvents(this, ((FaweBukkit) Fawe.imp()).getPlugin()); + } + } + + @Override + public boolean supports(Capability capability) { + switch (capability) { + case CHUNK_PACKETS: + Plugin plib = Bukkit.getPluginManager().getPlugin("ProtocolLib"); + return plib != null && plib.isEnabled(); + } + return super.supports(capability); + } + + @Override + public void sendChunk(FaweChunk fc) { + if (!Fawe.isMainThread()) { + startSet(true); + try { + super.sendChunk(fc); + } finally { + endSet(true); + } + } else super.sendChunk(fc); + } + + @Override + public void sendChunkUpdate(FaweChunk chunk, FawePlayer... players) { + if (supports(Capability.CHUNK_PACKETS)) { + sendChunkUpdatePLIB(chunk, players); + } else { + sendBlockUpdate(chunk, players); + } + } + + public void sendChunkUpdatePLIB(FaweChunk chunk, FawePlayer... players) { + ProtocolManager manager = ProtocolLibrary.getProtocolManager(); + WirePacket packet = null; + int viewDistance = Bukkit.getViewDistance(); + try { + for (int i = 0; i < players.length; i++) { + int cx = chunk.getX(); + int cz = chunk.getZ(); + + Player player = ((BukkitPlayer) players[i]).parent; + Location loc = player.getLocation(); + + if (Math.abs((loc.getBlockX() >> 4) - cx) <= viewDistance && Math.abs((loc.getBlockZ() >> 4) - cz) <= viewDistance) { + if (packet == null) { + byte[] data; + byte[] buffer = new byte[8192]; + if (chunk instanceof LazyFaweChunk) { + chunk = (FaweChunk) chunk.getChunk(); + } + // TODO FIXME +// if (chunk instanceof MCAChunk) { +// data = new MCAChunkPacket((MCAChunk) chunk, true, true, hasSky()).apply(buffer); +// } else { +// data = new FaweChunkPacket(chunk, true, true, hasSky()).apply(buffer); +// } +// packet = new WirePacket(PacketType.Play.Server.MAP_CHUNK, data); + } + manager.sendWirePacket(player, packet); + } + } + } catch (InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + @Override + public boolean queueChunkLoad(int cx, int cz, RunnableVal operation) { + if (PAPER) { + try { + new PaperChunkCallback(getImpWorld(), cx, cz) { + @Override + public void onLoad(Chunk bukkitChunk) { + try { + CHUNK chunk = (CHUNK) methodGetHandle.invoke(bukkitChunk); + try { + operation.run(chunk); + } catch (Throwable e) { + e.printStackTrace(); + } + } catch (Throwable e) { + PAPER = false; + } + } + }; + return true; + } catch (Throwable ignore) { + PAPER = false; + } + } + return super.queueChunkLoad(cx, cz); + } + + public static BukkitImplAdapter getAdapter() { + return adapter; + } + + public static Tag toNative(Object tag) { + BukkitImplAdapter adapter = getAdapter(); + return adapter.toNative(tag); + } + + public static Object fromNative(Tag tag) { + BukkitImplAdapter adapter = getAdapter(); + return adapter.fromNative(tag); + } + + @Override + public File getSaveFolder() { + return new File(Bukkit.getWorldContainer(), getWorldName() + File.separator + "region"); + } + + @Override + public void setFullbright(CHUNKSECTIONS sections) {} + + @Override + public void relight(int x, int y, int z) {} + + @Override + public void relightBlock(int x, int y, int z) {} + + @Override + public void relightSky(int x, int y, int z) {} + + @Override + public boolean removeSectionLighting(SECTION sections, int layer, boolean hasSky) { + return false; + } + + public static void checkVersion(String supported) { + String version = Bukkit.getServer().getClass().getPackage().getName(); + if (!version.contains(supported)) { + throw new IllegalStateException("Unsupported version: " + version + " (supports: " + supported + ")"); + } + } + + protected static boolean registered = false; + protected static boolean disableChunkLoad = false; + + @EventHandler + public static void onWorldLoad(WorldInitEvent event) { + if (disableChunkLoad) { + World world = event.getWorld(); + world.setKeepSpawnInMemory(false); + } + } + + public static ConcurrentHashMap keepLoaded = new ConcurrentHashMap<>(8, 0.9f, 1); + + + @EventHandler + public static void onChunkLoad(ChunkLoadEvent event) { + Chunk chunk = event.getChunk(); + long pair = MathMan.pairInt(chunk.getX(), chunk.getZ()); + keepLoaded.putIfAbsent(pair, Fawe.get().getTimer().getTickStart()); + } + + @EventHandler + public static void onChunkUnload(ChunkUnloadEvent event) { + Chunk chunk = event.getChunk(); + long pair = MathMan.pairInt(chunk.getX(), chunk.getZ()); + Long lastLoad = keepLoaded.get(pair); + if (lastLoad != null) { + if (Fawe.get().getTimer().getTickStart() - lastLoad < 10000) { + event.setCancelled(true); + } else { + keepLoaded.remove(pair); + } + } + } + + @Override + public boolean queueChunkLoad(int cx, int cz) { + if (super.queueChunkLoad(cx, cz)) { + keepLoaded.put(MathMan.pairInt(cx, cz), System.currentTimeMillis()); + return true; + } + return false; + } + + public World createWorld(final WorldCreator creator) { + World world = TaskManager.IMP.sync(new RunnableVal() { + @Override + public void run(World value) { + disableChunkLoad = true; + this.value = creator.createWorld(); + disableChunkLoad = false; + } + }); + return world; + } + + @Override + public World getImpWorld() { + return getWorldName() != null ? Bukkit.getWorld(getWorldName()) : null; + } + + @Override + public void sendChunk(int x, int z, int bitMask) {} + + @Override + public void refreshChunk(FaweChunk fs) {} + + @Override + public boolean regenerateChunk(World world, int x, int z, BaseBiome biome, Long seed) { + if (!keepLoaded.isEmpty()) keepLoaded.remove(MathMan.pairInt(x, z)); + boolean result = world.regenerateChunk(x, z); + return result; + } + + @Override + public IntFaweChunk getPrevious(IntFaweChunk fs, CHUNKSECTIONS sections, Map tiles, Collection[] entities, Set createdEntities, boolean all) throws Exception { + return fs; + } + + @Override + public boolean hasSky() { + World world = getWorld(); + return world == null || world.getEnvironment() == World.Environment.NORMAL; + } + + private volatile boolean timingsEnabled; + private static boolean alertTimingsChange = true; + + private static Field fieldTimingsEnabled; + private static Field fieldAsyncCatcherEnabled; + private static Method methodCheck; + static { + try { + fieldAsyncCatcherEnabled = Class.forName("org.spigotmc.AsyncCatcher").getField("enabled"); + fieldAsyncCatcherEnabled.setAccessible(true); + } catch (Throwable ignore) {} + try { + fieldTimingsEnabled = Class.forName("co.aikar.timings.Timings").getDeclaredField("timingsEnabled"); + fieldTimingsEnabled.setAccessible(true); + methodCheck = Class.forName("co.aikar.timings.TimingsManager").getDeclaredMethod("recheckEnabled"); + methodCheck.setAccessible(true); + } catch (Throwable ignore){} + } + + @Override + public void startSet(boolean parallel) { + ChunkListener.physicsFreeze = true; + if (parallel) { + try { + if (fieldAsyncCatcherEnabled != null) { + fieldAsyncCatcherEnabled.set(null, false); + } + if (fieldTimingsEnabled != null) { + timingsEnabled = (boolean) fieldTimingsEnabled.get(null); + if (timingsEnabled) { + if (alertTimingsChange) { + alertTimingsChange = false; + Fawe.debug("Having `parallel-threads` > 1 interferes with the timings."); + } + fieldTimingsEnabled.set(null, false); + methodCheck.invoke(null); + } + } + } catch (Throwable e) { + e.printStackTrace(); + } + } + } + + @Override + public void endSet(boolean parallel) { + ChunkListener.physicsFreeze = false; + if (parallel) { + try { + if (fieldAsyncCatcherEnabled != null) { + fieldAsyncCatcherEnabled.set(null, true); + } + if (fieldTimingsEnabled != null && timingsEnabled) { + fieldTimingsEnabled.set(null, true); + methodCheck.invoke(null); + } + } catch (Throwable e) { + e.printStackTrace(); + } + } + } + + @Override + public void sendBlockUpdate(final FaweChunk chunk, FawePlayer... players) { + if (players.length == 0) { + return; + } + int cx = chunk.getX(); + int cz = chunk.getZ(); + int view = Bukkit.getServer().getViewDistance(); + boolean sendAny = false; + boolean[] send = new boolean[players.length]; + for (int i = 0; i < players.length; i++) { + FawePlayer player = players[i]; + Player bp = ((BukkitPlayer) player).parent; + Location loc = bp.getLocation(); + if (Math.abs((loc.getBlockX() >> 4) - cx) <= view && Math.abs((loc.getBlockZ() >> 4) - cz) <= view) { + sendAny = true; + send[i] = true; + } + } + if (!sendAny) { + return; + } + final World world = getWorld(); + final int bx = cx << 4; + final int bz = cz << 4; + chunk.forEachQueuedBlock(new FaweChunkVisitor() { + @Override + public void run(int localX, int y, int localZ, int combined) { + Location loc = new Location(world, bx + localX, y, bz + localZ); + for (int i = 0; i < players.length; i++) { + if (send[i]) { + ((BukkitPlayer) players[i]).parent.sendBlockChange(loc, BukkitAdapter.adapt(BlockState.get(combined))); + } + } + } + }); + } +} diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/v0/BukkitQueue_All.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/v0/BukkitQueue_All.java new file mode 100644 index 000000000..850bb695c --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/v0/BukkitQueue_All.java @@ -0,0 +1,406 @@ +package com.boydti.fawe.bukkit.v0; + +import com.boydti.fawe.FaweCache; +import com.boydti.fawe.bukkit.util.BukkitReflectionUtils; +import com.boydti.fawe.config.Settings; +import com.boydti.fawe.example.NullRelighter; +import com.boydti.fawe.example.Relighter; +import com.boydti.fawe.object.FaweChunk; +import com.boydti.fawe.object.RegionWrapper; +import com.boydti.fawe.object.RunnableVal; +import com.boydti.fawe.util.MathMan; +import com.boydti.fawe.util.ReflectionUtils; +import com.boydti.fawe.util.SetQueue; +import com.boydti.fawe.util.TaskManager; +import com.google.common.collect.MapMaker; +import com.sk89q.jnbt.CompoundTag; +import java.io.File; +import java.io.RandomAccessFile; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayDeque; +import java.util.Map; +import java.util.concurrent.ConcurrentMap; + +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.world.block.BlockStateHolder; +import org.bukkit.Chunk; +import org.bukkit.ChunkSnapshot; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.block.Biome; +import org.bukkit.block.BlockState; +import org.bukkit.block.data.BlockData; + +public class BukkitQueue_All extends BukkitQueue_0 { + + public static int ALLOCATE; + private ConcurrentMap chunkCache = new MapMaker() + .weakValues() + .makeMap(); + + public BukkitQueue_All(com.sk89q.worldedit.world.World world) { + super(world); + if (Settings.IMP.QUEUE.EXTRA_TIME_MS != Integer.MIN_VALUE) { + ALLOCATE = Settings.IMP.QUEUE.EXTRA_TIME_MS; + Settings.IMP.QUEUE.EXTRA_TIME_MS = Integer.MIN_VALUE; + Settings.IMP.QUEUE.PARALLEL_THREADS = 1; + } + } + + public BukkitQueue_All(String world) { + super(world); + if (Settings.IMP.QUEUE.EXTRA_TIME_MS != Integer.MIN_VALUE) { + ALLOCATE = Settings.IMP.QUEUE.EXTRA_TIME_MS; + Settings.IMP.QUEUE.EXTRA_TIME_MS = Integer.MIN_VALUE; + Settings.IMP.QUEUE.PARALLEL_THREADS = 1; + } + } + + @Override + public boolean queueChunkLoad(int cx, int cz, RunnableVal operation) { + if (PAPER) { + try { + new PaperChunkCallback(getImpWorld(), cx, cz) { + @Override + public void onLoad(Chunk chunk) { + try { + ChunkSnapshot snapshot = chunk.getChunkSnapshot(); + operation.run(snapshot); + } catch (Throwable e) { + PAPER = false; + } + } + }; + return true; + } catch (Throwable ignore) { + PAPER = false; + } + } + return super.queueChunkLoad(cx, cz); + } + + @Override + public Relighter getRelighter() { + return NullRelighter.INSTANCE; + } + + private static Class classRegionFileCache; + private static Class classRegionFile; + private static Class classCraftChunk; + private static Class classCraftWorld; + private static Class classNMSChunk; + private static Class classNMSWorld; + private static Class classChunkProviderServer; + private static Class classIChunkLoader; + private static Class classChunkRegionLoader; + private static Class classIChunkProvider; + private static Method methodGetHandleChunk; + private static Method methodGetHandleWorld; + private static Method methodFlush; + private static Method methodNeedsSaving; + private static Field fieldChunkProvider; + private static Field fieldChunkLoader; + private static Field fieldRegionMap; + private static Field fieldRegionRAF; + + static { + try { + BukkitReflectionUtils.init(); + classRegionFileCache = BukkitReflectionUtils.getNmsClass("RegionFileCache"); + classRegionFile = BukkitReflectionUtils.getNmsClass("RegionFile"); + classCraftChunk = BukkitReflectionUtils.getCbClass("CraftChunk"); + classNMSChunk = BukkitReflectionUtils.getNmsClass("Chunk"); + classCraftWorld = BukkitReflectionUtils.getCbClass("CraftWorld"); + classNMSWorld = BukkitReflectionUtils.getNmsClass("World"); + classChunkProviderServer = BukkitReflectionUtils.getNmsClass("ChunkProviderServer"); + classIChunkProvider = BukkitReflectionUtils.getNmsClass("IChunkProvider"); + classIChunkLoader = BukkitReflectionUtils.getNmsClass("IChunkLoader"); + classChunkRegionLoader = BukkitReflectionUtils.getNmsClass("ChunkRegionLoader"); + + methodGetHandleChunk = ReflectionUtils.setAccessible(classCraftChunk.getDeclaredMethod("getHandle")); + methodGetHandleWorld = ReflectionUtils.setAccessible(classCraftWorld.getDeclaredMethod("getHandle")); + methodFlush = ReflectionUtils.findMethod(classChunkRegionLoader, boolean.class); + methodNeedsSaving = ReflectionUtils.findMethod(classNMSChunk, boolean.class, boolean.class); + + fieldChunkProvider = ReflectionUtils.findField(classNMSWorld, classIChunkProvider); + fieldChunkLoader = ReflectionUtils.findField(classChunkProviderServer, classIChunkLoader); + + fieldRegionMap = ReflectionUtils.findField(classRegionFileCache, Map.class); + fieldRegionRAF = ReflectionUtils.findField(classRegionFile, RandomAccessFile.class); + } catch (Throwable ignore) { + ignore.printStackTrace(); + } + } + + @Override + public boolean setMCA(int mcaX, int mcaZ, RegionWrapper allowed, Runnable whileLocked, boolean saveChunks, boolean load) { + if (classRegionFileCache == null) { + return super.setMCA(mcaX, mcaZ, allowed, whileLocked, saveChunks, load); + } + TaskManager.IMP.sync(new RunnableVal() { + @Override + public void run(Boolean value) { + long start = System.currentTimeMillis(); + long last = start; + synchronized (classRegionFileCache) { + try { + World world = getWorld(); + boolean autoSave = world.isAutoSave(); + + if (world.getKeepSpawnInMemory()) world.setKeepSpawnInMemory(false); + + ArrayDeque unloaded = null; + if (load) { + int bcx = mcaX << 5; + int bcz = mcaZ << 5; + int tcx = bcx + 31; + int tcz = bcz + 31; + for (Chunk chunk : world.getLoadedChunks()) { + int cx = chunk.getX(); + int cz = chunk.getZ(); + if (cx >= bcx && cx <= tcx && cz >= bcz && cz <= tcz) { + Object nmsChunk = methodGetHandleChunk.invoke(chunk); + boolean mustSave = saveChunks && (boolean) methodNeedsSaving.invoke(nmsChunk, false); + chunk.unload(mustSave, false); + if (unloaded == null) unloaded = new ArrayDeque(); + unloaded.add(chunk); + } + } + } else { + world.save(); + } + + Object nmsWorld = methodGetHandleWorld.invoke(world); + Object chunkProviderServer = fieldChunkProvider.get(nmsWorld); + Object chunkRegionLoader = fieldChunkLoader.get(chunkProviderServer); + while ((boolean) methodFlush.invoke(chunkRegionLoader)); + + if (unloaded != null) { + Map regionMap = (Map) fieldRegionMap.get(null); + File file = new File(world.getWorldFolder(), "region" + File.separator + "r." + mcaX + "." + mcaZ + ".mca"); + Object regionFile = regionMap.remove(file); + if (regionFile != null) { + RandomAccessFile raf = (RandomAccessFile) fieldRegionRAF.get(regionFile); + raf.close(); + } + } + + whileLocked.run(); + + if (load && unloaded != null) { + final ArrayDeque finalUnloaded = unloaded; + TaskManager.IMP.async(new Runnable() { + @Override + public void run() { + for (Chunk chunk : finalUnloaded) { + int cx = chunk.getX(); + int cz = chunk.getZ(); + if (world.isChunkLoaded(cx, cz)) continue; + SetQueue.IMP.addTask(() -> { + world.loadChunk(chunk.getX(), chunk.getZ(), false); + world.refreshChunk(chunk.getX(), chunk.getZ()); + }); + + } + } + }); + // load chunks + + } + } catch (Throwable e) { + e.printStackTrace(); + } + } + } + }); + return true; + } + + @Override + public void setHeightMap(FaweChunk chunk, byte[] heightMap) { + // Not supported + } + + @Override + public void setSkyLight(ChunkSnapshot chunk, int x, int y, int z, int value) { + // Not supported + } + + @Override + public void setBlockLight(ChunkSnapshot chunk, int x, int y, int z, int value) { + // Not supported + } + + @Override + public int getCombinedId4Data(ChunkSnapshot chunk, int x, int y, int z) { + if (chunk.isSectionEmpty(y >> 4)) { + return 0; + } + BlockData blockData = chunk.getBlockData(x & 15, y, z & 15); + return BukkitAdapter.adapt(blockData).getInternalId(); + } + + @Override + public int getBiome(ChunkSnapshot chunkSnapshot, int x, int z) { + Biome biome = chunkSnapshot.getBiome(x & 15, z & 15); + return getAdapter().getBiomeId(biome); + } + + @Override + public ChunkSnapshot getSections(ChunkSnapshot chunkSnapshot) { + return chunkSnapshot; + } + + @Override + public ChunkSnapshot getCachedChunk(World world, int cx, int cz) { + long pair = MathMan.pairInt(cx, cz); + ChunkSnapshot cached = chunkCache.get(pair); + if (cached != null) return cached; + if (world.isChunkLoaded(cx, cz)) { + Long originalKeep = keepLoaded.get(pair); + keepLoaded.put(pair, Long.MAX_VALUE); + if (world.isChunkLoaded(cx, cz)) { + Chunk chunk = world.getChunkAt(cx, cz); + ChunkSnapshot snapshot = getAndCacheChunk(chunk); + if (originalKeep != null) { + keepLoaded.put(pair, originalKeep); + } else { + keepLoaded.remove(pair); + } + return snapshot; + } else { + keepLoaded.remove(pair); + return null; + } + } else { + return null; + } + } + + @Override + public int getEmmittedLight(final ChunkSnapshot chunk, int x, int y, int z) { + return chunk.getBlockEmittedLight(x & 15, y, z & 15); + } + + @Override + public int getSkyLight(final ChunkSnapshot chunk, int x, int y, int z) { + return chunk.getBlockSkyLight(x & 15, y, z & 15); + } + + @Override + public int getLight(final ChunkSnapshot chunk, int x, int y, int z) { + x = x & 15; + z = z & 15; + return Math.max(chunk.getBlockEmittedLight(x, y, z), chunk.getBlockSkyLight(x, y, z)); + } + + @Override + public ChunkSnapshot loadChunk(World world, int x, int z, boolean generate) { + Chunk chunk = world.getChunkAt(x, z); + chunk.load(generate); + return chunk.isLoaded() ? getAndCacheChunk(chunk) : null; + } + + private ChunkSnapshot getAndCacheChunk(Chunk chunk) { + ChunkSnapshot snapshot = chunk.getChunkSnapshot(false, true, false); + chunkCache.put(MathMan.pairInt(chunk.getX(), chunk.getZ()), snapshot); + return snapshot; + } + + @Override + public ChunkSnapshot getCachedSections(World impWorld, int cx, int cz) { + return getCachedChunk(impWorld, cx, cz); + } + + @Override + public CompoundTag getTileEntity(ChunkSnapshot chunk, int x, int y, int z) { + if (getAdapter() == null) { + return null; + } + Location loc = new Location(getWorld(), x, y, z); + BlockStateHolder block = getAdapter().getBlock(loc); + return block.getNbtData(); + } + + @Override + public FaweChunk getFaweChunk(int x, int z) { + return new BukkitChunk_All(this, x, z); + } + + @Override + public boolean supports(Capability capability) { + switch (capability) { + case CHANGE_TASKS: return getAdapter() != null; + } + return super.supports(capability); + } + + private int skip; + + @Override + public void startSet(boolean parallel) { + super.startSet(true); + } + + private Field fieldNeighbors; + private Method chunkGetHandle; + + /** + * Exploiting a bug in the vanilla lighting algorithm for faster block placement + * - Could have been achieved without reflection by force unloading specific chunks + * - Much faster just setting the variable manually though + * @param chunk + * @return + */ + protected Object[] disableLighting(Chunk chunk) { + try { + if (chunkGetHandle == null) { + chunkGetHandle = chunk.getClass().getDeclaredMethod("getHandle"); + chunkGetHandle.setAccessible(true); + } + Object nmsChunk = chunkGetHandle.invoke(chunk); + if (fieldNeighbors == null) { + fieldNeighbors = nmsChunk.getClass().getDeclaredField("neighbors"); + fieldNeighbors.setAccessible(true); + } + Object value = fieldNeighbors.get(nmsChunk); + fieldNeighbors.set(nmsChunk, 0); + return new Object[] {nmsChunk, value}; + } catch (Throwable ignore) {} + return null; + } + + protected void disableLighting(Object[] disableResult) { + if (disableResult != null) { + try { + fieldNeighbors.set(disableResult[0], 0); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + } + + protected void resetLighting(Object[] disableResult) { + if (disableResult != null) { + try { + fieldNeighbors.set(disableResult[0], disableResult[1]); + } catch (Throwable ignore) { + ignore.printStackTrace(); + } + } + } + + protected void enableLighting(Object[] disableResult) { + if (disableResult != null) { + try { + fieldNeighbors.set(disableResult[0], 0x739C0); + } catch (Throwable ignore) {} + } + } + + @Override + public void endSet(boolean parallel) { + super.endSet(true); + } +} diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/v0/ChunkListener.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/v0/ChunkListener.java new file mode 100644 index 000000000..7fbce4b44 --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/v0/ChunkListener.java @@ -0,0 +1,378 @@ +package com.boydti.fawe.bukkit.v0; + +import com.boydti.fawe.Fawe; +import com.boydti.fawe.bukkit.FaweBukkit; +import com.boydti.fawe.config.Settings; +import com.boydti.fawe.util.FaweTimer; +import com.boydti.fawe.util.MathMan; +import com.boydti.fawe.util.TaskManager; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import org.bukkit.Bukkit; +import org.bukkit.Chunk; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockBurnEvent; +import org.bukkit.event.block.BlockCanBuildEvent; +import org.bukkit.event.block.BlockDamageEvent; +import org.bukkit.event.block.BlockDispenseEvent; +import org.bukkit.event.block.BlockExpEvent; +import org.bukkit.event.block.BlockExplodeEvent; +import org.bukkit.event.block.BlockFadeEvent; +import org.bukkit.event.block.BlockFromToEvent; +import org.bukkit.event.block.BlockGrowEvent; +import org.bukkit.event.block.BlockIgniteEvent; +import org.bukkit.event.block.BlockPhysicsEvent; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.block.BlockRedstoneEvent; +import org.bukkit.event.block.LeavesDecayEvent; +import org.bukkit.event.block.NotePlayEvent; +import org.bukkit.event.block.SignChangeEvent; +import org.bukkit.event.entity.EntityChangeBlockEvent; +import org.bukkit.event.entity.ItemSpawnEvent; +import org.bukkit.event.inventory.FurnaceBurnEvent; +import org.bukkit.event.inventory.FurnaceSmeltEvent; +import org.bukkit.event.world.ChunkLoadEvent; +import org.bukkit.util.Vector; + +public abstract class ChunkListener implements Listener { + + protected int rateLimit = 0; + private int[] badLimit = new int[]{Settings.IMP.TICK_LIMITER.PHYSICS_MS, Settings.IMP.TICK_LIMITER.FALLING, Settings.IMP.TICK_LIMITER.ITEMS}; + + public ChunkListener() { + if (Settings.IMP.TICK_LIMITER.ENABLED) { + Bukkit.getPluginManager().registerEvents(ChunkListener.this, Fawe.imp().getPlugin()); + TaskManager.IMP.repeat(new Runnable() { + @Override + public void run() { + rateLimit--; + physicsFreeze = false; + itemFreeze = false; + lastZ = Integer.MIN_VALUE; + physSkip = 0; + physCancelPair = Long.MIN_VALUE; + physCancel = false; + + counter.clear(); + for (Long2ObjectMap.Entry entry : badChunks.long2ObjectEntrySet()) { + long key = entry.getLongKey(); + int x = MathMan.unpairIntX(key); + int z = MathMan.unpairIntY(key); + counter.put(key, badLimit); + } + badChunks.clear(); + } + }, Settings.IMP.TICK_LIMITER.INTERVAL); + } + } + + protected abstract int getDepth(Exception ex); + protected abstract StackTraceElement getElement(Exception ex, int index); + + public static boolean physicsFreeze = false; + public static boolean itemFreeze = false; + + protected final Long2ObjectOpenHashMap badChunks = new Long2ObjectOpenHashMap<>(); + private Long2ObjectOpenHashMap counter = new Long2ObjectOpenHashMap<>(); + private int lastX = Integer.MIN_VALUE, lastZ = Integer.MIN_VALUE; + private int[] lastCount; + + public int[] getCount(int cx, int cz) { + if (lastX == cx && lastZ == cz) { + return lastCount; + } + lastX = cx; + lastZ = cz; + long pair = MathMan.pairInt(cx, cz); + int[] tmp = lastCount = counter.get(pair); + if (tmp == null) { + lastCount = tmp = new int[3]; + counter.put(pair, tmp); + } + return tmp; + } + + public void cleanup(Chunk chunk) { + for (Entity entity : chunk.getEntities()) { + if (entity.getType() == EntityType.DROPPED_ITEM) { + entity.remove(); + } + } + + } + + protected int physSkip; + protected boolean physCancel; + protected long physCancelPair; + + protected long physStart; + protected long physTick; + + private void reset() { + physSkip = 0; + physStart = System.currentTimeMillis(); + physCancel = false; + } + + @EventHandler(priority = EventPriority.LOWEST) + public void event(BlockBurnEvent event) { reset(); } + + @EventHandler(priority = EventPriority.LOWEST) + public void event(BlockCanBuildEvent event) { reset(); } + + @EventHandler(priority = EventPriority.LOWEST) + public void event(BlockDamageEvent event) { reset(); } + + @EventHandler(priority = EventPriority.LOWEST) + public void event(BlockDispenseEvent event) { reset(); } + + @EventHandler(priority = EventPriority.LOWEST) + public void event(BlockExpEvent event) { reset(); } + + @EventHandler(priority = EventPriority.LOWEST) + public void event(BlockExplodeEvent event) { reset(); } + + @EventHandler(priority = EventPriority.LOWEST) + public void event(BlockFadeEvent event) { reset(); } + + @EventHandler(priority = EventPriority.LOWEST) + public void event(BlockFromToEvent event) { reset(); } + + @EventHandler(priority = EventPriority.LOWEST) + public void event(BlockGrowEvent event) { reset(); } + + @EventHandler(priority = EventPriority.LOWEST) + public void event(BlockIgniteEvent event) { reset(); } + + @EventHandler(priority = EventPriority.LOWEST) + public void event(BlockPlaceEvent event) { reset(); } + +// @EventHandler(priority = EventPriority.LOWEST) +// public void event(BrewEvent event) { reset(); } +// +// @EventHandler(priority = EventPriority.LOWEST) +// public void event(BrewingStandFuelEvent event) { reset(); } +// +// @EventHandler(priority = EventPriority.LOWEST) +// public void event(CauldronLevelChangeEvent event ) { reset(); } + + @EventHandler(priority = EventPriority.LOWEST) + public void event(FurnaceBurnEvent event) { reset(); } + + @EventHandler(priority = EventPriority.LOWEST) + public void event(FurnaceSmeltEvent event) { reset(); } + + @EventHandler(priority = EventPriority.LOWEST) + public void event(LeavesDecayEvent event) { reset(); } + + @EventHandler(priority = EventPriority.LOWEST) + public void event(NotePlayEvent event) { reset(); } + + @EventHandler(priority = EventPriority.LOWEST) + public void event(SignChangeEvent event) { reset(); } + + @EventHandler(priority = EventPriority.LOWEST) + public void event(BlockRedstoneEvent event) { reset(); } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onPhysics(BlockPhysicsEvent event) { + if (physCancel) { + Block block = event.getBlock(); + long pair = MathMan.pairInt(block.getX() >> 4, block.getZ() >> 4); + if (physCancelPair == pair) { + event.setCancelled(true); + return; + } + if (badChunks.containsKey(pair)) { + physCancelPair = pair; + event.setCancelled(true); + return; + } + } else { + if ((++physSkip & 1023) != 0) return; + FaweTimer timer = Fawe.get().getTimer(); + if (timer.getTick() != physTick) { + physTick = timer.getTick(); + physStart = System.currentTimeMillis(); + return; + } else if (System.currentTimeMillis() - physStart < Settings.IMP.TICK_LIMITER.PHYSICS_MS) { + return; + } + } + if (physicsFreeze) { + event.setCancelled(true); + return; + } + switch (event.getChangedType()) { + case AIR: + case VOID_AIR: + case CAVE_AIR: + return; + } + Exception e = new Exception(); + int depth = getDepth(e); + if (depth >= 256) { + if (containsSetAir(e, event)) { + Block block = event.getBlock(); + int cx = block.getX() >> 4; + int cz = block.getZ() >> 4; + physCancelPair = MathMan.pairInt(cx, cz); + if (rateLimit <= 0) { + rateLimit = 20; + Fawe.debug("[FAWE `tick-limiter`] Detected and cancelled physics lag source at " + block.getLocation()); + } + cancelNearby(cx, cz); + event.setCancelled(true); + physCancel = true; + return; + } + } + physSkip = 1; + physCancel = false; + } + + protected boolean containsSetAir(Exception e, BlockPhysicsEvent event) { + for (int frame = 25; frame < 35; frame++) { + StackTraceElement elem = getElement(e, frame); + if (elem != null) { + String methodName = elem.getMethodName(); + // setAir | setTypeAndData (hacky, but this needs to be efficient) + if (methodName.charAt(0) == 's' && methodName.length() == 6 || methodName.length() == 14) { + return true; + } + } + } + return false; + } + + protected void cancelNearby(int cx, int cz) { + cancel(cx, cz); + cancel(cx + 1, cz); + cancel(cx - 1, cz); + cancel(cx, cz + 1); + cancel(cx, cz - 1); + cancel(cx - 1, cz - 1); + cancel(cx - 1, cz + 1); + cancel(cx + 1, cz - 1); + cancel(cx + 1, cz + 1); + } + + private void cancel(int cx, int cz) { + long key = MathMan.pairInt(cx, cz); + badChunks.put(key, (Boolean) true); + counter.put(key, badLimit); + int[] count = getCount(cx, cz); + count[0] = Integer.MAX_VALUE; + count[1] = Integer.MAX_VALUE; + count[2] = Integer.MAX_VALUE; + + } + + // Falling + @EventHandler(priority = EventPriority.LOWEST) + public void onBlockChange(EntityChangeBlockEvent event) { + if (physicsFreeze) { + event.setCancelled(true); + return; + } + Block block = event.getBlock(); + int x = block.getX(); + int z = block.getZ(); + int cx = x >> 4; + int cz = z >> 4; + int[] count = getCount(cx, cz); + if (count[1] >= Settings.IMP.TICK_LIMITER.FALLING) { + event.setCancelled(true); + return; + } + if (event.getEntityType() == EntityType.FALLING_BLOCK) { + if (++count[1] >= Settings.IMP.TICK_LIMITER.FALLING) { + + // Only cancel falling blocks when it's lagging + if (Fawe.get().getTimer().getTPS() < 18) { + cancelNearby(cx, cz); + if (rateLimit <= 0) { + rateLimit = 20; + Fawe.debug("[FAWE `tick-limiter`] Detected and cancelled falling block lag source at " + block.getLocation()); + } + event.setCancelled(true); + return; + } else { + count[1] = 0; + } + } + } + } + + /** + * Prevent FireWorks from loading chunks + * @param event + */ + @EventHandler(priority = EventPriority.LOWEST) + public void onChunkLoad(ChunkLoadEvent event) { + if (!Settings.IMP.TICK_LIMITER.FIREWORKS_LOAD_CHUNKS) { + Chunk chunk = event.getChunk(); + Entity[] entities = chunk.getEntities(); + World world = chunk.getWorld(); + + Exception e = new Exception(); + int start = 14; + int end = 22; + int depth = Math.min(end, getDepth(e)); + + for (int frame = start; frame < depth; frame++) { + StackTraceElement elem = getElement(e, frame); + if (elem == null) return; + String className = elem.getClassName(); + int len = className.length(); + if (className != null) { + if (len > 15 && className.charAt(len - 15) == 'E' && className.endsWith("EntityFireworks")) { + for (Entity ent : world.getEntities()) { + if (ent.getType() == EntityType.FIREWORK) { + Vector velocity = ent.getVelocity(); + double vertical = Math.abs(velocity.getY()); + if (Math.abs(velocity.getX()) > vertical || Math.abs(velocity.getZ()) > vertical) { + Fawe.debug("[FAWE `tick-limiter`] Detected and cancelled rogue FireWork at " + ent.getLocation()); + ent.remove(); + } + } + } + } + } + } + } + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onItemSpawn(ItemSpawnEvent event) { + if (physicsFreeze) { + event.setCancelled(true); + return; + } + Location loc = event.getLocation(); + int cx = loc.getBlockX() >> 4; + int cz = loc.getBlockZ() >> 4; + int[] count = getCount(cx, cz); + if (count[2] >= Settings.IMP.TICK_LIMITER.ITEMS) { + event.setCancelled(true); + return; + } + if (++count[2] >= Settings.IMP.TICK_LIMITER.ITEMS) { + cleanup(loc.getChunk()); + cancelNearby(cx, cz); + if (rateLimit <= 0) { + rateLimit = 20; + Fawe.debug("[FAWE `tick-limiter`] Detected and cancelled item lag source at " + loc); + } + event.setCancelled(true); + return; + } + } +} \ No newline at end of file diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/v0/ChunkListener_8.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/v0/ChunkListener_8.java new file mode 100644 index 000000000..528fd42f8 --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/v0/ChunkListener_8.java @@ -0,0 +1,16 @@ +package com.boydti.fawe.bukkit.v0; + +import sun.misc.SharedSecrets; + +public class ChunkListener_8 extends ChunkListener { + + @Override + protected int getDepth(Exception ex) { + return SharedSecrets.getJavaLangAccess().getStackTraceDepth(ex); + } + + @Override + protected StackTraceElement getElement(Exception ex, int index) { + return SharedSecrets.getJavaLangAccess().getStackTraceElement(ex, index); + } +} \ No newline at end of file diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/v0/ChunkListener_9.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/v0/ChunkListener_9.java new file mode 100644 index 000000000..7c84f1924 --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/v0/ChunkListener_9.java @@ -0,0 +1,91 @@ +package com.boydti.fawe.bukkit.v0; + +import com.boydti.fawe.Fawe; +import com.boydti.fawe.config.Settings; +import com.boydti.fawe.util.FaweTimer; +import com.boydti.fawe.util.MathMan; +import org.bukkit.block.Block; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockPhysicsEvent; + +// +public class ChunkListener_9 extends ChunkListener { + + private Exception exception; + private StackTraceElement[] elements; + + public ChunkListener_9() { + super(); + } + + @EventHandler(priority = EventPriority.LOWEST) + @Override + public void onPhysics(BlockPhysicsEvent event) { + if (physicsFreeze) { + event.setCancelled(true); + return; + } + if (physCancel) { + Block block = event.getBlock(); + long pair = MathMan.pairInt(block.getX() >> 4, block.getZ() >> 4); + if (physCancelPair == pair) { + event.setCancelled(true); + return; + } + if (badChunks.containsKey(pair)) { + physCancelPair = pair; + event.setCancelled(true); + return; + } + if (System.currentTimeMillis() - physStart > Settings.IMP.TICK_LIMITER.PHYSICS_MS) { + physCancelPair = pair; + event.setCancelled(true); + return; + } + } + FaweTimer timer = Fawe.get().getTimer(); + if (timer.getTick() != physTick) { + physTick = timer.getTick(); + physStart = System.currentTimeMillis(); + physSkip = 0; + physCancel = false; + return; + } + if ((++physSkip & 1023) == 0) { + if (System.currentTimeMillis() - physStart > Settings.IMP.TICK_LIMITER.PHYSICS_MS) { + Block block = event.getBlock(); + int cx = block.getX() >> 4; + int cz = block.getZ() >> 4; + physCancelPair = MathMan.pairInt(cx, cz); + if (rateLimit <= 0) { + rateLimit = 20; + Fawe.debug("[FAWE `tick-limiter`] Detected and cancelled physics lag source at " + block.getLocation()); + } + cancelNearby(cx, cz); + event.setCancelled(true); + physCancel = true; + return; + } + } + } + + private StackTraceElement[] getElements(Exception ex) { + if (elements == null || ex != exception) { + exception = ex; + elements = ex.getStackTrace(); + } + return elements; + } + + @Override + protected int getDepth(Exception ex) { + return getElements(ex).length; + } + + @Override + protected StackTraceElement getElement(Exception ex, int i) { + StackTraceElement[] elems = getElements(ex); + return elems.length > i ? elems[i] : null; + } +} \ No newline at end of file diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/v0/FaweAdapter_All.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/v0/FaweAdapter_All.java new file mode 100644 index 000000000..781cfe9b8 --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/v0/FaweAdapter_All.java @@ -0,0 +1,453 @@ +//package com.boydti.fawe.bukkit.v0; +// +//import com.boydti.fawe.FaweCache; +//import com.boydti.fawe.bukkit.util.BukkitReflectionUtils; +//import com.boydti.fawe.util.ReflectionUtils; +//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.adapter.BukkitImplAdapter; +//import com.sk89q.worldedit.entity.BaseEntity; +//import com.sk89q.worldedit.internal.Constants; +//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.ArrayList; +//import java.util.Arrays; +//import java.util.HashMap; +//import java.util.List; +//import java.util.Map; +//import java.util.concurrent.ConcurrentHashMap; +//import javax.annotation.Nullable; +//import org.bukkit.Location; +//import org.bukkit.Material; +//import org.bukkit.World; +//import org.bukkit.block.Biome; +//import org.bukkit.entity.Entity; +//import org.bukkit.event.entity.CreatureSpawnEvent; +// +//public class FaweAdapter_All implements BukkitImplAdapter { +// +// private final Class classCraftBlock; +// private final Method biomeToBiomeBase; +// private final Class classBiomeBase; +// private final Method biomeBaseToTypeId; +// private final Method getBiome; +// private final Method biomeBaseToBiome; +// private final Class classCraftWorld; +// private final Method getHandleWorld; +// private final Class classWorld; +// private final Method getTileEntity1; +// private final Method getTileEntity2; +// private final Class classNBTTagCompound; +// private final Constructor newNBTTagCompound; +// private final Class classTileEntity; +// private final Class classCraftEntity; +// private final Method getHandleEntity; +// private final Class classNBTTagInt; +// private final Class classNBTBase; +// private final Constructor newNBTTagInt; +// private final Method setNBTTagCompound; +// private Class classEntity; +// private Method getBukkitEntity; +// private Method addEntity; +// private Method setLocation; +// private Class classEntityTypes; +// private Method getEntityId; +// private Method createEntityFromId; +// private Method readTagIntoEntity; +// private Method readEntityIntoTag; +// private Constructor newMinecraftKey; +// private Class classMinecraftKey; +// private Method readTagIntoTileEntity; +// private Method readTileEntityIntoTag; +// private Class classBlockPosition; +// private Constructor newBlockPosition; +// +// private Map, NMSTagConstructor> WEToNMS = new ConcurrentHashMap<>(); +// private Map NMSToWE = new ConcurrentHashMap<>(); +// private Map, Integer> TagToId = new ConcurrentHashMap<>(); +// +// public FaweAdapter_All() throws Throwable { +// BukkitReflectionUtils.init(); +// classCraftWorld = BukkitReflectionUtils.getCbClass("CraftWorld"); +// classCraftBlock = BukkitReflectionUtils.getCbClass("block.CraftBlock"); +// classCraftEntity = BukkitReflectionUtils.getCbClass("entity.CraftEntity"); +// classBiomeBase = BukkitReflectionUtils.getNmsClass("BiomeBase"); +// classWorld = BukkitReflectionUtils.getNmsClass("World"); +// classTileEntity = BukkitReflectionUtils.getNmsClass("TileEntity"); +// +// biomeToBiomeBase = ReflectionUtils.setAccessible(classCraftBlock.getDeclaredMethod("biomeToBiomeBase", Biome.class)); +// biomeBaseToBiome = ReflectionUtils.setAccessible(classCraftBlock.getDeclaredMethod("biomeBaseToBiome", classBiomeBase)); +// getBiome = ReflectionUtils.setAccessible(classBiomeBase.getDeclaredMethod("getBiome", int.class)); +// biomeBaseToTypeId = ReflectionUtils.findMethod(classBiomeBase, int.class, classBiomeBase); +// getHandleWorld = ReflectionUtils.setAccessible(classCraftWorld.getDeclaredMethod("getHandle")); +// getHandleEntity = ReflectionUtils.setAccessible(classCraftEntity.getDeclaredMethod("getHandle")); +// try { +// classBlockPosition = BukkitReflectionUtils.getNmsClass("BlockPosition"); +// } catch (Throwable ignore) { +// } +// if (classBlockPosition != null) { +// getTileEntity1 = classWorld.getDeclaredMethod("getTileEntity", classBlockPosition); +// getTileEntity2 = null; +// newBlockPosition = ReflectionUtils.setAccessible(classBlockPosition.getConstructor(int.class, int.class, int.class)); +// } else { +// getTileEntity1 = null; +// getTileEntity2 = ReflectionUtils.setAccessible(classWorld.getDeclaredMethod("getTileEntity", int.class, int.class, int.class)); +// } +// +// classNBTTagCompound = BukkitReflectionUtils.getNmsClass("NBTTagCompound"); +// classNBTBase = BukkitReflectionUtils.getNmsClass("NBTBase"); +// classNBTTagInt = BukkitReflectionUtils.getNmsClass("NBTTagInt"); +// newNBTTagInt = ReflectionUtils.setAccessible(classNBTTagInt.getConstructor(int.class)); +// setNBTTagCompound = ReflectionUtils.setAccessible(classNBTTagCompound.getDeclaredMethod("set", String.class, classNBTBase)); +// newNBTTagCompound = ReflectionUtils.setAccessible(classNBTTagCompound.getConstructor()); +// try { +// readTileEntityIntoTag = ReflectionUtils.setAccessible(classTileEntity.getDeclaredMethod("save", classNBTTagCompound)); +// } catch (Throwable ignore) { +// readTileEntityIntoTag = ReflectionUtils.findMethod(classTileEntity, classNBTTagCompound, classNBTTagCompound); +// if (readTileEntityIntoTag == null) { +// readTileEntityIntoTag = ReflectionUtils.findMethod(classTileEntity, 1, Void.TYPE, classNBTTagCompound); +// } +// } +// +// +// try { +// readTagIntoTileEntity = ReflectionUtils.setAccessible(classTileEntity.getDeclaredMethod("load", classNBTTagCompound)); +// } catch (Throwable ignore) { +// readTagIntoTileEntity = ReflectionUtils.findMethod(classTileEntity, 0, Void.TYPE, classNBTTagCompound); +// } +// +// +// List nmsClasses = Arrays.asList("NBTTagCompound", "NBTTagByte", "NBTTagByteArray", "NBTTagDouble", "NBTTagFloat", "NBTTagInt", "NBTTagIntArray", "NBTTagList", "NBTTagEnd", "NBTTagString", "NBTTagShort", "NBTTagLong"); +// List> weClasses = Arrays.asList(CompoundTag.class, ByteTag.class, ByteArrayTag.class, DoubleTag.class, FloatTag.class, IntTag.class, IntArrayTag.class, ListTag.class, EndTag.class, StringTag.class, ShortTag.class, LongTag.class); +// int[] ids = new int[]{10, 1, 7, 6, 5, 3, 11, 9, 0, 8, 2, 4}; +// +// int noMods = Modifier.STATIC; +// int hasMods = 0; +// for (int i = 0; i < nmsClasses.size(); i++) { +// Class nmsClass = BukkitReflectionUtils.getNmsClass(nmsClasses.get(i)); +// Class weClass = weClasses.get(i); +// TagToId.put(weClass, ids[i]); +// +// Constructor nmsConstructor = ReflectionUtils.setAccessible(nmsClass.getDeclaredConstructor()); +// +// if (weClass == EndTag.class) { +// NMSToWE.put(nmsClass, value -> new EndTag()); +// WEToNMS.put(weClass, value -> nmsConstructor.newInstance()); +// } else if (weClass == CompoundTag.class) { +// Field mapField = ReflectionUtils.findField(nmsClass, Map.class, hasMods, noMods); +// Constructor weConstructor = ReflectionUtils.setAccessible(CompoundTag.class.getConstructor(Map.class)); +// +// NMSToWE.put(nmsClass, value -> { +// Map map = (Map) mapField.get(value); +// Map weMap = new HashMap(); +// for (Map.Entry entry : map.entrySet()) { +// weMap.put(entry.getKey(), toNative(entry.getValue())); +// } +// return new CompoundTag(weMap); +// }); +// +// WEToNMS.put(weClass, value -> { +// Map map = ReflectionUtils.getMap(((CompoundTag) value).getValue()); +// Object nmsTag = nmsConstructor.newInstance(); +// Map nmsMap = (Map) mapField.get(nmsTag); +// for (Map.Entry entry : map.entrySet()) { +// nmsMap.put(entry.getKey(), fromNative(entry.getValue())); +// } +// return nmsTag; +// }); +// } else if (weClass == ListTag.class) { +// Field listField = ReflectionUtils.findField(nmsClass, List.class, hasMods, noMods); +// Field typeField = ReflectionUtils.findField(nmsClass, byte.class, hasMods, noMods); +// Constructor weConstructor = ReflectionUtils.setAccessible(ListTag.class.getConstructor(Class.class, List.class)); +// +// NMSToWE.put(nmsClass, tag -> { +// int type = ((Number) typeField.get(tag)).intValue(); +// List list = (List) listField.get(tag); +// +// Class weType = NBTConstants.getClassFromType(type); +// ArrayList weList = new ArrayList<>(); +// for (Object nmsTag : list) { +// weList.add(toNative(nmsTag)); +// } +// return new ListTag(weType, weList); +// }); +// WEToNMS.put(weClass, tag -> { +// ListTag lt = (ListTag) tag; +// List list = ReflectionUtils.getList(lt.getValue()); +// Class type = lt.getType(); +// +// int typeId = TagToId.get(type); +// Object nmsTagList = nmsConstructor.newInstance(); +// typeField.set(nmsTagList, (byte) typeId); +// ArrayList nmsList = (ArrayList) listField.get(nmsTagList); +// for (Tag weTag : list) { +// nmsList.add(fromNative(weTag)); +// } +// return nmsTagList; +// }); +// } else { +// Field typeField = ReflectionUtils.findField(nmsClass, null, hasMods, noMods); +// Constructor weConstructor = ReflectionUtils.setAccessible(weClass.getConstructor(typeField.getType())); +// +// NMSToWE.put(nmsClass, tag -> { +// Object value = typeField.get(tag); +// return weConstructor.newInstance(value); +// }); +// +// WEToNMS.put(weClass, tag -> { +// Object nmsTag = nmsConstructor.newInstance(); +// typeField.set(nmsTag, tag.getValue()); +// return nmsTag; +// }); +// } +// } +// try { +// classEntity = BukkitReflectionUtils.getNmsClass("Entity"); +// classEntityTypes = BukkitReflectionUtils.getNmsClass("EntityTypes"); +// +// getBukkitEntity = ReflectionUtils.setAccessible(classEntity.getDeclaredMethod("getBukkitEntity")); +// addEntity = ReflectionUtils.setAccessible(classWorld.getDeclaredMethod("addEntity", classEntity, CreatureSpawnEvent.SpawnReason.class)); +// setLocation = ReflectionUtils.setAccessible(classEntity.getDeclaredMethod("setLocation", double.class, double.class, double.class, float.class, float.class)); +// +// try { +// classMinecraftKey = BukkitReflectionUtils.getNmsClass("MinecraftKey"); +// newMinecraftKey = classMinecraftKey.getConstructor(String.class); +// } catch (Throwable ignore) { +// } +// if (classMinecraftKey != null) { +// getEntityId = ReflectionUtils.findMethod(classEntityTypes, classMinecraftKey, classEntity); +// createEntityFromId = ReflectionUtils.findMethod(classEntityTypes, classEntity, classMinecraftKey, classWorld); +// } else { +// getEntityId = ReflectionUtils.findMethod(classEntityTypes, String.class, classEntity); +// createEntityFromId = ReflectionUtils.findMethod(classEntityTypes, classEntity, String.class, classWorld); +// } +// +// noMods = Modifier.ABSTRACT | Modifier.PROTECTED | Modifier.PRIVATE; +// try { +// readEntityIntoTag = classEntity.getDeclaredMethod("save", classNBTTagCompound); +// } catch (Throwable ignore) { +// readEntityIntoTag = ReflectionUtils.findMethod(classEntity, classNBTTagCompound, classNBTTagCompound); +// if (readEntityIntoTag == null) { +// readEntityIntoTag = ReflectionUtils.findMethod(classEntity, 0, 0, noMods, Void.TYPE, classNBTTagCompound); +// } +// } +// ReflectionUtils.setAccessible(readEntityIntoTag); +// readTagIntoEntity = ReflectionUtils.findMethod(classEntity, 1, 0, noMods, Void.TYPE, classNBTTagCompound); +// if (readTagIntoEntity == null) { +// readTagIntoEntity = ReflectionUtils.findMethod(classEntity, 0, 0, noMods, Void.TYPE, classNBTTagCompound); +// } +// } catch (Throwable e) { +// e.printStackTrace(); +// classEntity = null; +// } +// } +// +// @Nullable +// @Override +// public BaseEntity getEntity(Entity entity) { +// try { +// if (classEntity == null) return null; +// Object nmsEntity = getHandleEntity.invoke(entity); +// +// String id = getEntityId(nmsEntity); +// +// if (id != null) { +// Object tag = newNBTTagCompound.newInstance(); +// readEntityIntoTag.invoke(nmsEntity, tag); +// return new BaseEntity(id, (CompoundTag) toNative(tag)); +// } +// } catch (Throwable e) { +// throw new RuntimeException(e); +// } +// return null; +// } +// +// private String getEntityId(Object entity) throws InvocationTargetException, IllegalAccessException { +// Object res = getEntityId.invoke(null, entity); +// return res == null ? null : res.toString(); +// } +// +// private Object createEntityFromId(String id, Object world) throws InvocationTargetException, IllegalAccessException, InstantiationException { +// if (classMinecraftKey != null) { +// Object key = newMinecraftKey.newInstance(id); +// return createEntityFromId.invoke(null, key, world); +// } else { +// return createEntityFromId.invoke(null, id, world); +// } +// } +// +// @Nullable +// @Override +// public Entity createEntity(Location location, BaseEntity state) { +// try { +// if (classEntity == null) return null; +// World world = location.getWorld(); +// Object nmsWorld = getHandleWorld.invoke(world); +// +// Object createdEntity = createEntityFromId(state.getTypeId(), nmsWorld); +// +// if (createdEntity != null) { +// CompoundTag nativeTag = state.getNbtData(); +// Map rawMap = ReflectionUtils.getMap(nativeTag.getValue()); +// for (String name : Constants.NO_COPY_ENTITY_NBT_FIELDS) { +// rawMap.remove(name); +// } +// if (nativeTag != null) { +// Object tag = fromNative(nativeTag); +// readTagIntoEntity.invoke(createdEntity, tag); +// } +// +// setLocation.invoke(createdEntity, location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); +// +// addEntity.invoke(nmsWorld, createdEntity, CreatureSpawnEvent.SpawnReason.CUSTOM); +// return (Entity) getBukkitEntity.invoke(createdEntity); +// } +// } catch (Throwable e) { +// throw new RuntimeException(e); +// } +// return null; +// } +// +// public Tag toNative(Object nmsTag) { +// try { +// return NMSToWE.get(nmsTag.getClass()).construct(nmsTag); +// } catch (Exception e) { +// throw new RuntimeException(e); +// } +// } +// +// public Object fromNative(Tag tag) { +// try { +// return WEToNMS.get(tag.getClass()).construct(tag); +// } catch (Exception e) { +// throw new RuntimeException(e); +// } +// } +// +// @Override +// public int getBlockId(Material material) { +// return material.getId(); +// } +// +// @Override +// public Material getMaterial(int id) { +// return Material.getMaterial(id); +// } +// +// @Override +// public int getBiomeId(Biome biome) { +// try { +// Object biomeBase = biomeToBiomeBase.invoke(null, biome); +// if (biomeBase != null) return (int) biomeBaseToTypeId.invoke(null, biomeBase); +// } catch (Throwable e) { +// throw new RuntimeException(e); +// } +// return 0; +// } +// +// @Override +// public Biome getBiome(int id) { +// try { +// Object biomeBase = getBiome.invoke(null, id); +// if (biomeBase != null) return (Biome) biomeBaseToBiome.invoke(null, biomeBase); +// } catch (Throwable e) { +// throw new RuntimeException(e); +// } +// return Biome.OCEAN; +// } +// +// @Override +// public BaseBlock getBlock(Location location) { +// try { +// World craftWorld = location.getWorld(); +// int x = location.getBlockX(); +// int y = location.getBlockY(); +// int z = location.getBlockZ(); +// +// org.bukkit.block.Block bukkitBlock = location.getBlock(); +// BaseBlock block = FaweCache.getBlock(bukkitBlock.getTypeId(), bukkitBlock.getData()); +// +// // Read the NBT data +// Object nmsWorld = getHandleWorld.invoke(craftWorld); +// Object tileEntity = getTileEntity(nmsWorld, x, y, z); +// +// if (tileEntity != null) { +// block = new BaseBlock(block); +// Object tag = newNBTTagCompound.newInstance(); +// readTileEntityIntoTag.invoke(tileEntity, tag); +// block.setNbtData((CompoundTag) toNative(tag)); +// } +// return block; +// } catch (Throwable e) { +// throw new RuntimeException(e); +// } +// } +// +// public Object getTileEntity(Object nmsWorld, int x, int y, int z) { +// try { +// if (getTileEntity1 != null) { +// Object pos = newBlockPosition.newInstance(x, y, z); +// return getTileEntity1.invoke(nmsWorld, pos); +// } else { +// return getTileEntity2.invoke(nmsWorld, x, y, z); +// } +// } catch (Exception e) { +// throw new RuntimeException(e); +// } +// } +// +// @Override +// public boolean setBlock(Location location, BaseBlock block, boolean notifyAndLight) { +// World craftWorld = location.getWorld(); +// int x = location.getBlockX(); +// int y = location.getBlockY(); +// int z = location.getBlockZ(); +// +// boolean changed = location.getBlock().setTypeIdAndData(block.getId(), (byte) block.getData(), notifyAndLight); +// +// CompoundTag nativeTag = block.getNbtData(); +// if (nativeTag != null) { +// try { +// Object nmsWorld = getHandleWorld.invoke(craftWorld); +// Object tileEntity = getTileEntity(nmsWorld, x, y, z); +// if (tileEntity != null) { +// Object tag = fromNative(nativeTag); +// +// setNBTTagCompound.invoke(tag, "x", newNBTTagInt.newInstance(x)); +// setNBTTagCompound.invoke(tag, "y", newNBTTagInt.newInstance(y)); +// setNBTTagCompound.invoke(tag, "z", newNBTTagInt.newInstance(z)); +// readTagIntoTileEntity.invoke(tileEntity, tag); // Load data +// } +// } catch (Throwable e) { +// throw new RuntimeException(e); +// } +// } +// +// return changed; +// } +// +// private interface NMSTagConstructor { +// Object construct(Tag value) throws Exception; +// } +// +// private interface WETagConstructor { +// Tag construct(Object value) throws Exception; +// } +//} diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/v0/PaperChunkCallback.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/v0/PaperChunkCallback.java new file mode 100644 index 000000000..369f9a71a --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/v0/PaperChunkCallback.java @@ -0,0 +1,12 @@ +package com.boydti.fawe.bukkit.v0; + +import org.bukkit.Chunk; +import org.bukkit.World; + +public abstract class PaperChunkCallback { + public PaperChunkCallback(World world, int x, int z) { + world.getChunkAtAsync(x, z, chunk -> PaperChunkCallback.this.onLoad(chunk)); + } + + public abstract void onLoad(Chunk chunk); +} \ No newline at end of file diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/wrapper/AsyncBlock.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/wrapper/AsyncBlock.java new file mode 100644 index 000000000..49634459f --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/wrapper/AsyncBlock.java @@ -0,0 +1,308 @@ +package com.boydti.fawe.bukkit.wrapper; + +import com.bekvon.bukkit.residence.commands.tool; +import com.boydti.fawe.FaweCache; +import com.boydti.fawe.bukkit.wrapper.state.AsyncSign; +import com.boydti.fawe.object.FaweQueue; +import com.boydti.fawe.util.TaskManager; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.blocks.BlockID; +import java.util.Collection; +import java.util.List; +import java.util.function.Supplier; + +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.world.block.BlockTypes; +import org.bukkit.Chunk; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.block.Biome; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.BlockState; +import org.bukkit.block.PistonMoveReaction; +import org.bukkit.block.data.BlockData; +import org.bukkit.inventory.ItemStack; +import org.bukkit.metadata.MetadataValue; +import org.bukkit.plugin.Plugin; + +public class AsyncBlock implements Block { + + public final int z; + public final int y; + public final int x; + public final FaweQueue queue; + public final AsyncWorld world; + + public AsyncBlock(AsyncWorld world, FaweQueue queue, int x, int y, int z) { + this.world = world; + this.queue = queue; + this.x = x; + this.y = Math.max(0, Math.min(255, y)); + this.z = z; + } + + @Override + @Deprecated + public byte getData() { + return (byte) (queue.getCachedCombinedId4Data(x, y, z, BlockTypes.AIR.getInternalId()) & 0xF); + } + + @Override + public Block getRelative(int modX, int modY, int modZ) { + return new AsyncBlock(world, queue, x + modX, y + modY, z + modZ); + } + + @Override + public Block getRelative(BlockFace face) { + return this.getRelative(face.getModX(), face.getModY(), face.getModZ()); + } + + @Override + public Block getRelative(BlockFace face, int distance) { + return this.getRelative(face.getModX() * distance, face.getModY() * distance, face.getModZ() * distance); + } + + @Override + public Material getType() { + return getBlockData().getMaterial(); + } + + @Override + public BlockData getBlockData() { + return BukkitAdapter.getBlockData(queue.getCachedCombinedId4Data(x, y, z, BlockTypes.AIR.getInternalId())); + } + + @Override + public byte getLightLevel() { + return (byte) queue.getLight(x, y, z); + } + + @Override + public byte getLightFromSky() { + return (byte) queue.getSkyLight(x, y, z); + } + + @Override + public byte getLightFromBlocks() { + return (byte) queue.getEmmittedLight(x, y, z); + } + + @Override + public World getWorld() { + return world; + } + + @Override + public int getX() { + return x; + } + + @Override + public int getY() { + return y; + } + + @Override + public int getZ() { + return z; + } + + @Override + public Location getLocation() { + return new Location(world, x, y, z); + } + + @Override + public Location getLocation(Location loc) { + if(loc != null) { + loc.setWorld(this.getWorld()); + loc.setX((double)this.x); + loc.setY((double)this.y); + loc.setZ((double)this.z); + } + return loc; + } + + @Override + public Chunk getChunk() { + return world.getChunkAt(x >> 4, z >> 4); + } + + @Override + public void setBlockData(BlockData blockData) { + try { + queue.setBlock(x, y, z, BukkitAdapter.adapt(blockData)); + } catch (WorldEditException e) { + throw new RuntimeException(e); + } + } + + @Override + public void setBlockData(BlockData blockData, boolean b) { + setBlockData(blockData); + } + + @Override + public void setType(Material type) { + try { + queue.setBlock(x, y, z, BukkitAdapter.adapt(type).getDefaultState()); + } catch (WorldEditException e) { + throw new RuntimeException(e); + } + } + + @Override + public void setType(Material type, boolean applyPhysics) { + setType(type); + } + + @Override + public BlockFace getFace(Block block) { + BlockFace[] directions = BlockFace.values(); + for(int i = 0; i < directions.length; ++i) { + BlockFace face = directions[i]; + if(this.getX() + face.getModX() == block.getX() && this.getY() + face.getModY() == block.getY() && this.getZ() + face.getModZ() == block.getZ()) { + return face; + } + } + return null; + } + + @Override + public BlockState getState() { + int combined = queue.getCombinedId4Data(x, y, z, 0); + BlockTypes type = BlockTypes.getFromStateId(combined); + switch (type) { + case SIGN: + case WALL_SIGN: + return new AsyncSign(this, combined); + } + return new AsyncBlockState(this, combined); + } + + @Override + public BlockState getState(boolean useSnapshot) { + return getState(); + } + + @Override + public Biome getBiome() { + return world.getAdapter().getBiome(queue.getBiomeId(x, z)); + } + + @Override + public void setBiome(Biome bio) { + int id = world.getAdapter().getBiomeId(bio); + queue.setBiome(x, z, FaweCache.getBiome(id)); + } + + @Override + public boolean isBlockPowered() { + return false; + } + + @Override + public boolean isBlockIndirectlyPowered() { + return false; + } + + @Override + public boolean isBlockFacePowered(BlockFace face) { + return false; + } + + @Override + public boolean isBlockFaceIndirectlyPowered(BlockFace face) { + return false; + } + + @Override + public int getBlockPower(BlockFace face) { + return 0; + } + + @Override + public int getBlockPower() { + return 0; + } + + @Override + public boolean isEmpty() { + switch (getType()) { + case AIR: + case CAVE_AIR: + case VOID_AIR: + return true; + default: + return false; + } + } + + @Override + public boolean isLiquid() { + int combined = queue.getCombinedId4Data(x, y, z, 0); + BlockTypes type = BlockTypes.getFromStateId(combined); + return type.getMaterial().isLiquid(); + } + + @Override + public double getTemperature() { + return this.getWorld().getTemperature(this.getX(), this.getZ()); + } + + @Override + public double getHumidity() { + return this.getWorld().getHumidity(this.getX(), this.getZ()); + } + + @Override + public PistonMoveReaction getPistonMoveReaction() { + return null; + } + + public Block getBukkitBlock() { + return world.getBukkitWorld().getBlockAt(x, y, z); + } + + @Override + public boolean breakNaturally() { + return TaskManager.IMP.sync(() -> getBukkitBlock().breakNaturally()); + } + + @Override + public boolean breakNaturally(ItemStack tool) { + return TaskManager.IMP.sync(() -> getBukkitBlock().breakNaturally(tool)); + } + + @Override + public Collection getDrops() { + return TaskManager.IMP.sync(() -> getBukkitBlock().getDrops()); + } + + @Override + public Collection getDrops(ItemStack tool) { + return TaskManager.IMP.sync(() -> getBukkitBlock().getDrops(tool)); + } + + @Override + public void setMetadata(String metadataKey, MetadataValue newMetadataValue) { + this.getBukkitBlock().setMetadata(metadataKey, newMetadataValue); + } + + @Override + public List getMetadata(String metadataKey) { + return this.getBukkitBlock().getMetadata(metadataKey); + } + + @Override + public boolean hasMetadata(String metadataKey) { + return this.getBukkitBlock().hasMetadata(metadataKey); + } + + @Override + public void removeMetadata(String metadataKey, Plugin owningPlugin) { + this.getBukkitBlock().removeMetadata(metadataKey, owningPlugin); + } +} diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/wrapper/AsyncBlockState.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/wrapper/AsyncBlockState.java new file mode 100644 index 000000000..e91548c1b --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/wrapper/AsyncBlockState.java @@ -0,0 +1,183 @@ +package com.boydti.fawe.bukkit.wrapper; + +import com.boydti.fawe.FaweCache; +import com.sk89q.jnbt.CompoundTag; +import java.util.List; + +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.world.block.BlockTypes; +import org.bukkit.Chunk; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.block.data.BlockData; +import org.bukkit.material.MaterialData; +import org.bukkit.metadata.MetadataValue; +import org.bukkit.plugin.Plugin; + +public class AsyncBlockState implements BlockState { + + private int combinedId; + private BlockData blockData; + private CompoundTag nbt; + private final AsyncBlock block; + + public AsyncBlockState(AsyncBlock block) { + this(block, block.queue.getCombinedId4Data(block.x, block.y, block.z, 0)); + } + + public AsyncBlockState(AsyncBlock block, int combined) { + this.combinedId = combined; + this.block = block; + this.blockData = BukkitAdapter.getBlockData(combined); + if (BlockTypes.getFromStateId(combined).getMaterial().hasContainer()) { + this.nbt = block.queue.getTileEntity(block.x, block.y, block.z); + } + } + + @Override + public Block getBlock() { + return block; + } + + @Override + public BlockData getBlockData() { + return blockData; + } + + @Override + public MaterialData getData() { + return new MaterialData(blockData.getMaterial()); + } + + @Override + public Material getType() { + return blockData.getMaterial(); + } + + @Override + public byte getLightLevel() { + return (byte) BlockTypes.getFromStateId(combinedId).getMaterial().getLightValue(); + } + + @Override + public World getWorld() { + return block.world; + } + + @Override + public int getX() { + return block.x; + } + + @Override + public int getY() { + return block.y; + } + + @Override + public int getZ() { + return block.z; + } + + @Override + public Location getLocation() { + return block.getLocation(); + } + + @Override + public Location getLocation(Location loc) { + return block.getLocation(loc); + } + + @Override + public Chunk getChunk() { + return block.getChunk(); + } + + @Override + public void setData(MaterialData data) { + setBlockData(data.getItemType().createBlockData()); + } + + @Override + public void setBlockData(BlockData blockData) { + this.blockData = blockData; + this.combinedId = BukkitAdapter.adapt(blockData).getInternalId(); + } + + @Override + public void setType(Material type) { + setBlockData(type.createBlockData()); + } + + @Override + public boolean update() { + return update(false); + } + + @Override + public boolean update(boolean force) { + return update(force, true); + } + + @Override + public boolean update(boolean force, boolean applyPhysics) { + try { + boolean result = block.queue.setBlock(block.x, block.y, block.z, BukkitAdapter.adapt(blockData)); + if (nbt != null) { + block.queue.setTile(block.x, block.y, block.z, nbt); + } + return result; + } catch (WorldEditException e) { + throw new RuntimeException(e); + } + } + + public CompoundTag getNbtData() { + return nbt; + } + + public void setNbtData(CompoundTag nbt) { + this.nbt = nbt; + } + + @Override + public byte getRawData() { + return (byte) (combinedId >> BlockTypes.BIT_OFFSET); + } + + @Override + public void setRawData(byte data) { + this.combinedId = (combinedId & BlockTypes.BIT_MASK) + data; + this.blockData = BukkitAdapter.getBlockData(this.combinedId); + } + + @Override + public boolean isPlaced() { + return true; + } + + @Override + public void setMetadata(String key, MetadataValue value) { + block.setMetadata(key, value); + } + + @Override + public List getMetadata(String key) { + return block.getMetadata(key); + } + + @Override + public boolean hasMetadata(String key) { + return block.hasMetadata(key); + } + + @Override + public void removeMetadata(String key, Plugin plugin) { + block.removeMetadata(key, plugin); + } +} diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/wrapper/AsyncChunk.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/wrapper/AsyncChunk.java new file mode 100644 index 000000000..370baa38c --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/wrapper/AsyncChunk.java @@ -0,0 +1,172 @@ +package com.boydti.fawe.bukkit.wrapper; + +import com.boydti.fawe.Fawe; +import com.boydti.fawe.bukkit.v0.BukkitQueue_0; +import com.boydti.fawe.object.FaweQueue; +import com.boydti.fawe.object.RunnableVal; +import com.boydti.fawe.util.MathMan; +import com.boydti.fawe.util.TaskManager; +import org.bukkit.Chunk; +import org.bukkit.ChunkSnapshot; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.entity.Entity; + +public class AsyncChunk implements Chunk { + + private final AsyncWorld world; + private final int z; + private final int x; + private final FaweQueue queue; + + public AsyncChunk(World world, FaweQueue queue, int x, int z) { + this.world = world instanceof AsyncWorld ? (AsyncWorld) world : new AsyncWorld(world, true); + this.queue = queue; + this.x = x; + this.z = z; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof Chunk)) { + return false; + } + Chunk other = (Chunk) obj; + return other.getX() == x && other.getZ() == z && world.equals(other.getWorld()); + } + + @Override + public int hashCode() { + return MathMan.pair((short) x, (short) z); + } + + @Override + public int getX() { + return x; + } + + @Override + public int getZ() { + return z; + } + + @Override + public World getWorld() { + return world; + } + + @Override + public Block getBlock(int x, int y, int z) { + return new AsyncBlock(world, queue, (this.x << 4) + x, y, (this.z << 4) + z); + } + + @Override + public ChunkSnapshot getChunkSnapshot() { + return getChunkSnapshot(false, true, false); + } + + @Override + public ChunkSnapshot getChunkSnapshot(boolean includeMaxblocky, boolean includeBiome, boolean includeBiomeTempRain) { + if (Thread.currentThread() == Fawe.get().getMainThread()) { + return world.getChunkAt(x, z).getChunkSnapshot(includeMaxblocky, includeBiome, includeBiomeTempRain); + } + return whenLoaded(new RunnableVal() { + @Override + public void run(ChunkSnapshot value) { + this.value = world.getChunkAt(x, z).getChunkSnapshot(includeBiome, includeBiome, includeBiomeTempRain); + } + }); + } + + private T whenLoaded(RunnableVal task) { + if (Thread.currentThread() == Fawe.get().getMainThread()) { + task.run(); + return task.value; + } + if (queue instanceof BukkitQueue_0) { + BukkitQueue_0 bq = (BukkitQueue_0) queue; + if (world.isChunkLoaded(x, z)) { + long pair = MathMan.pairInt(x, z); + Long originalKeep = bq.keepLoaded.get(pair); + bq.keepLoaded.put(pair, Long.MAX_VALUE); + if (world.isChunkLoaded(x, z)) { + task.run(); + if (originalKeep != null) { + bq.keepLoaded.put(pair, originalKeep); + } else { + bq.keepLoaded.remove(pair); + } + return task.value; + } + } + } + return TaskManager.IMP.sync(task); + } + + @Override + public Entity[] getEntities() { + if (!isLoaded()) { + return new Entity[0]; + } + return whenLoaded(new RunnableVal() { + @Override + public void run(Entity[] value) { + world.getChunkAt(x, z).getEntities(); + } + }); + } + + @Override + public BlockState[] getTileEntities() { + if (!isLoaded()) { + return new BlockState[0]; + } + return TaskManager.IMP.sync(new RunnableVal() { + @Override + public void run(BlockState[] value) { + this.value = world.getChunkAt(x, z).getTileEntities(); + } + }); + } + + @Override + public boolean isLoaded() { + return world.isChunkLoaded(x, z); + } + + @Override + public boolean load(final boolean generate) { + return TaskManager.IMP.sync(new RunnableVal() { + @Override + public void run(Boolean value) { + this.value = world.loadChunk(x, z, generate); + } + }); + } + + @Override + public boolean load() { + return load(false); + } + + @Override + public boolean unload(boolean save, boolean safe) { + return world.unloadChunk(x, z, save, safe); + } + + @Override + public boolean unload(boolean save) { + return unload(true, false); + } + + @Override + public boolean unload() { + return unload(true); + } + + @Override + public boolean isSlimeChunk() { + return false; + } +} diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/wrapper/AsyncWorld.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/wrapper/AsyncWorld.java new file mode 100644 index 000000000..f6d5abdd5 --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/wrapper/AsyncWorld.java @@ -0,0 +1,1177 @@ +package com.boydti.fawe.bukkit.wrapper; + +import com.boydti.fawe.Fawe; +import com.boydti.fawe.FaweAPI; +import com.boydti.fawe.bukkit.FaweBukkit; +import com.boydti.fawe.bukkit.v0.BukkitQueue_0; +import com.boydti.fawe.object.FaweQueue; +import com.boydti.fawe.object.HasFaweQueue; +import com.boydti.fawe.object.RunnableVal; +import com.boydti.fawe.object.queue.DelegateFaweQueue; +import com.boydti.fawe.util.SetQueue; +import com.boydti.fawe.util.StringMan; +import com.boydti.fawe.util.TaskManager; +import com.sk89q.worldedit.bukkit.WorldEditPlugin; +import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; +import com.sk89q.worldedit.function.operation.Operation; +import com.sk89q.worldedit.world.biome.BaseBiome; +import java.io.File; +import java.lang.reflect.Field; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.function.Supplier; + +import com.sk89q.worldedit.world.block.BlockTypes; +import org.bukkit.*; +import org.bukkit.block.Biome; +import org.bukkit.block.Block; +import org.bukkit.block.data.BlockData; +import org.bukkit.entity.Arrow; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.FallingBlock; +import org.bukkit.entity.Item; +import org.bukkit.entity.LightningStrike; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.generator.BlockPopulator; +import org.bukkit.generator.ChunkGenerator; +import org.bukkit.inventory.ItemStack; +import org.bukkit.material.MaterialData; +import org.bukkit.metadata.MetadataValue; +import org.bukkit.plugin.Plugin; +import org.bukkit.util.Consumer; +import org.bukkit.util.Vector; + +/** + * Modify the world from an async thread
+ * - Use world.commit() to execute all the changes
+ * - Any Chunk/Block/BlockState objects returned should also be safe to use from the same async thread
+ * - Only block read,write and biome write are fast, other methods will perform slower async
+ * - + * @see #wrap(World) + * @see #create(WorldCreator) + */ +public class AsyncWorld extends DelegateFaweQueue implements World, HasFaweQueue { + + private World parent; + private FaweQueue queue; + private BukkitImplAdapter adapter; + + @Override + public void spawnParticle(Particle particle, double v, double v1, double v2, int i, double v3, double v4, double v5, double v6, T t) { + parent.spawnParticle(particle, v, v1, v2, i, v3, v4, v5, v6, t); + } + + @Override + public Entity getEntity(UUID uuid) { + return TaskManager.IMP.sync(() -> parent.getEntity(uuid)); + } + + + @Override + public boolean createExplosion(Entity source, Location loc, float power, boolean setFire, boolean breakBlocks) { + return TaskManager.IMP.sync(() -> parent.createExplosion(source, loc, power, setFire, breakBlocks)); + } + + + @Override + public void spawnParticle(Particle particle, List receivers, Player source, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra, T data) { + parent.spawnParticle(particle, receivers, source, x, y, z, count, offsetX, offsetY, offsetZ, extra, data); + } + + /** + * @deprecated use {@link #wrap(World)} instead + * @param parent Parent world + * @param autoQueue + */ + @Deprecated + public AsyncWorld(World parent, boolean autoQueue) { + this(parent, FaweAPI.createQueue(parent.getName(), autoQueue)); + } + + public AsyncWorld(String world, boolean autoQueue) { + this(Bukkit.getWorld(world), autoQueue); + } + + /** + * @deprecated use {@link #wrap(World)} instead + * @param parent + * @param queue + */ + @Deprecated + public AsyncWorld(World parent, FaweQueue queue) { + super(queue); + this.parent = parent; + this.queue = queue; + if (queue instanceof BukkitQueue_0) { + this.adapter = (BukkitImplAdapter) ((BukkitQueue_0) queue).getAdapter(); + } else { + try { + this.adapter = WorldEditPlugin.getInstance().getBukkitImplAdapter(); + } catch (Throwable e) { + e.printStackTrace(); + } + } + } + + /** + * Wrap a world for async usage + * @param world + * @return + */ + public static AsyncWorld wrap(World world) { + if (world instanceof AsyncWorld) { + return (AsyncWorld) world; + } + return new AsyncWorld(world, false); + } + + public void changeWorld(World world, FaweQueue queue) { + this.parent = world; + if (queue != this.queue) { + if (this.queue != null) { + final FaweQueue oldQueue = this.queue; + TaskManager.IMP.async(new Runnable() { + @Override + public void run() { + oldQueue.flush(); + } + }); + } + this.queue = queue; + } + setParent(queue); + } + + @Override + public String toString() { + return super.toString() + ":" + queue.toString(); + } + + public World getBukkitWorld() { + return parent; + } + + public FaweQueue getQueue() { + return queue; + } + + /** + * Create a world async (untested) + * - Only optimized for 1.10 + * @param creator + * @return + */ + public synchronized static AsyncWorld create(final WorldCreator creator) { + BukkitQueue_0 queue = (BukkitQueue_0) SetQueue.IMP.getNewQueue(creator.name(), true, false); + World world = queue.createWorld(creator); + return wrap(world); + } + + public Operation commit() { + flush(); + return null; + } + + public void flush() { + if (queue != null) { + queue.flush(); + } + } + + @Override + public WorldBorder getWorldBorder() { + return TaskManager.IMP.sync(new RunnableVal() { + @Override + public void run(WorldBorder value) { + this.value = parent.getWorldBorder(); + } + }); + } + + @Override + public void spawnParticle(Particle particle, Location location, int i) { + parent.spawnParticle(particle, location, i); + } + + @Override + public void spawnParticle(Particle particle, double v, double v1, double v2, int i) { + parent.spawnParticle(particle, v, v1, v2, i); + } + + @Override + public void spawnParticle(Particle particle, Location location, int i, T t) { + parent.spawnParticle(particle, location, i, t); + } + + @Override + public void spawnParticle(Particle particle, double v, double v1, double v2, int i, T t) { + parent.spawnParticle(particle, v, v1, v2, i, t); + } + + @Override + public void spawnParticle(Particle particle, Location location, int i, double v, double v1, double v2) { + parent.spawnParticle(particle, location, i, v, v1, v2); + } + + @Override + public void spawnParticle(Particle particle, double v, double v1, double v2, int i, double v3, double v4, double v5) { + parent.spawnParticle(particle, v, v1, v2, i, v3, v4, v5); + } + + @Override + public void spawnParticle(Particle particle, Location location, int i, double v, double v1, double v2, T t) { + parent.spawnParticle(particle, location, i, v, v1, v2, t); + } + + @Override + public void spawnParticle(Particle particle, double v, double v1, double v2, int i, double v3, double v4, double v5, T t) { + parent.spawnParticle(particle, v, v1, v2, i, v3, v4, v5, t); + } + + @Override + public void spawnParticle(Particle particle, Location location, int i, double v, double v1, double v2, double v3) { + parent.spawnParticle(particle, location, i, v, v1, v2, v3); + } + + @Override + public void spawnParticle(Particle particle, double v, double v1, double v2, int i, double v3, double v4, double v5, double v6) { + parent.spawnParticle(particle, v, v1, v2, i, v3, v4, v5, v6); + } + + @Override + public void spawnParticle(Particle particle, Location location, int i, double v, double v1, double v2, double v3, T t) { + parent.spawnParticle(particle, location, i, v, v1, v2, v3, t); + } + + @Override + public boolean setSpawnLocation(Location location) { + return parent.setSpawnLocation(location); + } + + @Override + public Block getBlockAt(final int x, final int y, final int z) { + return new AsyncBlock(this, queue, x, y, z); + } + + @Override + public Block getBlockAt(Location loc) { + return getBlockAt(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()); + } + + @Override + public int getHighestBlockYAt(int x, int z) { + for (int y = getMaxHeight() - 1; y >= 0; y--) { + if (queue.getCachedCombinedId4Data(x, y, z, BlockTypes.AIR.getInternalId()) != 0) { + return y; + } + } + return 0; + } + + @Override + public int getHighestBlockYAt(Location loc) { + return getHighestBlockYAt(loc.getBlockX(), loc.getBlockZ()); + } + + @Override + public Block getHighestBlockAt(int x, int z) { + int y = getHighestBlockYAt(x, z); + return getBlockAt(x, y, z); + } + + @Override + public Block getHighestBlockAt(Location loc) { + return getHighestBlockAt(loc.getBlockX(), loc.getBlockZ()); + } + + @Override + public Chunk getChunkAt(int x, int z) { + return new AsyncChunk(this, queue, x, z); + } + + @Override + public Chunk getChunkAt(Location location) { + return getChunkAt(location.getBlockX(), location.getBlockZ()); + } + + @Override + public Chunk getChunkAt(Block block) { + return getChunkAt(block.getX(), block.getZ()); + } + + @Override + public void getChunkAtAsync(int x, int z, ChunkLoadCallback cb) { + parent.getChunkAtAsync(x, z, cb); + } + + @Override + public void getChunkAtAsync(Location location, ChunkLoadCallback cb) { + parent.getChunkAtAsync(location, cb); + } + + @Override + public void getChunkAtAsync(Block block, ChunkLoadCallback cb) { + parent.getChunkAtAsync(block, cb); + } + + @Override + public boolean isChunkLoaded(Chunk chunk) { + return chunk.isLoaded(); + } + + @Override + public Chunk[] getLoadedChunks() { + return parent.getLoadedChunks(); + } + + @Override + public void loadChunk(final Chunk chunk) { + if (!chunk.isLoaded()) { + TaskManager.IMP.sync(new RunnableVal() { + @Override + public void run(Object value) { + parent.loadChunk(chunk); + } + }); + } + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof World)) { + return false; + } + World other = (World) obj; + return StringMan.isEqual(other.getName(), getName()); + } + + @Override + public int hashCode() { + return this.getUID().hashCode(); + } + + @Override + public boolean isChunkLoaded(int x, int z) { + return parent.isChunkLoaded(x, z); + } + + @Override + public boolean isChunkInUse(int x, int z) { + return parent.isChunkInUse(x, z); + } + + @Override + public void loadChunk(final int x, final int z) { + if (!isChunkLoaded(x, z)) { + TaskManager.IMP.sync(new RunnableVal() { + @Override + public void run(Object value) { + parent.loadChunk(x, z); + } + }); + } + } + + @Override + public boolean loadChunk(final int x, final int z, final boolean generate) { + if (!isChunkLoaded(x, z)) { + return TaskManager.IMP.sync(new RunnableVal() { + @Override + public void run(Boolean value) { + this.value = parent.loadChunk(x, z, generate); + } + }); + } + return true; + } + + @Override + public boolean unloadChunk(final Chunk chunk) { + if (chunk.isLoaded()) { + return TaskManager.IMP.sync(new RunnableVal() { + @Override + public void run(Boolean value) { + this.value = parent.unloadChunk(chunk); + } + }); + } + return true; + } + + @Override + public boolean unloadChunk(int x, int z) { + return unloadChunk(x, z, true); + } + + @Override + public boolean unloadChunk(int x, int z, boolean save) { + return unloadChunk(x, z, save, false); + } + + @Deprecated + @Override + public boolean unloadChunk(final int x, final int z, final boolean save, final boolean safe) { + if (isChunkLoaded(x, z)) { + return TaskManager.IMP.sync(new RunnableVal() { + @Override + public void run(Boolean value) { + this.value = parent.unloadChunk(x, z, save, safe); + } + }); + } + return true; + } + + @Override + public boolean unloadChunkRequest(int x, int z) { + return unloadChunk(x, z); + } + + @Override + public boolean unloadChunkRequest(int x, int z, boolean safe) { + return unloadChunk(x, z, safe); + } + + @Override + public boolean regenerateChunk(final int x, final int z) { + return TaskManager.IMP.sync(new RunnableVal() { + @Override + public void run(Boolean value) { + this.value = parent.regenerateChunk(x, z); + } + }); + } + + @Override + @Deprecated + public boolean refreshChunk(int x, int z) { + queue.sendChunk(queue.getFaweChunk(x, z)); + return true; + } + + @Override + public Item dropItem(final Location location, final ItemStack item) { + return TaskManager.IMP.sync(new RunnableVal() { + @Override + public void run(Item value) { + this.value = parent.dropItem(location, item); + } + }); + } + + @Override + public Item dropItemNaturally(final Location location, final ItemStack item) { + return TaskManager.IMP.sync(new RunnableVal() { + @Override + public void run(Item value) { + this.value = parent.dropItemNaturally(location, item); + } + }); + } + + @Override + public Arrow spawnArrow(final Location location, final Vector direction, final float speed, final float spread) { + return TaskManager.IMP.sync(new RunnableVal() { + @Override + public void run(Arrow value) { + this.value = parent.spawnArrow(location, direction, speed, spread); + } + }); + } + + @Override + public T spawnArrow(Location location, Vector vector, float v, float v1, Class aClass) { + return parent.spawnArrow(location, vector, v, v1, aClass); + } + + @Override + public boolean generateTree(final Location location, final TreeType type) { + return TaskManager.IMP.sync(new RunnableVal() { + @Override + public void run(Boolean value) { + this.value = parent.generateTree(location, type); + } + }); + } + + @Override + public boolean generateTree(final Location loc, final TreeType type, final BlockChangeDelegate delegate) { + return TaskManager.IMP.sync(new RunnableVal() { + @Override + public void run(Boolean value) { + this.value = parent.generateTree(loc, type, delegate); + } + }); + } + + @Override + public Entity spawnEntity(Location loc, EntityType type) { + return spawn(loc, type.getEntityClass()); + } + + @Override + public LightningStrike strikeLightning(final Location loc) { + return TaskManager.IMP.sync(new RunnableVal() { + @Override + public void run(LightningStrike value) { + this.value = parent.strikeLightning(loc); + } + }); + } + + @Override + public LightningStrike strikeLightningEffect(final Location loc) { + return TaskManager.IMP.sync(new RunnableVal() { + @Override + public void run(LightningStrike value) { + this.value = parent.strikeLightningEffect(loc); + } + }); + } + + @Override + public List getEntities() { + return TaskManager.IMP.sync(new RunnableVal>() { + @Override + public void run(List value) { + this.value = parent.getEntities(); + } + }); + } + + @Override + public List getLivingEntities() { + return TaskManager.IMP.sync(new RunnableVal>() { + @Override + public void run(List value) { + this.value = parent.getLivingEntities(); + } + }); + } + + @Override + @Deprecated + public Collection getEntitiesByClass(final Class... classes) { + return TaskManager.IMP.sync(new RunnableVal>() { + @Override + public void run(Collection value) { + this.value = (Collection) parent.getEntitiesByClass(classes); + } + }); + } + + @Override + public Collection getEntitiesByClass(final Class cls) { + return TaskManager.IMP.sync(new RunnableVal>() { + @Override + public void run(Collection value) { + this.value = (Collection) parent.getEntitiesByClass(cls); + } + }); + } + + @Override + public Collection getEntitiesByClasses(final Class... classes) { + return TaskManager.IMP.sync(new RunnableVal>() { + @Override + public void run(Collection value) { + this.value = parent.getEntitiesByClasses(classes); + } + }); + } + + @Override + public List getPlayers() { + return TaskManager.IMP.sync(new RunnableVal>() { + @Override + public void run(List value) { + this.value = parent.getPlayers(); + } + }); + } + + @Override + public Collection getNearbyEntities(final Location location, final double x, final double y, final double z) { + return TaskManager.IMP.sync(new RunnableVal>() { + @Override + public void run(Collection value) { + this.value = parent.getNearbyEntities(location, x, y, z); + } + }); + } + + @Override + public String getName() { + return parent.getName(); + } + + @Override + public UUID getUID() { + return parent.getUID(); + } + + @Override + public Location getSpawnLocation() { + return parent.getSpawnLocation(); + } + + @Override + public boolean setSpawnLocation(final int x, final int y, final int z) { + return TaskManager.IMP.sync(new RunnableVal() { + @Override + public void run(Boolean value) { + this.value = parent.setSpawnLocation(x, y, z); + } + }); + } + + @Override + public long getTime() { + return parent.getTime(); + } + + @Override + public void setTime(long time) { + parent.setTime(time); + } + + @Override + public long getFullTime() { + return parent.getFullTime(); + } + + @Override + public void setFullTime(long time) { + parent.setFullTime(time); + } + + @Override + public boolean hasStorm() { + return parent.hasStorm(); + } + + @Override + public void setStorm(boolean hasStorm) { + parent.setStorm(hasStorm); + } + + @Override + public int getWeatherDuration() { + return parent.getWeatherDuration(); + } + + @Override + public void setWeatherDuration(int duration) { + parent.setWeatherDuration(duration); + } + + @Override + public boolean isThundering() { + return parent.isThundering(); + } + + @Override + public void setThundering(boolean thundering) { + parent.setThundering(thundering); + } + + @Override + public int getThunderDuration() { + return parent.getThunderDuration(); + } + + @Override + public void setThunderDuration(int duration) { + parent.setThunderDuration(duration); + } + + public boolean createExplosion(double x, double y, double z, float power) { + return this.createExplosion(x, y, z, power, false, true); + } + + public boolean createExplosion(double x, double y, double z, float power, boolean setFire) { + return this.createExplosion(x, y, z, power, setFire, true); + } + + public boolean createExplosion(final double x, final double y, final double z, final float power, final boolean setFire, final boolean breakBlocks) { + return TaskManager.IMP.sync(new RunnableVal() { + @Override + public void run(Boolean value) { + this.value = parent.createExplosion(x, y, z, power, setFire, breakBlocks); + } + }); + } + + public boolean createExplosion(Location loc, float power) { + return this.createExplosion(loc, power, false); + } + + public boolean createExplosion(Location loc, float power, boolean setFire) { + return this.createExplosion(loc.getX(), loc.getY(), loc.getZ(), power, setFire); + } + + @Override + public Environment getEnvironment() { + return parent.getEnvironment(); + } + + @Override + public long getSeed() { + return parent.getSeed(); + } + + @Override + public boolean getPVP() { + return parent.getPVP(); + } + + @Override + public void setPVP(boolean pvp) { + parent.setPVP(pvp); + } + + @Override + public ChunkGenerator getGenerator() { + return parent.getGenerator(); + } + + @Override + public void save() { + TaskManager.IMP.sync(new RunnableVal() { + @Override + public void run(Object value) { + parent.save(); + } + }); + } + + @Override + public List getPopulators() { + return parent.getPopulators(); + } + + @Override + public T spawn(final Location location, final Class clazz) throws IllegalArgumentException { + return TaskManager.IMP.sync(new RunnableVal() { + @Override + public void run(T value) { + this.value = parent.spawn(location, clazz); + } + }); + } + + @Override + public T spawn(Location location, Class clazz, Consumer function) throws IllegalArgumentException { + return TaskManager.IMP.sync(new RunnableVal() { + @Override + public void run(T value) { + this.value = parent.spawn(location, clazz, function); + } + }); + } + + @Override + public FallingBlock spawnFallingBlock(Location location, MaterialData data) throws IllegalArgumentException { + return TaskManager.IMP.sync(new RunnableVal() { + @Override + public void run(FallingBlock value) { + this.value = parent.spawnFallingBlock(location, data); + } + }); + } + + @Override + @Deprecated + public FallingBlock spawnFallingBlock(Location location, Material material, byte data) throws IllegalArgumentException { + return TaskManager.IMP.sync(new Supplier() { + @Override + public FallingBlock get() { + return parent.spawnFallingBlock(location, material, data); + } + }); + } + + @Override + public FallingBlock spawnFallingBlock(Location location, BlockData blockData) throws IllegalArgumentException { + return TaskManager.IMP.sync(new Supplier() { + @Override + public FallingBlock get() { + return parent.spawnFallingBlock(location, blockData); + } + }); + } + + @Override + public void playEffect(Location location, Effect effect, int data) { + this.playEffect(location, effect, data, 64); + } + + @Override + public void playEffect(final Location location, final Effect effect, final int data, final int radius) { + TaskManager.IMP.sync(new RunnableVal() { + @Override + public void run(Object value) { + parent.playEffect(location, effect, data, radius); + } + }); + } + + @Override + public void playEffect(Location loc, Effect effect, T data) { + this.playEffect(loc, effect, data, 64); + } + + @Override + public void playEffect(final Location location, final Effect effect, final T data, final int radius) { + TaskManager.IMP.sync(new RunnableVal() { + @Override + public void run(Object value) { + parent.playEffect(location, effect, data, radius); + } + }); + } + + @Override + public ChunkSnapshot getEmptyChunkSnapshot(final int x, final int z, final boolean includeBiome, final boolean includeBiomeTempRain) { + return TaskManager.IMP.sync(new RunnableVal() { + @Override + public void run(ChunkSnapshot value) { + this.value = parent.getEmptyChunkSnapshot(x, z, includeBiome, includeBiomeTempRain); + } + }); + } + + @Override + public void setSpawnFlags(boolean allowMonsters, boolean allowAnimals) { + parent.setSpawnFlags(allowMonsters, allowAnimals); + } + + @Override + public boolean getAllowAnimals() { + return parent.getAllowAnimals(); + } + + @Override + public boolean getAllowMonsters() { + return parent.getAllowMonsters(); + } + + @Override + public Biome getBiome(int x, int z) { + return adapter.getBiome(queue.getBiomeId(x, z)); + } + + @Override + public void setBiome(int x, int z, Biome bio) { + int id = adapter.getBiomeId(bio); + queue.setBiome(x, z, new BaseBiome(id)); + } + + @Override + public double getTemperature(int x, int z) { + return parent.getTemperature(x, z); + } + + @Override + public double getHumidity(int x, int z) { + return parent.getHumidity(x, z); + } + + @Override + public int getMaxHeight() { + return parent.getMaxHeight(); + } + + @Override + public int getSeaLevel() { + return parent.getSeaLevel(); + } + + @Override + public boolean getKeepSpawnInMemory() { + return parent.getKeepSpawnInMemory(); + } + + @Override + public void setKeepSpawnInMemory(final boolean keepLoaded) { + TaskManager.IMP.sync(new RunnableVal() { + @Override + public void run(Object value) { + parent.setKeepSpawnInMemory(keepLoaded); + } + }); + } + + @Override + public boolean isAutoSave() { + return parent.isAutoSave(); + } + + @Override + public void setAutoSave(boolean value) { + parent.setAutoSave(value); + } + + @Override + public void setDifficulty(Difficulty difficulty) { + parent.setDifficulty(difficulty); + } + + @Override + public Difficulty getDifficulty() { + return parent.getDifficulty(); + } + + @Override + public File getWorldFolder() { + return parent.getWorldFolder(); + } + + @Override + public WorldType getWorldType() { + return parent.getWorldType(); + } + + @Override + public boolean canGenerateStructures() { + return parent.canGenerateStructures(); + } + + @Override + public long getTicksPerAnimalSpawns() { + return parent.getTicksPerAnimalSpawns(); + } + + @Override + public void setTicksPerAnimalSpawns(int ticksPerAnimalSpawns) { + parent.setTicksPerAnimalSpawns(ticksPerAnimalSpawns); + } + + @Override + public long getTicksPerMonsterSpawns() { + return parent.getTicksPerMonsterSpawns(); + } + + @Override + public void setTicksPerMonsterSpawns(int ticksPerMonsterSpawns) { + parent.setTicksPerMonsterSpawns(ticksPerMonsterSpawns); + } + + @Override + public int getMonsterSpawnLimit() { + return parent.getMonsterSpawnLimit(); + } + + @Override + public void setMonsterSpawnLimit(int limit) { + parent.setMonsterSpawnLimit(limit); + } + + @Override + public int getAnimalSpawnLimit() { + return parent.getAnimalSpawnLimit(); + } + + @Override + public void setAnimalSpawnLimit(int limit) { + parent.setAnimalSpawnLimit(limit); + } + + @Override + public int getWaterAnimalSpawnLimit() { + return parent.getWaterAnimalSpawnLimit(); + } + + @Override + public void setWaterAnimalSpawnLimit(int limit) { + parent.setWaterAnimalSpawnLimit(limit); + } + + @Override + public int getAmbientSpawnLimit() { + return parent.getAmbientSpawnLimit(); + } + + @Override + public void setAmbientSpawnLimit(int limit) { + parent.setAmbientSpawnLimit(limit); + } + + @Override + public void playSound(final Location location, final Sound sound, final float volume, final float pitch) { + TaskManager.IMP.sync(new RunnableVal() { + @Override + public void run(Object value) { + parent.playSound(location, sound, volume, pitch); + } + }); + } + + @Override + public void playSound(final Location location, final String sound, final float volume, final float pitch) { + TaskManager.IMP.sync(new RunnableVal() { + @Override + public void run(Object value) { + parent.playSound(location, sound, volume, pitch); + } + }); + } + + @Override + public void playSound(Location location, Sound sound, SoundCategory category, float volume, float pitch) { + TaskManager.IMP.sync(new RunnableVal() { + @Override + public void run(Object value) { + parent.playSound(location, sound, category, volume, pitch); + } + }); + } + + @Override + public void playSound(Location location, String sound, SoundCategory category, float volume, float pitch) { + TaskManager.IMP.sync(new RunnableVal() { + @Override + public void run(Object value) { + parent.playSound(location, sound, category, volume, pitch); + } + }); + } + + @Override + public String[] getGameRules() { + return parent.getGameRules(); + } + + @Override + public String getGameRuleValue(String rule) { + return parent.getGameRuleValue(rule); + } + + @Override + public boolean setGameRuleValue(String rule, String value) { + return parent.setGameRuleValue(rule, value); + } + + @Override + public boolean isGameRule(String rule) { + return parent.isGameRule(rule); + } + + @Override + public T getGameRuleValue(GameRule gameRule) { + return parent.getGameRuleValue(gameRule); + } + + @Override + public T getGameRuleDefault(GameRule gameRule) { + return parent.getGameRuleDefault(gameRule); + } + + @Override + public boolean setGameRule(GameRule gameRule, T t) { + return parent.setGameRule(gameRule, t); + } + + @Override + public Spigot spigot() { + return parent.spigot(); + } + + @Override + public void setMetadata(final String key, final MetadataValue meta) { + TaskManager.IMP.sync(new RunnableVal() { + @Override + public void run(Object value) { + parent.setMetadata(key, meta); + } + }); + } + + @Override + public List getMetadata(String key) { + return parent.getMetadata(key); + } + + @Override + public boolean hasMetadata(String key) { + return parent.hasMetadata(key); + } + + @Override + public void removeMetadata(final String key, final Plugin plugin) { + TaskManager.IMP.sync(new RunnableVal() { + @Override + public void run(Object value) { + parent.removeMetadata(key, plugin); + } + }); + } + + @Override + public void sendPluginMessage(Plugin source, String channel, byte[] message) { + parent.sendPluginMessage(source, channel, message); + } + + @Override + public Set getListeningPluginChannels() { + return parent.getListeningPluginChannels(); + } + + public BukkitImplAdapter getAdapter() { + return adapter; + } + + @Override + public int getEntityCount() { + return TaskManager.IMP.sync(new RunnableVal() { + @Override + public void run(Integer value) { + this.value = parent.getEntityCount(); + } + }); + } + + @Override + public int getTileEntityCount() { + return TaskManager.IMP.sync(new RunnableVal() { + @Override + public void run(Integer value) { + this.value = parent.getTileEntityCount(); + } + }); + } + + @Override + public int getTickableTileEntityCount() { + return TaskManager.IMP.sync(new RunnableVal() { + @Override + public void run(Integer value) { + this.value = parent.getTickableTileEntityCount(); + } + }); + } + + @Override + public int getChunkCount() { + return TaskManager.IMP.sync(new RunnableVal() { + @Override + public void run(Integer value) { + this.value = parent.getChunkCount(); + } + }); + } + + @Override + public int getPlayerCount() { + return TaskManager.IMP.sync(new RunnableVal() { + @Override + public void run(Integer value) { + this.value = parent.getPlayerCount(); + } + }); + } +} diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/wrapper/state/AsyncSign.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/wrapper/state/AsyncSign.java new file mode 100644 index 000000000..54018dc14 --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/wrapper/state/AsyncSign.java @@ -0,0 +1,54 @@ +package com.boydti.fawe.bukkit.wrapper.state; + +import com.boydti.fawe.bukkit.chat.FancyMessage; +import com.boydti.fawe.bukkit.wrapper.AsyncBlock; +import com.boydti.fawe.bukkit.wrapper.AsyncBlockState; +import com.boydti.fawe.util.ReflectionUtils; +import com.sk89q.jnbt.CompoundTag; +import com.sk89q.jnbt.StringTag; +import com.sk89q.jnbt.Tag; +import java.util.Map; +import org.bukkit.block.Sign; + +public class AsyncSign extends AsyncBlockState implements Sign { + public AsyncSign(AsyncBlock block, int combined) { + super(block, combined); + } + + @Override + public String[] getLines() { + CompoundTag nbt = getNbtData(); + String[] data = new String[4]; + if (nbt != null) { + for (int i = 1; i <= 4; i++) { + data[i - 1] = fromJson(nbt.getString("Text" + i)); + } + } + return data; + } + + private String fromJson(String jsonInput) { + if (jsonInput == null || jsonInput.isEmpty()) return ""; + return FancyMessage.deserialize(jsonInput).toOldMessageFormat(); + } + + private String toJson(String oldInput) { + if (oldInput == null || oldInput.isEmpty()) return ""; + return new FancyMessage("").color(oldInput).toJSONString(); + } + + @Override + public String getLine(int index) throws IndexOutOfBoundsException { + CompoundTag nbt = getNbtData(); + return nbt == null ? null : fromJson(nbt.getString("Text" + (index + 1))); + } + + @Override + public void setLine(int index, String line) throws IndexOutOfBoundsException { + CompoundTag nbt = getNbtData(); + if (nbt != null) { + Map map = ReflectionUtils.getMap(nbt.getValue()); + map.put("Text" + (index + 1), new StringTag(toJson(line))); + } + } +} diff --git a/worldedit-bukkit/src/main/java/com/sk89q/bukkit/util/CommandRegistration.java b/worldedit-bukkit/src/main/java/com/sk89q/bukkit/util/CommandRegistration.java index e7df6832c..0e47b5f9a 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/bukkit/util/CommandRegistration.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/bukkit/util/CommandRegistration.java @@ -35,7 +35,11 @@ import java.util.Set; public class CommandRegistration { static { - Bukkit.getServer().getHelpMap().registerHelpTopicFactory(DynamicPluginCommand.class, new DynamicPluginCommandHelpTopic.Factory()); + try { + Bukkit.getServer().getHelpMap().registerHelpTopicFactory(DynamicPluginCommand.class, new DynamicPluginCommandHelpTopic.Factory()); + } catch (Throwable e) { + e.printStackTrace(); + } } protected final Plugin plugin; diff --git a/worldedit-bukkit/src/main/java/com/sk89q/bukkit/util/FallbackRegistrationListener.java b/worldedit-bukkit/src/main/java/com/sk89q/bukkit/util/FallbackRegistrationListener.java index 73ee10759..fc797bfc4 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/bukkit/util/FallbackRegistrationListener.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/bukkit/util/FallbackRegistrationListener.java @@ -34,9 +34,8 @@ public class FallbackRegistrationListener implements Listener { @EventHandler(ignoreCancelled = true) public void onPlayerCommandPreprocess(PlayerCommandPreprocessEvent event) { - if (commandRegistration.dispatch(event.getPlayer(), event.getMessage())) { + if (commandRegistration.dispatch(event.getPlayer(), event.getMessage().substring(1))) { event.setCancelled(true); } } - -} +} \ No newline at end of file diff --git a/worldedit-bukkit/src/main/java/com/sk89q/wepif/ConfigurationPermissionsResolver.java b/worldedit-bukkit/src/main/java/com/sk89q/wepif/ConfigurationPermissionsResolver.java index d61674c1e..b309265d6 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/wepif/ConfigurationPermissionsResolver.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/wepif/ConfigurationPermissionsResolver.java @@ -19,16 +19,16 @@ package com.sk89q.wepif; -import com.sk89q.util.yaml.YAMLNode; -import com.sk89q.util.yaml.YAMLProcessor; -import org.bukkit.OfflinePlayer; - import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import com.sk89q.util.yaml.YAMLNode; +import com.sk89q.util.yaml.YAMLProcessor; +import org.bukkit.OfflinePlayer; + public class ConfigurationPermissionsResolver implements PermissionsResolver { private YAMLProcessor config; private Map> userPermissionsCache; diff --git a/worldedit-bukkit/src/main/java/com/sk89q/wepif/NijiPermissionsResolver.java b/worldedit-bukkit/src/main/java/com/sk89q/wepif/NijiPermissionsResolver.java index fe23bfa46..182a29faf 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/wepif/NijiPermissionsResolver.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/wepif/NijiPermissionsResolver.java @@ -19,7 +19,6 @@ package com.sk89q.wepif; -import com.nijikokun.bukkit.Permissions.Permissions; import com.sk89q.util.yaml.YAMLProcessor; import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; @@ -28,6 +27,7 @@ import org.bukkit.command.PluginCommand; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.PluginManager; +import com.nijikokun.bukkit.Permissions.Permissions; import java.util.logging.Level; import java.util.logging.Logger; diff --git a/worldedit-bukkit/src/main/java/com/sk89q/wepif/PermissionsResolverManager.java b/worldedit-bukkit/src/main/java/com/sk89q/wepif/PermissionsResolverManager.java index 01f04546b..27091ad84 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/wepif/PermissionsResolverManager.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/wepif/PermissionsResolverManager.java @@ -21,6 +21,7 @@ package com.sk89q.wepif; import com.sk89q.util.yaml.YAMLFormat; import com.sk89q.util.yaml.YAMLProcessor; +import com.sk89q.worldedit.world.registry.LegacyMapper; import org.bukkit.OfflinePlayer; import org.bukkit.Server; import org.bukkit.event.EventHandler; @@ -102,7 +103,6 @@ public class PermissionsResolverManager implements PermissionsResolver { protected PermissionsResolverManager(Plugin plugin) { this.server = plugin.getServer(); (new ServerListener()).register(plugin); // Register the events - loadConfig(new File("wepif.yml")); findResolver(); } diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitAdapter.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitAdapter.java index f1181cf50..ed23a4ff4 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitAdapter.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitAdapter.java @@ -21,13 +21,12 @@ package com.sk89q.worldedit.bukkit; import static com.google.common.base.Preconditions.checkNotNull; -import com.google.common.base.Function; +import com.bekvon.bukkit.residence.commands.material; import com.sk89q.worldedit.Vector; -import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.entity.Entity; -import com.sk89q.worldedit.extension.input.InputParseException; import com.sk89q.worldedit.extension.input.ParserContext; +import com.sk89q.worldedit.registry.state.Property; import com.sk89q.worldedit.util.Location; import com.sk89q.worldedit.world.World; import com.sk89q.worldedit.world.block.BlockState; @@ -40,24 +39,19 @@ import com.sk89q.worldedit.world.item.ItemType; import com.sk89q.worldedit.world.item.ItemTypes; import org.bukkit.Bukkit; import org.bukkit.Material; +import org.bukkit.NamespacedKey; import org.bukkit.block.data.BlockData; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; -import java.util.HashMap; -import java.util.Map; +import java.util.List; import java.util.Objects; -import javax.annotation.Nullable; - /** * Adapts between Bukkit and WorldEdit equivalent objects. */ public class BukkitAdapter { - private BukkitAdapter() { - } - private static final ParserContext TO_BLOCK_CONTEXT = new ParserContext(); static { @@ -107,26 +101,6 @@ public class BukkitAdapter { return new BukkitWorld(world); } - /** - * Create a WorldEdit Player from a Bukkit Player. - * - * @param player The Bukkit player - * @return The WorldEdit player - */ - public static BukkitPlayer adapt(Player player) { - return WorldEditPlugin.getInstance().wrapPlayer(player); - } - - /** - * Create a Bukkit Player from a WorldEdit Player. - * - * @param player The WorldEdit player - * @return The Bukkit player - */ - public static Player adapt(com.sk89q.worldedit.entity.Player player) { - return ((BukkitPlayer) player).getPlayer(); - } - /** * Create a Bukkit world from a WorldEdit world. * @@ -247,6 +221,8 @@ public class BukkitAdapter { return Material.getMaterial(itemType.getId().replace("minecraft:", "").toUpperCase()); } + private static boolean test; + /** * Create a Bukkit Material form a WorldEdit BlockType * @@ -258,7 +234,8 @@ public class BukkitAdapter { if (!blockType.getId().startsWith("minecraft:")) { throw new IllegalArgumentException("Bukkit only supports Minecraft blocks"); } - return Material.getMaterial(blockType.getId().replace("minecraft:", "").toUpperCase()); + String id = blockType.getId().substring(10).toUpperCase(); + return Material.getMaterial(id); } /** @@ -281,11 +258,18 @@ public class BukkitAdapter { public static BlockType asBlockType(Material material) { checkNotNull(material); if (!material.isBlock()) { - throw new IllegalArgumentException(material.getKey().toString() + " is not a block!"); + throw new IllegalArgumentException(material.getKey().toString() + " is not a block!") { + @Override + public synchronized Throwable fillInStackTrace() { + return this; + } + }; } return BlockTypes.get(material.getKey().toString()); } + + /** * Converts a Material to a ItemType * @@ -293,35 +277,25 @@ public class BukkitAdapter { * @return The itemtype */ public static ItemType asItemType(Material material) { - checkNotNull(material); - if (!material.isItem()) { - throw new IllegalArgumentException(material.getKey().toString() + " is not an item!"); - } - return ItemTypes.get(material.getKey().toString()); + return CachedBukkitAdapter.asItemType(material); } - private static Map blockStateCache = new HashMap<>(); - /** - * Create a WorldEdit BlockState from a Bukkit BlockData + * Create a WorldEdit BlockStateHolder from a Bukkit BlockData * * @param blockData The Bukkit BlockData * @return The WorldEdit BlockState */ public static BlockState adapt(BlockData blockData) { - checkNotNull(blockData); - return blockStateCache.computeIfAbsent(blockData.getAsString(), new Function() { - @Nullable - @Override - public BlockState apply(@Nullable String input) { - try { - return WorldEdit.getInstance().getBlockFactory().parseFromInput(input, TO_BLOCK_CONTEXT).toImmutableState(); - } catch (InputParseException e) { - e.printStackTrace(); - return null; - } - } - }); + return CachedBukkitAdapter.adapt(blockData); + } + + public static BlockData getBlockData(int combinedId) { + return CachedBukkitAdapter.getBlockData(combinedId); + } + + public static BlockTypes adapt(Material material) { + return CachedBukkitAdapter.adapt(material); } /** @@ -331,17 +305,16 @@ public class BukkitAdapter { * @return The Bukkit BlockData */ public static BlockData adapt(BlockStateHolder block) { - checkNotNull(block); - return Bukkit.createBlockData(block.getAsString()); + return CachedBukkitAdapter.adapt(block); } /** - * Create a WorldEdit BlockState from a Bukkit ItemStack + * Create a WorldEdit BlockStateHolder from a Bukkit ItemStack * * @param itemStack The Bukkit ItemStack * @return The WorldEdit BlockState */ - public static BlockState asBlockState(ItemStack itemStack) { + public static BlockStateHolder asBlockState(ItemStack itemStack) { checkNotNull(itemStack); if (itemStack.getType().isBlock()) { return adapt(itemStack.getType().createBlockData()); @@ -358,6 +331,8 @@ public class BukkitAdapter { */ public static BaseItemStack adapt(ItemStack itemStack) { checkNotNull(itemStack); + + return new BaseItemStack(ItemTypes.get(itemStack.getType().getKey().toString()), itemStack.getAmount()); } @@ -371,4 +346,23 @@ public class BukkitAdapter { checkNotNull(item); return new ItemStack(adapt(item.getType()), item.getAmount()); } + + /** + * Create a WorldEdit Player from a Bukkit Player. + * + * @param player The Bukkit player + * @return The WorldEdit player + */ + public static BukkitPlayer adapt(Player player) { + return WorldEditPlugin.getInstance().wrapPlayer(player); + } + /** + * Create a Bukkit Player from a WorldEdit Player. + * + * @param player The WorldEdit player + * @return The Bukkit player + */ + public static Player adapt(com.sk89q.worldedit.entity.Player player) { + return ((BukkitPlayer) player).getPlayer(); + } } diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitBiomeRegistry.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitBiomeRegistry.java index cb0fea4de..8a69eaf31 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitBiomeRegistry.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitBiomeRegistry.java @@ -25,12 +25,11 @@ import com.sk89q.worldedit.world.biome.BiomeData; import com.sk89q.worldedit.world.registry.BiomeRegistry; import org.bukkit.block.Biome; +import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import javax.annotation.Nullable; - /** * A biome registry for Bukkit. */ diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitBlockRegistry.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitBlockRegistry.java index 6f272af72..d44624da3 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitBlockRegistry.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitBlockRegistry.java @@ -19,13 +19,21 @@ package com.sk89q.worldedit.bukkit; -import com.sk89q.worldedit.world.registry.BlockMaterial; +import com.bekvon.bukkit.residence.commands.material; +import com.sk89q.worldedit.blocks.BlockMaterial; +import com.sk89q.worldedit.command.tool.BlockDataCyler; import com.sk89q.worldedit.registry.state.Property; +import com.sk89q.worldedit.world.block.BlockStateHolder; import com.sk89q.worldedit.world.block.BlockType; +import com.sk89q.worldedit.world.block.BlockTypes; import com.sk89q.worldedit.world.registry.BundledBlockRegistry; +import com.sk89q.worldedit.world.registry.LegacyMapper; import com.sk89q.worldedit.world.registry.PassthroughBlockMaterial; import org.bukkit.Material; +import org.bukkit.block.data.BlockData; +import java.util.ArrayList; +import java.util.Collection; import java.util.EnumMap; import java.util.Map; @@ -38,7 +46,11 @@ public class BukkitBlockRegistry extends BundledBlockRegistry { @Nullable @Override public BlockMaterial getMaterial(BlockType blockType) { - return materialMap.computeIfAbsent(BukkitAdapter.adapt(blockType), + Material type = BukkitAdapter.adapt(blockType); + if (type == null) { + type = Material.AIR; + } + return materialMap.computeIfAbsent(type, material -> new BukkitBlockMaterial(BukkitBlockRegistry.super.getMaterial(blockType), material)); } @@ -75,4 +87,16 @@ public class BukkitBlockRegistry extends BundledBlockRegistry { return material.isTransparent(); } } + + @Override + public Collection registerBlocks() { + ArrayList blocks = new ArrayList<>(); + for (Material m : Material.values()) { + if (!m.isLegacy() && m.isBlock()) { + BlockData blockData = m.createBlockData(); + blocks.add(blockData.getAsString()); + } + } + return blocks; + } } diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitCommandInspector.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitCommandInspector.java index b392c50ac..8f4b8e5ae 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitCommandInspector.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitCommandInspector.java @@ -19,8 +19,6 @@ package com.sk89q.worldedit.bukkit; -import static com.google.common.base.Preconditions.checkNotNull; - import com.sk89q.bukkit.util.CommandInspector; import com.sk89q.minecraft.util.commands.CommandLocals; import com.sk89q.worldedit.extension.platform.Actor; @@ -32,6 +30,8 @@ import org.bukkit.command.CommandSender; import java.util.logging.Logger; +import static com.google.common.base.Preconditions.checkNotNull; + class BukkitCommandInspector implements CommandInspector { private static final Logger logger = Logger.getLogger(BukkitCommandInspector.class.getCanonicalName()); diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitCommandSender.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitCommandSender.java index 55929af02..3d289df5d 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitCommandSender.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitCommandSender.java @@ -19,22 +19,24 @@ package com.sk89q.worldedit.bukkit; -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; - -import com.sk89q.worldedit.extension.platform.Actor; -import com.sk89q.worldedit.internal.cui.CUIEvent; +import com.sk89q.worldedit.entity.metadata.Metadatable; import com.sk89q.worldedit.session.SessionKey; import com.sk89q.worldedit.util.auth.AuthorizationException; +import com.sk89q.worldedit.extension.platform.Actor; +import com.sk89q.worldedit.internal.cui.CUIEvent; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; -import java.io.File; -import java.util.UUID; - import javax.annotation.Nullable; +import java.io.File; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; -public class BukkitCommandSender implements Actor { +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +public class BukkitCommandSender implements Actor, Metadatable { /** * One time generated ID. @@ -43,6 +45,7 @@ public class BukkitCommandSender implements Actor { private CommandSender sender; private WorldEditPlugin plugin; + private ConcurrentHashMap meta; public BukkitCommandSender(WorldEditPlugin plugin, CommandSender sender) { checkNotNull(plugin); @@ -53,6 +56,12 @@ public class BukkitCommandSender implements Actor { this.sender = sender; } + @Override + public synchronized Map getMetaMap() { + if (meta == null) meta = new ConcurrentHashMap<>(); + return meta; + } + @Override public UUID getUniqueId() { return DEFAULT_ID; diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitEntity.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitEntity.java index 418980ad7..085f6c8b8 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitEntity.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitEntity.java @@ -19,8 +19,6 @@ package com.sk89q.worldedit.bukkit; -import static com.google.common.base.Preconditions.checkNotNull; - import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; import com.sk89q.worldedit.entity.BaseEntity; import com.sk89q.worldedit.entity.Entity; @@ -30,9 +28,10 @@ import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.util.Location; import com.sk89q.worldedit.world.NullWorld; +import javax.annotation.Nullable; import java.lang.ref.WeakReference; -import javax.annotation.Nullable; +import static com.google.common.base.Preconditions.checkNotNull; /** * An adapter to adapt a Bukkit entity into a WorldEdit one. diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitEntityProperties.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitEntityProperties.java index 86618f62f..0e20172bd 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitEntityProperties.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitEntityProperties.java @@ -19,8 +19,6 @@ package com.sk89q.worldedit.bukkit; -import static com.google.common.base.Preconditions.checkNotNull; - import com.sk89q.worldedit.entity.metadata.EntityProperties; import com.sk89q.worldedit.util.Enums; import org.bukkit.entity.Ambient; @@ -44,6 +42,8 @@ import org.bukkit.entity.Tameable; import org.bukkit.entity.Villager; import org.bukkit.entity.minecart.ExplosiveMinecart; +import static com.google.common.base.Preconditions.checkNotNull; + class BukkitEntityProperties implements EntityProperties { private static final org.bukkit.entity.EntityType armorStandType = diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitItemRegistry.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitItemRegistry.java new file mode 100644 index 000000000..9d7d68d08 --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitItemRegistry.java @@ -0,0 +1,18 @@ +package com.sk89q.worldedit.bukkit; + +import com.sk89q.worldedit.world.registry.BundledItemRegistry; +import org.bukkit.Material; + +import java.util.ArrayList; +import java.util.Collection; + +public class BukkitItemRegistry extends BundledItemRegistry { + @Override + public Collection registerItems() { + ArrayList items = new ArrayList<>(); + for (Material m : Material.values()) { + if (!m.isLegacy() && m.isItem()) items.add(m.getKey().getNamespace() + ":" + m.getKey().getKey()); + } + return items; + } +} diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitItemStack.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitItemStack.java new file mode 100644 index 000000000..a84772161 --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitItemStack.java @@ -0,0 +1,60 @@ +package com.sk89q.worldedit.bukkit; + +import com.boydti.fawe.Fawe; +import com.boydti.fawe.bukkit.FaweBukkit; +import com.boydti.fawe.bukkit.util.ItemUtil; +import com.sk89q.jnbt.CompoundTag; +import com.sk89q.worldedit.blocks.BaseItemStack; +import com.sk89q.worldedit.world.item.ItemType; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +import javax.annotation.Nullable; + +public class BukkitItemStack extends BaseItemStack { + private ItemStack stack; + private boolean loadedNBT; + + public BukkitItemStack(ItemStack stack) { + super(BukkitAdapter.asItemType(stack.getType())); + this.stack = stack; + } + + public BukkitItemStack(ItemType type, ItemStack stack) { + super(type); + this.stack = stack; + } + + @Override + public int getAmount() { + return stack.getAmount(); + } + + @Nullable + @Override + public Object getNativeItem() { + return super.getNativeItem(); + } + + @Nullable + @Override + public CompoundTag getNbtData() { + if (!loadedNBT) { + loadedNBT = true; + ItemUtil util = Fawe.imp().getItemUtil(); + if (util != null) { + super.setNbtData(util.getNBT(stack)); + } + } + return super.getNbtData(); + } + + @Override + public void setNbtData(@Nullable CompoundTag nbtData) { + ItemUtil util = Fawe.imp().getItemUtil(); + if (util != null) { + stack = util.setNBT(stack, nbtData); + } + super.setNbtData(nbtData); + } +} diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitPlayer.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitPlayer.java index 7066da770..c4ee5348f 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitPlayer.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitPlayer.java @@ -22,7 +22,8 @@ package com.sk89q.worldedit.bukkit; import com.sk89q.util.StringUtil; import com.sk89q.worldedit.Vector; import com.sk89q.worldedit.WorldEditException; -import com.sk89q.worldedit.world.block.BaseBlock; +import com.sk89q.worldedit.blocks.BaseBlock; +import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.entity.BaseEntity; import com.sk89q.worldedit.extension.platform.AbstractPlayerActor; @@ -70,7 +71,7 @@ public class BukkitPlayer extends AbstractPlayerActor { ItemStack itemStack = handSide == HandSide.MAIN_HAND ? player.getInventory().getItemInMainHand() : player.getInventory().getItemInOffHand(); - return BukkitAdapter.asBlockState(itemStack).toBaseBlock(); + return new BaseBlock(BukkitAdapter.asBlockState(itemStack)); } @Override @@ -156,7 +157,7 @@ public class BukkitPlayer extends AbstractPlayerActor { if (params.length > 0) { send = send + "|" + StringUtil.joinString(params, "|"); } - player.sendPluginMessage(plugin, WorldEditPlugin.CUI_PLUGIN_CHANNEL, send.getBytes(CUIChannelListener.UTF_8_CHARSET)); + player.sendPluginMessage(plugin, WorldEditPlugin.getCuiPluginChannel(), send.getBytes(CUIChannelListener.UTF_8_CHARSET)); } public Player getPlayer() { diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitPlayerBlockBag.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitPlayerBlockBag.java index 7334a48c8..64b5f96e9 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitPlayerBlockBag.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitPlayerBlockBag.java @@ -19,18 +19,17 @@ package com.sk89q.worldedit.bukkit; +import com.sk89q.worldedit.blocks.BaseItem; import com.sk89q.worldedit.blocks.BaseItemStack; -import com.sk89q.worldedit.extent.inventory.BlockBag; -import com.sk89q.worldedit.extent.inventory.BlockBagException; -import com.sk89q.worldedit.extent.inventory.OutOfBlocksException; -import com.sk89q.worldedit.extent.inventory.OutOfSpaceException; -import com.sk89q.worldedit.util.Location; +import com.sk89q.worldedit.extent.inventory.*; import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.block.BlockStateHolder; import com.sk89q.worldedit.world.block.BlockTypes; +import com.sk89q.worldedit.util.Location; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; -public class BukkitPlayerBlockBag extends BlockBag { +public class BukkitPlayerBlockBag extends BlockBag implements SlottableBlockBag { private Player player; private ItemStack[] items; @@ -181,4 +180,16 @@ public class BukkitPlayerBlockBag extends BlockBag { public void addSingleSourcePosition(Location pos) { } + @Override + public BaseItem getItem(int slot) { + loadInventory(); + return BukkitAdapter.adapt(items[slot]); + } + + @Override + public void setItem(int slot, BaseItem block) { + loadInventory(); + BaseItemStack stack = block instanceof BaseItemStack ? (BaseItemStack) block : new BaseItemStack(block.getType(), block.getNbtData(), 1); + items[slot] = BukkitAdapter.adapt(stack); + } } diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitRegistries.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitRegistries.java index 6ed0999c7..e6409c557 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitRegistries.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitRegistries.java @@ -22,6 +22,7 @@ package com.sk89q.worldedit.bukkit; import com.sk89q.worldedit.world.registry.BiomeRegistry; import com.sk89q.worldedit.world.registry.BlockRegistry; import com.sk89q.worldedit.world.registry.BundledRegistries; +import com.sk89q.worldedit.world.registry.ItemRegistry; /** * World data for the Bukkit platform. @@ -30,6 +31,7 @@ class BukkitRegistries extends BundledRegistries { private static final BukkitRegistries INSTANCE = new BukkitRegistries(); private final BlockRegistry blockRegistry = new BukkitBlockRegistry(); + private final ItemRegistry itemRegistry = new BukkitItemRegistry(); private final BiomeRegistry biomeRegistry = new BukkitBiomeRegistry(); /** @@ -48,6 +50,11 @@ class BukkitRegistries extends BundledRegistries { return biomeRegistry; } + @Override + public ItemRegistry getItemRegistry() { + return itemRegistry; + } + /** * Get a static instance. * diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitServerInterface.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitServerInterface.java index 3e8f4bcf9..4db6408f1 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitServerInterface.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitServerInterface.java @@ -100,7 +100,7 @@ public class BukkitServerInterface implements MultiUserPlatform { return player; } else { org.bukkit.entity.Player bukkitPlayer = server.getPlayerExact(player.getName()); - return bukkitPlayer != null ? new BukkitPlayer(plugin, bukkitPlayer) : null; + return bukkitPlayer != null ? plugin.wrapPlayer(bukkitPlayer) : null; } } @@ -177,7 +177,7 @@ public class BukkitServerInterface implements MultiUserPlatform { public Collection getConnectedUsers() { List users = new ArrayList<>(); for (org.bukkit.entity.Player player : Bukkit.getServer().getOnlinePlayers()) { - users.add(new BukkitPlayer(plugin, player)); + users.add(plugin.wrapPlayer(player)); } return users; } diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitWorld.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitWorld.java index c0d16dddb..3fd027903 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitWorld.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitWorld.java @@ -19,16 +19,12 @@ package com.sk89q.worldedit.bukkit; -import static com.google.common.base.Preconditions.checkNotNull; - -import com.sk89q.worldedit.BlockVector2D; -import com.sk89q.worldedit.EditSession; +import com.boydti.fawe.Fawe; +import com.sk89q.worldedit.*; import com.sk89q.worldedit.Vector; -import com.sk89q.worldedit.Vector2D; -import com.sk89q.worldedit.WorldEdit; -import com.sk89q.worldedit.WorldEditException; -import com.sk89q.worldedit.world.block.BaseBlock; +import com.sk89q.worldedit.blocks.BaseBlock; import com.sk89q.worldedit.blocks.BaseItemStack; +import com.sk89q.worldedit.blocks.LazyBlock; import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; import com.sk89q.worldedit.entity.BaseEntity; import com.sk89q.worldedit.history.change.BlockChange; @@ -47,19 +43,17 @@ import org.bukkit.block.Block; import org.bukkit.block.BlockState; import org.bukkit.block.Chest; import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; import org.bukkit.inventory.DoubleChestInventory; import org.bukkit.inventory.Inventory; +import javax.annotation.Nullable; import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.EnumMap; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; -import javax.annotation.Nullable; +import static com.google.common.base.Preconditions.checkNotNull; public class BukkitWorld extends AbstractWorld { @@ -91,7 +85,7 @@ public class BukkitWorld extends AbstractWorld { List entities = new ArrayList<>(); for (Entity ent : ents) { if (region.contains(BukkitAdapter.asVector(ent.getLocation()))) { - entities.add(BukkitAdapter.adapt(ent)); + addEntities(ent, entities); } } return entities; @@ -101,11 +95,43 @@ public class BukkitWorld extends AbstractWorld { public List getEntities() { List list = new ArrayList<>(); for (Entity entity : getWorld().getEntities()) { - list.add(BukkitAdapter.adapt(entity)); + addEntities(entity, list); } return list; } + private static com.sk89q.worldedit.entity.Entity adapt(Entity ent) { + if (ent == null) return null; + return BukkitAdapter.adapt(ent); + } + + private void addEntities(Entity ent, Collection ents) { + ents.add(BukkitAdapter.adapt(ent)); + if (ent instanceof Player) { + final Player plr = (Player) ent; + com.sk89q.worldedit.entity.Entity left = adapt(((Player) ent).getShoulderEntityLeft()); + com.sk89q.worldedit.entity.Entity right = adapt(((Player) ent).getShoulderEntityRight()); + if (left != null) { + ents.add(new DelegateEntity(left) { + @Override + public boolean remove() { + plr.setShoulderEntityLeft(null); + return true; + } + }); + } + if (right != null) { + ents.add(new DelegateEntity(right) { + @Override + public boolean remove() { + plr.setShoulderEntityRight(null); + return true; + } + }); + } + } + } + @Nullable @Override public com.sk89q.worldedit.entity.Entity createEntity(com.sk89q.worldedit.util.Location location, BaseEntity entity) { @@ -165,7 +191,7 @@ public class BukkitWorld extends AbstractWorld { @Override public boolean regenerate(Region region, EditSession editSession) { - BlockStateHolder[] history = new BlockStateHolder[16 * 16 * (getMaxY() + 1)]; + com.sk89q.worldedit.world.block.BlockStateHolder[] history = new com.sk89q.worldedit.world.block.BlockState[16 * 16 * (getMaxY() + 1)]; for (Vector2D chunk : region.getChunks()) { Vector min = new Vector(chunk.getBlockX() * 16, 0, chunk.getBlockZ() * 16); @@ -415,15 +441,15 @@ public class BukkitWorld extends AbstractWorld { } @Override - public boolean setBlock(Vector position, BlockStateHolder block, boolean notifyAndLight) throws WorldEditException { + public boolean setBlock(Vector position, BlockStateHolder block, boolean notifyAndLight) { BukkitImplAdapter adapter = WorldEditPlugin.getInstance().getBukkitImplAdapter(); if (adapter != null) { try { return adapter.setBlock(BukkitAdapter.adapt(getWorld(), position), block, notifyAndLight); } catch (Exception e) { - if (block instanceof BaseBlock && ((BaseBlock) block).getNbtData() != null) { + if (block.getNbtData() != null) { logger.warning("Tried to set a corrupt tile entity at " + position.toString()); - logger.warning(((BaseBlock) block).getNbtData().toString()); + logger.warning(block.getNbtData().toString()); } e.printStackTrace(); Block bukkitBlock = getWorld().getBlockAt(position.getBlockX(), position.getBlockY(), position.getBlockZ()); @@ -432,18 +458,23 @@ public class BukkitWorld extends AbstractWorld { } } else { Block bukkitBlock = getWorld().getBlockAt(position.getBlockX(), position.getBlockY(), position.getBlockZ()); - bukkitBlock.setBlockData(BukkitAdapter.adapt(block), notifyAndLight); + bukkitBlock.setBlockData(BukkitAdapter.adapt(block), false); return true; } } @Override - public BaseBlock getFullBlock(Vector position) { + public com.sk89q.worldedit.world.block.BlockState getLazyBlock(Vector position) { + return getBlock(position); + } + + @Override + public com.sk89q.worldedit.world.block.BlockState getFullBlock(Vector position) { BukkitImplAdapter adapter = WorldEditPlugin.getInstance().getBukkitImplAdapter(); if (adapter != null) { return adapter.getBlock(BukkitAdapter.adapt(getWorld(), position)); } else { - return getBlock(position).toBaseBlock(); + return getBlock(position); } } diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/CachedBukkitAdapter.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/CachedBukkitAdapter.java new file mode 100644 index 000000000..d1e7244f3 --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/CachedBukkitAdapter.java @@ -0,0 +1,105 @@ +package com.sk89q.worldedit.bukkit; + +import com.sk89q.worldedit.registry.state.Property; +import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.block.BlockStateHolder; +import com.sk89q.worldedit.world.block.BlockTypes; +import com.sk89q.worldedit.world.item.ItemType; +import com.sk89q.worldedit.world.item.ItemTypes; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.block.data.BlockData; + +import java.util.List; + +import static com.google.common.base.Preconditions.checkNotNull; + +public class CachedBukkitAdapter { + private static final BlockData[][] blockDataCache = new BlockData[BlockTypes.size()][]; + private static final ItemTypes[] itemTypes; + private static final BlockTypes[] blockTypes; + static { + Material[] materials = Material.values(); + itemTypes = new ItemTypes[materials.length]; + blockTypes = new BlockTypes[materials.length]; + for (int i = 0; i < materials.length; i++) { + Material material = materials[i]; + if (material.isBlock() && !material.isLegacy()) { + NamespacedKey key = material.getKey(); + blockTypes[i] = BlockTypes.get(key.getNamespace() + ":" + key.getKey()); + } else if (material.isItem() && !material.isLegacy()) { + itemTypes[i] = ItemTypes.get(material.getKey().toString()); + } + } + } + + /** + * Converts a Material to a ItemType + * + * @param material The material + * @return The itemtype + */ + public static ItemType asItemType(Material material) { + return itemTypes[material.ordinal()]; + } + + /** + * Create a WorldEdit BlockStateHolder from a Bukkit BlockData + * + * @param blockData The Bukkit BlockData + * @return The WorldEdit BlockState + */ + public static BlockState adapt(BlockData blockData) { + checkNotNull(blockData); + Material material = blockData.getMaterial(); + BlockTypes type = blockTypes[material.ordinal()]; + + List propList = type.getProperties(); + if (propList.size() == 0) return type.getDefaultState(); + String properties = blockData.getAsString(); + + return BlockState.get(type, properties, type.getDefaultState().getInternalPropertiesId()); + } + + public static BlockData getBlockData(int combinedId) { + int typeId = combinedId & BlockTypes.BIT_MASK; + BlockData[] dataCache = blockDataCache[typeId]; + if (dataCache == null) { + BlockTypes type = BlockTypes.get(typeId); + blockDataCache[typeId] = dataCache = new BlockData[type.getMaxStateId() + 1]; + } + int propId = combinedId >> BlockTypes.BIT_OFFSET; + BlockData blockData = dataCache[propId]; + if (blockData == null) { + dataCache[propId] = blockData = Bukkit.createBlockData(BlockState.get(combinedId).getAsString()); + } + return blockData; + } + + public static BlockTypes adapt(Material material) { + return blockTypes[material.ordinal()]; + } + + /** + * Create a Bukkit BlockData from a WorldEdit BlockStateHolder + * + * @param block The WorldEdit BlockStateHolder + * @return The Bukkit BlockData + */ + public static BlockData adapt(BlockStateHolder block) { + checkNotNull(block); + int typeId = block.getInternalBlockTypeId(); + BlockData[] dataCache = blockDataCache[typeId]; + if (dataCache == null) { + BlockTypes type = BlockTypes.get(typeId); + blockDataCache[typeId] = dataCache = new BlockData[type.getMaxStateId() + 1]; + } + int propId = block.getInternalPropertiesId(); + BlockData blockData = dataCache[propId]; + if (blockData == null) { + dataCache[propId] = blockData = Bukkit.createBlockData(block.getAsString()); + } + return blockData; + } +} diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/DelegateEntity.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/DelegateEntity.java new file mode 100644 index 000000000..26cb9847e --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/DelegateEntity.java @@ -0,0 +1,47 @@ +package com.sk89q.worldedit.bukkit; + +import com.sk89q.worldedit.entity.BaseEntity; +import com.sk89q.worldedit.entity.Entity; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.util.Location; + +import javax.annotation.Nullable; + +public class DelegateEntity implements Entity { + private final Entity parent; + + public DelegateEntity(Entity parent) { + this.parent = parent; + } + + public Entity getParent() { + return parent; + } + + @Override + @Nullable + public BaseEntity getState() { + return parent.getState(); + } + + @Override + public Location getLocation() { + return parent.getLocation(); + } + + @Override + public Extent getExtent() { + return parent.getExtent(); + } + + @Override + public boolean remove() { + return parent.remove(); + } + + @Override + @Nullable + public T getFacet(Class cls) { + return parent.getFacet(cls); + } +} diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java index 057742b92..91de23487 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java @@ -21,6 +21,11 @@ package com.sk89q.worldedit.bukkit; import static com.google.common.base.Preconditions.checkNotNull; +import com.bekvon.bukkit.residence.commands.message; +import com.bekvon.bukkit.residence.containers.cmd; +import com.boydti.fawe.Fawe; +import com.boydti.fawe.bukkit.FaweBukkit; +import com.boydti.fawe.bukkit.adapter.Spigot_v1_13_R1; import com.google.common.base.Joiner; import com.sk89q.util.yaml.YAMLProcessor; import com.sk89q.wepif.PermissionsResolverManager; @@ -35,20 +40,30 @@ import com.sk89q.worldedit.event.platform.CommandSuggestionEvent; import com.sk89q.worldedit.event.platform.PlatformReadyEvent; import com.sk89q.worldedit.extension.platform.Actor; import com.sk89q.worldedit.extension.platform.Capability; +import com.sk89q.worldedit.extension.platform.NoCapablePlatformException; import com.sk89q.worldedit.extension.platform.Platform; import com.sk89q.worldedit.extent.inventory.BlockBag; +import com.sk89q.worldedit.world.registry.LegacyMapper; +import org.bukkit.Bukkit; +import org.bukkit.Material; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.command.TabCompleter; import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.PluginDescriptionFile; +import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.plugin.java.JavaPluginLoader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; -import java.util.List; +import java.lang.reflect.Field; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; import java.util.jar.JarFile; import java.util.logging.Level; import java.util.logging.Logger; @@ -62,13 +77,62 @@ import javax.annotation.Nullable; public class WorldEditPlugin extends JavaPlugin implements TabCompleter { private static final Logger log = Logger.getLogger(WorldEditPlugin.class.getCanonicalName()); - public static final String CUI_PLUGIN_CHANNEL = "worldedit:cui"; + private static final String CUI_PLUGIN_CHANNEL = "worldedit:cui"; private static WorldEditPlugin INSTANCE; private BukkitImplAdapter bukkitAdapter; private BukkitServerInterface server; private BukkitConfiguration config; + private static Map lookupNames; + static { + { // Disable AWE as otherwise both fail to load + PluginManager manager = Bukkit.getPluginManager(); + try { + Field pluginsField = manager.getClass().getDeclaredField("plugins"); + Field lookupNamesField = manager.getClass().getDeclaredField("lookupNames"); + pluginsField.setAccessible(true); + lookupNamesField.setAccessible(true); + List plugins = (List) pluginsField.get(manager); + lookupNames = (Map) lookupNamesField.get(manager); + pluginsField.set(manager, plugins = new ArrayList(plugins) { + @Override + public boolean add(Plugin plugin) { + if (plugin.getName().startsWith("AsyncWorldEdit")) { + Fawe.debug("Disabling `" + plugin.getName() + "` as it is incompatible"); + } else if (plugin.getName().startsWith("BetterShutdown")) { + Fawe.debug("Disabling `" + plugin.getName() + "` as it is incompatible (Improperly shaded classes from com.sk89q.minecraft.util.commands)"); + } else { + return super.add(plugin); + } + return false; + } + }); + lookupNamesField.set(manager, lookupNames = new ConcurrentHashMap(lookupNames) { + @Override + public Plugin put(String key, Plugin plugin) { + if (plugin.getName().startsWith("AsyncWorldEdit") || plugin.getName().startsWith("BetterShutdown")) { + return null; + } + return super.put(key, plugin); + } + }); + } catch (Throwable ignore) {} + } + } + + public WorldEditPlugin() { + if (lookupNames != null) lookupNames.putIfAbsent("WorldEdit".toLowerCase(Locale.ENGLISH), this); + } + + public WorldEditPlugin(JavaPluginLoader loader, PluginDescriptionFile desc, File dataFolder, File jarFile) { + if (lookupNames != null) lookupNames.putIfAbsent("WorldEdit".toLowerCase(Locale.ENGLISH), this); + } + + public static String getCuiPluginChannel() { + return CUI_PLUGIN_CHANNEL; + } + /** * Called on plugin enable. */ @@ -76,6 +140,7 @@ public class WorldEditPlugin extends JavaPlugin implements TabCompleter { @Override public void onEnable() { this.INSTANCE = this; + FaweBukkit imp = new FaweBukkit(this); //noinspection ResultOfMethodCallIgnored getDataFolder().mkdirs(); @@ -90,11 +155,14 @@ public class WorldEditPlugin extends JavaPlugin implements TabCompleter { worldEdit.loadMappings(); loadConfig(); // Load configuration - PermissionsResolverManager.initialize(this); // Setup permission resolver + fail(() -> PermissionsResolverManager.initialize(INSTANCE), "Failed to initialize permissions resolver"); + // Register CUI - getServer().getMessenger().registerIncomingPluginChannel(this, CUI_PLUGIN_CHANNEL, new CUIChannelListener(this)); - getServer().getMessenger().registerOutgoingPluginChannel(this, CUI_PLUGIN_CHANNEL); + fail(() -> { + getServer().getMessenger().registerIncomingPluginChannel(INSTANCE, CUI_PLUGIN_CHANNEL, new CUIChannelListener(INSTANCE)); + getServer().getMessenger().registerOutgoingPluginChannel(INSTANCE, CUI_PLUGIN_CHANNEL); + }, "Failed to register CUI"); // Now we can register events getServer().getPluginManager().registerEvents(new WorldEditListener(this), this); @@ -103,13 +171,35 @@ public class WorldEditPlugin extends JavaPlugin implements TabCompleter { // Forge WorldEdit and there's (probably) not going to be any other // platforms to be worried about... at the current time of writing WorldEdit.getInstance().getEventBus().post(new PlatformReadyEvent()); + + { // Register 1.13 Material ids with LegacyMapper + LegacyMapper legacyMapper = LegacyMapper.getInstance(); + for (Material m : Material.values()) { + if (!m.isLegacy() && m.isBlock()) { + legacyMapper.register(m.getId(), 0, BukkitAdapter.adapt(m).getDefaultState()); + } + } + } + } + + private void fail(Runnable run, String message) { + try { + run.run(); + } catch (Throwable e) { + log.log(Level.SEVERE, message); + e.printStackTrace(); + } } private void loadConfig() { - createDefaultConfiguration("config.yml"); // Create the default configuration file - - config = new BukkitConfiguration(new YAMLProcessor(new File(getDataFolder(), "config.yml"), true), this); - config.load(); + createDefaultConfiguration("config-legacy.yml"); // Create the default configuration file + try { + config = new BukkitConfiguration(new YAMLProcessor(new File(getDataFolder(), "config-legacy.yml"), true), this); + config.load(); + } catch (Throwable e) { + log.log(Level.SEVERE, "Failed to load config.yml"); + e.printStackTrace(); + } } private void loadAdapter() { @@ -117,6 +207,7 @@ public class WorldEditPlugin extends JavaPlugin implements TabCompleter { // Attempt to load a Bukkit adapter BukkitImplLoader adapterLoader = new BukkitImplLoader(); + adapterLoader.addClass(Spigot_v1_13_R1.class); try { adapterLoader.addFromPath(getClass().getClassLoader()); @@ -133,14 +224,18 @@ public class WorldEditPlugin extends JavaPlugin implements TabCompleter { bukkitAdapter = adapterLoader.loadAdapter(); log.log(Level.INFO, "Using " + bukkitAdapter.getClass().getCanonicalName() + " as the Bukkit adapter"); } catch (AdapterLoadException e) { - Platform platform = worldEdit.getPlatformManager().queryCapability(Capability.WORLD_EDITING); - if (platform instanceof BukkitServerInterface) { - log.log(Level.WARNING, e.getMessage()); - } else { - log.log(Level.INFO, "WorldEdit could not find a Bukkit adapter for this MC version, " + - "but it seems that you have another implementation of WorldEdit installed (" + platform.getPlatformName() + ") " + - "that handles the world editing."); - } + try { + Platform platform = worldEdit.getPlatformManager().queryCapability(Capability.WORLD_EDITING); + if (platform instanceof BukkitServerInterface) { + log.log(Level.WARNING, e.getMessage()); + return; + } else { + log.log(Level.INFO, "WorldEdit could not find a Bukkit adapter for this MC version, " + + "but it seems that you have another implementation of WorldEdit installed (" + platform.getPlatformName() + ") " + + "that handles the world editing."); + } + } catch (NoCapablePlatformException ignore) {} + log.log(Level.INFO, "WorldEdit could not find a Bukkit adapter for this MC version"); } } @@ -149,6 +244,7 @@ public class WorldEditPlugin extends JavaPlugin implements TabCompleter { */ @Override public void onDisable() { + Fawe.get().onDisable(); WorldEdit worldEdit = WorldEdit.getInstance(); worldEdit.getSessionManager().clear(); worldEdit.getPlatformManager().unregister(server); @@ -342,7 +438,7 @@ public class WorldEditPlugin extends JavaPlugin implements TabCompleter { * @return an instance of the plugin * @throws NullPointerException if the plugin hasn't been enabled */ - static WorldEditPlugin getInstance() { + public static WorldEditPlugin getInstance() { return checkNotNull(INSTANCE); } @@ -352,7 +448,7 @@ public class WorldEditPlugin extends JavaPlugin implements TabCompleter { * @return the adapter */ @Nullable - BukkitImplAdapter getBukkitImplAdapter() { + public BukkitImplAdapter getBukkitImplAdapter() { return bukkitAdapter; } diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/BukkitImplAdapter.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/BukkitImplAdapter.java index 4a30a11c0..36b298bb2 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/BukkitImplAdapter.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/BukkitImplAdapter.java @@ -19,11 +19,15 @@ package com.sk89q.worldedit.bukkit.adapter; -import com.sk89q.worldedit.world.block.BaseBlock; -import com.sk89q.worldedit.entity.BaseEntity; +import com.sk89q.jnbt.Tag; +import com.sk89q.worldedit.blocks.BaseBlock; +import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.registry.state.Property; import com.sk89q.worldedit.world.block.BlockStateHolder; +import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.entity.BaseEntity; import com.sk89q.worldedit.world.block.BlockType; +import net.minecraft.server.v1_13_R1.NBTBase; import org.bukkit.Location; import org.bukkit.block.Biome; import org.bukkit.entity.Entity; @@ -35,7 +39,7 @@ import javax.annotation.Nullable; /** * An interface for adapters of various Bukkit implementations. */ -public interface BukkitImplAdapter { +public interface BukkitImplAdapter { /** * Get the biome ID for the given biome. @@ -63,7 +67,7 @@ public interface BukkitImplAdapter { * @param location the location * @return the block */ - BaseBlock getBlock(Location location); + BlockState getBlock(Location location); /** * Set the block at the given location. @@ -101,4 +105,12 @@ public interface BukkitImplAdapter { * @return The properties map */ Map getProperties(BlockType blockType); + + default Tag toNative(T foreign) { + return null; + } + + default T fromNative(Tag foreign) { + return null; + } } diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/BukkitImplLoader.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/BukkitImplLoader.java index 40a94baf1..719e33b06 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/BukkitImplLoader.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/BukkitImplLoader.java @@ -77,6 +77,10 @@ public class BukkitImplLoader { } } + public void addClass(Class cls) { + adapterCandidates.add(0, cls.getName()); + } + /** * Search the given JAR for candidate implementations. * diff --git a/worldedit-bukkit/src/main/resources/com/sk89q/worldedit/bukkit/adapter/impl/Spigot_v1_13_R1.class b/worldedit-bukkit/src/main/resources/com/sk89q/worldedit/bukkit/adapter/impl/Spigot_v1_13_R1.class index 9655a35a6..ccf0a882d 100644 Binary files a/worldedit-bukkit/src/main/resources/com/sk89q/worldedit/bukkit/adapter/impl/Spigot_v1_13_R1.class and b/worldedit-bukkit/src/main/resources/com/sk89q/worldedit/bukkit/adapter/impl/Spigot_v1_13_R1.class differ diff --git a/worldedit-bukkit/src/main/resources/defaults/config-legacy.yml b/worldedit-bukkit/src/main/resources/defaults/config-legacy.yml new file mode 100644 index 000000000..4b8d14715 --- /dev/null +++ b/worldedit-bukkit/src/main/resources/defaults/config-legacy.yml @@ -0,0 +1,85 @@ +# +# WorldEdit's Configuration File +# +# About editing this file: +# - DO NOT USE TABS. You MUST use spaces or Bukkit will complain and post +# errors. If you use an editor, like Notepad++ (recommended for Windows +# users), you must configure it to "replace tabs with spaces." +# This can be changed in Settings > Preferences > Language Menu. +# - Don't get rid of indentations. They are indented so some entries that are +# in categories, like "max-blocks-changed", are placed in the "limits" +# category. +# - If you want to check the format of this file before putting it +# into WorldEdit, paste it into http://yaml-online-parser.appspot.com/ +# and see if it gives you "ERROR:". +# - Lines starting with # are comments, so they are ignored. +# - If you want to allow blocks, make sure to change "disallowed-blocks" to [] +# + +limits: + allow-extra-data-values: false + max-blocks-changed: + default: -1 + maximum: -1 + max-polygonal-points: + default: -1 + maximum: 20 + max-radius: -1 + max-super-pickaxe-size: 5 + max-brush-radius: 100 + butcher-radius: + default: -1 + maximum: -1 + disallowed-blocks: [] + +use-inventory: + enable: false + allow-override: true + creative-mode-overrides: false + +logging: + log-commands: false + file: worldedit.log + # The format of custom log message. This is java general format string (java.util.Formatter). Arguments are: + # 1$ : date - a Date object representing event time of the log record. + # 2$ : source - a string representing the caller, if available; otherwise, the logger's name. + # 3$ : logger - the logger's name. + # 4$ : level - the log level. + # 5$ : message - the formatted log message returned from the Formatter.formatMessage(LogRecord) method. It uses java.text formatting and does not use the java.util.Formatter format argument. + # 6$ : thrown - a string representing the throwable associated with the log record and its backtrace beginning with a newline character, if any; otherwise, an empty string. + # For details see: + # https://docs.oracle.com/javase/8/docs/api/java/util/Formatter.html + # https://docs.oracle.com/javase/8/docs/api/java/util/logging/SimpleFormatter.html#format-java.util.logging.LogRecord- + format: "[%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS %4$s]: %5$s%6$s%n" + +super-pickaxe: + drop-items: true + many-drop-items: false + +snapshots: + directory: + +navigation-wand: + item: minecraft:compass + max-distance: 100 + +scripting: + timeout: 3000 + dir: craftscripts + +saving: + dir: schematics + +files: + allow-symbolic-links: false + +history: + size: 15 + expiration: 10 + +wand-item: minecraft:wooden_axe +shell-save-type: +no-double-slash: false +no-op-permissions: false +debug: false +show-help-on-first-use: true diff --git a/worldedit-bukkit/src/main/resources/plugin.yml b/worldedit-bukkit/src/main/resources/plugin.yml index 9afc2c5a5..4bfc02222 100644 --- a/worldedit-bukkit/src/main/resources/plugin.yml +++ b/worldedit-bukkit/src/main/resources/plugin.yml @@ -1,8 +1,164 @@ -name: WorldEdit +name: FastAsyncWorldEdit main: com.sk89q.worldedit.bukkit.WorldEditPlugin version: "${internalVersion}" -softdepend: [Spout] #hack to fix trove errors api-version: 1.13 - -# Permissions aren't here. Read http://wiki.sk89q.com/wiki/WEPIF/DinnerPerms -# for how WorldEdit permissions actually work. +description: Fast Async WorldEdit plugin +authors: [Empire92] +loadbefore: [WorldEdit,AsyncWorldEdit,AsyncWorldEditInjector,WorldGuard] +load: STARTUP +database: false +#softdepend: [WorldGuard, PlotSquared, MCore, Factions, GriefPrevention, Residence, Towny, PlotMe, PreciousStones] +commands: + fcancel: + description: (FAWE) Cancel your edit + aliases: [fawecancel,/fcancel,/cancel,/fawecancel] +permissions: + fawe.plotsquared: + default: true + children: + fawe.plotsquared.trusted: true + fawe.plotme: + default: true + fawe.bypass.regions: + default: false + fawe.bypass: + default: false + children: + fawe.bypass.regions: true + fawe.limit.*: true + fawe.tips: + default: false + fawe.admin: + default: op + fawe.reload: + default: false + fawe.permpack.basic: + default: op + children: + fawe.worldeditregion: true + fawe.cancel: true + worldedit.biome.info: true + worldedit.biome.set: true + worldedit.biome.list: true + worldedit.chunkinfo: true + worldedit.listchunks: true + worldedit.clipboard.cut: true + worldedit.clipboard.paste: true + worldedit.schematic.formats: true + worldedit.schematic.load: true + worldedit.schematic.list: true + worldedit.schematic.save: true + worldedit.clipboard.clear: true + worldedit.clipboard.copy: true + worldedit.clipboard.lazycopy: true + worldedit.clipboard.place: true + worldedit.clipboard.download: true + worldedit.clipboard.flip: true + worldedit.clipboard.rotate: true + worldedit.help: true + worldedit.global-mask: true + worldedit.global-transform: true + worldedit.generation.cylinder: true + worldedit.generation.sphere: true + worldedit.generation.forest: true + worldedit.generation.pumpkins: true + worldedit.generation.pyramid: true + worldedit.generation.shape: true + worldedit.biome.set: true + worldedit.history.undo: true + worldedit.history.redo: true + worldedit.history.rollback: true + worldedit.navigation.unstuck: true + worldedit.navigation.ascend: true + worldedit.navigation.descend: true + worldedit.navigation.ceiling: true + worldedit.navigation.thru.command: true + worldedit.navigation.jumpto.command: true + worldedit.navigation.up: true + worldedit.region.hollow: true + worldedit.region.line: true + worldedit.region.curve: true + worldedit.region.overlay: true + worldedit.region.center: true + worldedit.region.naturalize: true + worldedit.region.walls: true + worldedit.region.faces: true + worldedit.region.smooth: true + worldedit.region.move: true + worldedit.region.forest: true + worldedit.region.replace: true + worldedit.region.stack: true + worldedit.region.set: true + worldedit.selection.pos: true + worldedit.selection.chunk: true + worldedit.selection.hpos: true + worldedit.wand: true + worldedit.wand.toggle: true + worldedit.selection.contract: true + worldedit.selection.outset: true + worldedit.selection.inset: true + worldedit.analysis.distr: true + worldedit.analysis.count: true + worldedit.selection.size: true + worldedit.selection.expand: true + worldedit.selection.shift: true + worldedit.snapshots.list: true + worldedit.superpickaxe: true + worldedit.superpickaxe.area: true + worldedit.superpickaxe.recursive: true + worldedit.brush.blendball: true + worldedit.brush.erode: true + worldedit.brush.pull: true + worldedit.brush.circle: true + worldedit.brush.recursive: true + worldedit.brush.line: true + worldedit.brush.spline: true + worldedit.brush.surfacespline: true + worldedit.brush.shatter: true + worldedit.brush.stencil: true + worldedit.brush.height: true + worldedit.brush.layer: true + worldedit.brush.populateschematic: true + worldedit.brush.scatter: true + worldedit.brush.splatter: true + worldedit.brush.scattercommand: true + worldedit.brush.copy: true + worldedit.brush.command: true + worldedit.brush.apply: true + worldedit.brush.sphere: true + worldedit.brush.cylinder: true + worldedit.brush.clipboard: true + worldedit.brush.smooth: true + worldedit.brush.ex: true + worldedit.brush.gravity: true + worldedit.brush.options.range: true + worldedit.brush.options.material: true + worldedit.brush.options.size: true + worldedit.brush.options.mask: true + worldedit.brush.options.smask: true + worldedit.brush.options.transform: true + worldedit.brush.options.scroll: true + worldedit.brush.options.visualize: true + worldedit.tool.deltree: true + worldedit.tool.farwand: true + worldedit.tool.lrbuild: true + worldedit.tool.info: true + worldedit.tool.tree: true + worldedit.tool.replacer: true + worldedit.tool.data-cycler: true + worldedit.tool.flood-fill: true + worldedit.tool.inspect: true + worldedit.fill.recursive: true + worldedit.drain: true + worldedit.fixlava: true + worldedit.fixwater: true + worldedit.removeabove: true + worldedit.removebelow: true + worldedit.removenear: true + worldedit.replacenear: true + worldedit.snow: true + worldedit.thaw: true + worldedit.green: true + worldedit.extinguish: true + worldedit.calc: true + worldedit.fill: true \ No newline at end of file diff --git a/worldedit-bukkit/src/test/java/com/sk89q/wepif/DinnerPermsResolverTest.java b/worldedit-bukkit/src/test/java/com/sk89q/wepif/DinnerPermsResolverTest.java index e207e7b8d..8e80a57bc 100644 --- a/worldedit-bukkit/src/test/java/com/sk89q/wepif/DinnerPermsResolverTest.java +++ b/worldedit-bukkit/src/test/java/com/sk89q/wepif/DinnerPermsResolverTest.java @@ -19,15 +19,13 @@ package com.sk89q.wepif; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - import org.bukkit.Server; import org.bukkit.plugin.PluginManager; import org.junit.Before; import org.junit.Test; +import static org.junit.Assert.*; + +import static org.mockito.Mockito.*; public class DinnerPermsResolverTest { private DinnerPermsResolver resolver; diff --git a/worldedit-bukkit/src/test/java/com/sk89q/wepif/TestOfflinePermissible.java b/worldedit-bukkit/src/test/java/com/sk89q/wepif/TestOfflinePermissible.java index 21c83228f..a67351cd5 100644 --- a/worldedit-bukkit/src/test/java/com/sk89q/wepif/TestOfflinePermissible.java +++ b/worldedit-bukkit/src/test/java/com/sk89q/wepif/TestOfflinePermissible.java @@ -28,11 +28,7 @@ import org.bukkit.permissions.PermissionAttachment; import org.bukkit.permissions.PermissionAttachmentInfo; import org.bukkit.plugin.Plugin; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.UUID; +import java.util.*; public class TestOfflinePermissible implements OfflinePlayer, Permissible { private boolean op; diff --git a/worldedit-core/build.gradle b/worldedit-core/build.gradle index 57a5eb2ca..f1034d1b3 100644 --- a/worldedit-core/build.gradle +++ b/worldedit-core/build.gradle @@ -4,15 +4,21 @@ apply plugin: 'idea' dependencies { compile 'de.schlichtherle:truezip:6.8.3' compile 'rhino:js:1.7R2' - compile 'org.yaml:snakeyaml:1.9' compile 'com.google.guava:guava:21.0' compile 'com.sk89q:jchronic:0.2.4a' compile 'com.google.code.findbugs:jsr305:1.3.9' compile 'com.thoughtworks.paranamer:paranamer:2.6' compile 'com.google.code.gson:gson:2.8.0' compile 'com.sk89q.lib:jlibnoise:1.0.0' - //compile 'net.sf.trove4j:trove4j:3.0.3' testCompile 'org.mockito:mockito-core:1.9.0-rc1' + + // Fawe depends + compile 'org.yaml:snakeyaml:1.19' + compile 'net.fabiozumbi12:redprotect:1.9.6' + compile group: "com.plotsquared", name: "plotsquared-api", version: "latest" +// compile 'org.primesoft:BlocksHub:2.0' + compile 'com.github.luben:zstd-jni:1.1.1' + compile 'co.aikar:fastutil-lite:1.0' } sourceSets { @@ -27,4 +33,14 @@ sourceSets { } } +processResources { + from('src/main/resources') { + include 'fawe.properties' + expand( + version: "${project.parent.version}", + name: project.parent.name, + ) + } +} + build.dependsOn(shadowJar) diff --git a/worldedit-core/src/legacy/java/com/sk89q/worldedit/blocks/LazyBlock.java b/worldedit-core/src/legacy/java/com/sk89q/worldedit/blocks/LazyBlock.java new file mode 100644 index 000000000..3bf29bdd9 --- /dev/null +++ b/worldedit-core/src/legacy/java/com/sk89q/worldedit/blocks/LazyBlock.java @@ -0,0 +1,93 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.blocks; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.sk89q.jnbt.CompoundTag; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.block.BlockStateHolder; +import com.sk89q.worldedit.world.block.BlockType; +import com.sk89q.worldedit.extent.Extent; + +/** + * A implementation of a lazy block for {@link Extent#getLazyBlock(Vector)} + * that takes the block's ID and metadata, but will defer loading of NBT + * data until time of access. + * + *

NBT data is later loaded using a call to {@link Extent#getBlock(Vector)} + * with a stored {@link Extent} and location.

+ * + *

All mutators on this object will throw an + * {@link UnsupportedOperationException}.

+ */ +public class LazyBlock extends BaseBlock { + + private final Extent extent; + private final Vector position; + private boolean loaded = false; + + /** + * Create a new lazy block. + * + * @param type the block type + * @param extent the extent to later load the full block data from + * @param position the position to later load the full block data from + */ + public LazyBlock(BlockType type, Extent extent, Vector position) { + super(type); + checkNotNull(extent); + checkNotNull(position); + this.extent = extent; + this.position = position; + } + + /** + * Create a new lazy block. + * + * @param state the block state + * @param extent the extent to later load the full block data from + * @param position the position to later load the full block data from + */ + public LazyBlock(BlockState state, Extent extent, Vector position) { + super(state); + checkNotNull(extent); + checkNotNull(position); + this.extent = extent; + this.position = position; + } + + @Override + public CompoundTag getNbtData() { + if (!loaded) { + BlockState loadedBlock = extent.getFullBlock(position); + this.nbtData = loadedBlock.getNbtData(); + loaded = true; + } + return super.getNbtData(); + } + + @Override + public void setNbtData(CompoundTag nbtData) { + throw new UnsupportedOperationException("This object is immutable"); + } + +} diff --git a/worldedit-core/src/legacy/java/com/sk89q/worldedit/blocks/MobSpawnerBlock.java b/worldedit-core/src/legacy/java/com/sk89q/worldedit/blocks/MobSpawnerBlock.java index 0d175e974..0e4690a1b 100644 --- a/worldedit-core/src/legacy/java/com/sk89q/worldedit/blocks/MobSpawnerBlock.java +++ b/worldedit-core/src/legacy/java/com/sk89q/worldedit/blocks/MobSpawnerBlock.java @@ -25,8 +25,8 @@ import com.sk89q.jnbt.NBTUtils; import com.sk89q.jnbt.ShortTag; import com.sk89q.jnbt.StringTag; import com.sk89q.jnbt.Tag; -import com.sk89q.worldedit.world.block.BaseBlock; import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.block.BlockStateHolder; import com.sk89q.worldedit.world.storage.InvalidFormatException; import java.util.HashMap; @@ -35,7 +35,7 @@ import java.util.Map; /** * A mob spawner block. */ -public class MobSpawnerBlock extends BaseBlock implements TileEntityBlock { +public class MobSpawnerBlock extends BaseBlock { private String mobType; private short delay; @@ -118,7 +118,7 @@ public class MobSpawnerBlock extends BaseBlock implements TileEntityBlock { @Override public CompoundTag getNbtData() { - Map values = new HashMap<>(); + Map values = new HashMap(); values.put("EntityId", new StringTag(mobType)); values.put("Delay", new ShortTag(delay)); values.put("SpawnCount", new ShortTag(spawnCount)); diff --git a/worldedit-core/src/legacy/java/com/sk89q/worldedit/blocks/SignBlock.java b/worldedit-core/src/legacy/java/com/sk89q/worldedit/blocks/SignBlock.java index e11330faa..dce851c0d 100644 --- a/worldedit-core/src/legacy/java/com/sk89q/worldedit/blocks/SignBlock.java +++ b/worldedit-core/src/legacy/java/com/sk89q/worldedit/blocks/SignBlock.java @@ -22,9 +22,9 @@ package com.sk89q.worldedit.blocks; import com.sk89q.jnbt.CompoundTag; import com.sk89q.jnbt.StringTag; import com.sk89q.jnbt.Tag; -import com.sk89q.worldedit.util.gson.GsonUtil; -import com.sk89q.worldedit.world.block.BaseBlock; import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.block.BlockStateHolder; +import com.sk89q.worldedit.util.gson.GsonUtil; import java.util.HashMap; import java.util.Map; @@ -93,7 +93,7 @@ public class SignBlock extends BaseBlock implements TileEntityBlock { @Override public CompoundTag getNbtData() { - Map values = new HashMap<>(); + Map values = new HashMap(); values.put("Text1", new StringTag(text[0])); values.put("Text2", new StringTag(text[1])); values.put("Text3", new StringTag(text[2])); diff --git a/worldedit-core/src/legacy/java/com/sk89q/worldedit/blocks/SkullBlock.java b/worldedit-core/src/legacy/java/com/sk89q/worldedit/blocks/SkullBlock.java index 4ecddddc2..e63d88310 100644 --- a/worldedit-core/src/legacy/java/com/sk89q/worldedit/blocks/SkullBlock.java +++ b/worldedit-core/src/legacy/java/com/sk89q/worldedit/blocks/SkullBlock.java @@ -22,8 +22,8 @@ package com.sk89q.worldedit.blocks; import com.sk89q.jnbt.CompoundTag; import com.sk89q.jnbt.StringTag; import com.sk89q.jnbt.Tag; -import com.sk89q.worldedit.world.block.BaseBlock; import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.block.BlockStateHolder; import java.util.HashMap; import java.util.Map; @@ -114,4 +114,4 @@ public class SkullBlock extends BaseBlock implements TileEntityBlock { owner = ((StringTag) t).getValue(); } } -} +} \ No newline at end of file diff --git a/worldedit-core/src/main/java/com/boydti/fawe/Fawe.java b/worldedit-core/src/main/java/com/boydti/fawe/Fawe.java new file mode 100644 index 000000000..542f7f794 --- /dev/null +++ b/worldedit-core/src/main/java/com/boydti/fawe/Fawe.java @@ -0,0 +1,585 @@ +package com.boydti.fawe; + +import com.boydti.fawe.command.Cancel; +import com.boydti.fawe.config.BBC; +import com.boydti.fawe.config.Commands; +import com.boydti.fawe.config.Settings; +import com.boydti.fawe.object.FawePlayer; +import com.boydti.fawe.object.brush.visualization.VisualQueue; +import com.boydti.fawe.regions.general.plot.PlotSquaredFeature; +import com.boydti.fawe.util.*; +import com.boydti.fawe.util.chat.ChatManager; +import com.boydti.fawe.util.chat.PlainChatManager; +import com.boydti.fawe.util.cui.CUI; +import com.boydti.fawe.util.metrics.BStats; +import com.sk89q.jnbt.*; +import com.sk89q.worldedit.*; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.command.*; +import com.sk89q.worldedit.command.composition.SelectionCommand; +import com.sk89q.worldedit.command.tool.*; +import com.sk89q.worldedit.command.tool.brush.GravityBrush; +import com.sk89q.worldedit.event.extent.EditSessionEvent; +import com.sk89q.worldedit.extension.factory.DefaultMaskParser; +import com.sk89q.worldedit.extension.factory.DefaultTransformParser; +import com.sk89q.worldedit.extension.factory.HashTagPatternParser; +import com.sk89q.worldedit.extension.platform.Actor; +import com.sk89q.worldedit.extension.platform.CommandManager; +import com.sk89q.worldedit.extension.platform.PlatformManager; +import com.sk89q.worldedit.extent.AbstractDelegateExtent; +import com.sk89q.worldedit.extent.MaskingExtent; +import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard; +import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormat; +import com.sk89q.worldedit.extent.clipboard.io.SchematicReader; +import com.sk89q.worldedit.extent.inventory.BlockBagExtent; +import com.sk89q.worldedit.extent.transform.BlockTransformExtent; +import com.sk89q.worldedit.function.CombinedRegionFunction; +import com.sk89q.worldedit.function.block.BlockReplace; +import com.sk89q.worldedit.function.block.ExtentBlockCopy; +import com.sk89q.worldedit.function.entity.ExtentEntityCopy; +import com.sk89q.worldedit.function.mask.*; +import com.sk89q.worldedit.function.operation.ChangeSetExecutor; +import com.sk89q.worldedit.function.operation.ForwardExtentCopy; +import com.sk89q.worldedit.function.operation.Operations; +import com.sk89q.worldedit.function.pattern.*; +import com.sk89q.worldedit.function.visitor.*; +import com.sk89q.worldedit.internal.command.WorldEditBinding; +import com.sk89q.worldedit.internal.expression.Expression; +import com.sk89q.worldedit.internal.expression.runtime.*; +import com.sk89q.worldedit.math.convolution.HeightMap; +import com.sk89q.worldedit.math.interpolation.KochanekBartelsInterpolation; +import com.sk89q.worldedit.math.transform.AffineTransform; +import com.sk89q.worldedit.regions.CuboidRegion; +import com.sk89q.worldedit.regions.CylinderRegion; +import com.sk89q.worldedit.regions.EllipsoidRegion; +import com.sk89q.worldedit.regions.selector.*; +import com.sk89q.worldedit.regions.shape.WorldEditExpressionEnvironment; +import com.sk89q.worldedit.session.ClipboardHolder; +import com.sk89q.worldedit.session.PasteBuilder; +import com.sk89q.worldedit.session.SessionManager; +import com.sk89q.worldedit.session.request.Request; +import com.sk89q.worldedit.util.command.SimpleCommandMapping; +import com.sk89q.worldedit.util.command.SimpleDispatcher; +import com.sk89q.worldedit.util.command.fluent.DispatcherNode; +import com.sk89q.worldedit.util.command.parametric.*; +import com.sk89q.worldedit.util.formatting.Fragment; +import com.sk89q.worldedit.util.formatting.component.CommandListBox; +import com.sk89q.worldedit.util.formatting.component.CommandUsageBox; +import com.sk89q.worldedit.util.formatting.component.MessageBox; +import com.sk89q.worldedit.world.biome.BaseBiome; + +import javax.annotation.Nullable; +import javax.management.InstanceAlreadyExistsException; +import javax.management.Notification; +import javax.management.NotificationEmitter; +import javax.management.NotificationListener; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.lang.management.ManagementFactory; +import java.lang.management.MemoryMXBean; +import java.lang.management.MemoryPoolMXBean; +import java.lang.management.MemoryUsage; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * [ WorldEdit action] + * | + * \|/ + * [ EditSession ] - The change is processed (area restrictions, change limit, block type) + * | + * \|/ + * [Block change] - A block change from some location + * | + * \|/ + * [ Set Queue ] - The SetQueue manages the implementation specific queue + * | + * \|/ + * [ Fawe Queue] - A queue of chunks - check if the queue has the chunk for a change + * | + * \|/ + * [ Fawe Chunk Implementation ] - Otherwise create a new FaweChunk object which is a wrapper around the Chunk object + * | + * \|/ + * [ Execution ] - When done, the queue then sets the blocks for the chunk, performs lighting updates and sends the chunk packet to the clients + *

+ * Why it's faster: + * - The chunk is modified directly rather than through the API + * \ Removes some overhead, and means some processing can be done async + * - Lighting updates are performed on the chunk level rather than for every block + * \ e.g. A blob of stone: only the visible blocks need to have the lighting calculated + * - Block changes are sent with a chunk packet + * \ A chunk packet is generally quicker to create and smaller for large world edits + * - No physics updates + * \ Physics updates are slow, and are usually performed on each block + * - Block data shortcuts + * \ Some known blocks don't need to have the data set or accessed (e.g. air is never going to have data) + * - Remove redundant extents + * \ Up to 11 layers of extents can be removed + * - History bypassing + * \ FastMode bypasses history and means blocks in the world don't need to be checked and recorded + */ +public class Fawe { + /** + * The FAWE instance; + */ + private static Fawe INSTANCE; + + /** + * TPS timer + */ + private final FaweTimer timer; + private FaweVersion version; + private VisualQueue visualQueue; + private Updater updater; + private TextureUtil textures; + private DefaultTransformParser transformParser; + private ChatManager chatManager = new PlainChatManager(); + + private BStats stats; + + /** + * Get the implementation specific class + * + * @return + */ + @SuppressWarnings("unchecked") + public static T imp() { + return INSTANCE != null ? (T) INSTANCE.IMP : null; + } + + /** + * Get the implementation independent class + * + * @return + */ + public static Fawe get() { + return INSTANCE; + } + + /** + * Setup Fawe + * + * @param implementation + * @throws InstanceAlreadyExistsException + */ + public static void set(final IFawe implementation) throws InstanceAlreadyExistsException, IllegalArgumentException { + if (INSTANCE != null) { + throw new InstanceAlreadyExistsException("FAWE has already been initialized with: " + INSTANCE.IMP); + } + if (implementation == null) { + throw new IllegalArgumentException("Implementation may not be null."); + } + INSTANCE = new Fawe(implementation); + } + + public static void debugPlain(String s) { + if (INSTANCE != null) { + INSTANCE.IMP.debug(s); + } else { + System.out.println(BBC.stripColor(BBC.color(s))); + } + } + + /** + * Write something to the console + * + * @param s + */ + public static void debug(Object s) { + debugPlain(BBC.PREFIX.original() + " " + s); + } + + /** + * The platform specific implementation + */ + private final IFawe IMP; + private Thread thread = Thread.currentThread(); + + private Fawe(final IFawe implementation) { + this.INSTANCE = this; + this.IMP = implementation; + this.thread = Thread.currentThread(); + /* + * Implementation dependent stuff + */ + this.setupConfigs(); + TaskManager.IMP = this.IMP.getTaskManager(); + + TaskManager.IMP.async(new Runnable() { + @Override + public void run() { + MainUtil.deleteOlder(MainUtil.getFile(IMP.getDirectory(), Settings.IMP.PATHS.HISTORY), TimeUnit.DAYS.toMillis(Settings.IMP.HISTORY.DELETE_AFTER_DAYS), false); + MainUtil.deleteOlder(MainUtil.getFile(IMP.getDirectory(), Settings.IMP.PATHS.CLIPBOARD), TimeUnit.DAYS.toMillis(Settings.IMP.CLIPBOARD.DELETE_AFTER_DAYS), false); + } + }); + + if (Settings.IMP.METRICS) { + try { + this.stats = new BStats(); + this.IMP.startMetrics(); + TaskManager.IMP.later(new Runnable() { + @Override + public void run() { + stats.start(); + } + }, 1); + } catch (Throwable ignore) { + ignore.printStackTrace(); + } + } + this.setupCommands(); + /* + * Instance independent stuff + */ + this.setupMemoryListener(); + this.timer = new FaweTimer(); + Fawe.this.IMP.setupVault(); + + File jar = MainUtil.getJarFile(); + // TODO FIXME remove extrablocks.json +// File extraBlocks = MainUtil.copyFile(jar, "extrablocks.json", null); +// if (extraBlocks != null && extraBlocks.exists()) { +// TaskManager.IMP.task(() -> { +// try { +// BundledBlockData.getInstance().loadFromResource(); +// BundledBlockData.getInstance().add(extraBlocks.toURI().toURL(), true); +// } catch (Throwable ignore) { +// ignore.printStackTrace(); +// Fawe.debug("Invalid format: extrablocks.json"); +// } +// }); +// } + + // Delayed worldedit setup + TaskManager.IMP.later(() -> { + try { + transformParser = new DefaultTransformParser(getWorldEdit()); + visualQueue = new VisualQueue(3); + WEManager.IMP.managers.addAll(Fawe.this.IMP.getMaskManagers()); + WEManager.IMP.managers.add(new PlotSquaredFeature()); + Fawe.debug("Plugin 'PlotSquared' found. Using it now."); + } catch (Throwable e) {} + }, 0); + + TaskManager.IMP.repeat(timer, 1); + + if (!Settings.IMP.UPDATE.equalsIgnoreCase("false")) { + // Delayed updating + updater = new Updater(); + TaskManager.IMP.async(() -> update()); + TaskManager.IMP.repeatAsync(() -> update(), 36000); + } + } + + public void onDisable() { + if (stats != null) { + stats.close(); + } + } + + private boolean update() { + if (updater != null) { + updater.getUpdate(IMP.getPlatform(), getVersion()); + return true; + } + return false; + } + + public CUI getCUI(Actor actor) { + FawePlayer fp = FawePlayer.wrap(actor); + CUI cui = fp.getMeta("CUI"); + if (cui == null) { + cui = Fawe.imp().getCUI(fp); + if (cui != null) { + synchronized (fp) { + CUI tmp = fp.getMeta("CUI"); + if (tmp == null) { + fp.setMeta("CUI", cui); + } else { + cui = tmp; + } + } + } + } + return cui; + } + + public ChatManager getChatManager() { + return chatManager; + } + + public void setChatManager(ChatManager chatManager) { + checkNotNull(chatManager); + this.chatManager = chatManager; + } + + // @Deprecated +// public boolean isJava8() { +// return isJava8; +// } + + public DefaultTransformParser getTransformParser() { + return transformParser; + } + + /** + * The FAWE updater class + * - Use to get basic update information (changelog/version etc) + * + * @return + */ + public Updater getUpdater() { + return updater; + } + + public TextureUtil getCachedTextureUtil(boolean randomize, int min, int max) { + TextureUtil tu = getTextureUtil(); + try { + tu = min == 0 && max == 100 ? tu : new CleanTextureUtil(tu, min, max); + tu = randomize ? new RandomTextureUtil(tu) : new CachedTextureUtil(tu); + } catch (FileNotFoundException neverHappens) { + neverHappens.printStackTrace(); + } + return tu; + } + + public TextureUtil getTextureUtil() { + TextureUtil tmp = textures; + if (tmp == null) { + synchronized (this) { + tmp = textures; + if (tmp == null) { + try { + textures = tmp = new TextureUtil(); + tmp.loadModTextures(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + } + return tmp; + } + + /** + * The FaweTimer is a useful class for monitoring TPS + * + * @return FaweTimer + */ + public FaweTimer getTimer() { + return timer; + } + + /** + * The visual queue is used to queue visualizations + * + * @return + */ + public VisualQueue getVisualQueue() { + return visualQueue; + } + + /** + * The FAWE version + * - Unofficial jars may be lacking version information + * + * @return FaweVersion + */ + public + @Nullable + FaweVersion getVersion() { + return version; + } + + public double getTPS() { + return timer.getTPS(); + } + + private void setupCommands() { + this.IMP.setupCommand("fcancel", new Cancel()); + } + + public void setupConfigs() { + MainUtil.copyFile(MainUtil.getJarFile(), "de/message.yml", null); + MainUtil.copyFile(MainUtil.getJarFile(), "ru/message.yml", null); + MainUtil.copyFile(MainUtil.getJarFile(), "ru/commands.yml", null); + MainUtil.copyFile(MainUtil.getJarFile(), "tr/message.yml", null); + MainUtil.copyFile(MainUtil.getJarFile(), "es/message.yml", null); + MainUtil.copyFile(MainUtil.getJarFile(), "es/commands.yml", null); + // Setting up config.yml + File file = new File(this.IMP.getDirectory(), "config.yml"); + Settings.IMP.PLATFORM = IMP.getPlatform().replace("\"", ""); + try { + InputStream stream = getClass().getResourceAsStream("/fawe.properties"); + java.util.Scanner scanner = new java.util.Scanner(stream).useDelimiter("\\A"); + String versionString = scanner.next().trim(); + scanner.close(); + this.version = new FaweVersion(versionString); + Settings.IMP.DATE = new Date(100 + version.year, version.month, version.day).toGMTString(); + Settings.IMP.BUILD = "https://ci.athion.net/job/FastAsyncWorldEdit/" + version.build; + Settings.IMP.COMMIT = "https://github.com/boy0001/FastAsyncWorldedit/commit/" + Integer.toHexString(version.hash); + } catch (Throwable ignore) {} + try { + Settings.IMP.reload(file); + // Setting up message.yml + String lang = Objects.toString(Settings.IMP.LANGUAGE); + BBC.load(new File(this.IMP.getDirectory(), (lang.isEmpty() ? "" : lang + File.separator) + "message.yml")); + Commands.load(new File(INSTANCE.IMP.getDirectory(), "commands.yml")); + } catch (Throwable e) { + debug("====== Failed to load config ======"); + debug("Please validate your yaml files:"); + debug("===================================="); + e.printStackTrace(); + debug("===================================="); + } + } + + + public WorldEdit getWorldEdit() { + return WorldEdit.getInstance(); + } + + public static void setupInjector() { + /* + * Modify the sessions + * - EditSession supports custom queue and a lot of optimizations + * - LocalSession supports VirtualPlayers and undo on disk + */ + if (!Settings.IMP.EXPERIMENTAL.DISABLE_NATIVES) { + try { + com.github.luben.zstd.util.Native.load(); + } catch (Throwable e) { + if (Settings.IMP.CLIPBOARD.COMPRESSION_LEVEL > 6 || Settings.IMP.HISTORY.COMPRESSION_LEVEL > 6) { + Settings.IMP.CLIPBOARD.COMPRESSION_LEVEL = Math.min(6, Settings.IMP.CLIPBOARD.COMPRESSION_LEVEL); + Settings.IMP.HISTORY.COMPRESSION_LEVEL = Math.min(6, Settings.IMP.HISTORY.COMPRESSION_LEVEL); + debug("====== ZSTD COMPRESSION BINDING NOT FOUND ======"); + debug(e); + debug("==============================================="); + debug("FAWE will work but won't compress data as much"); + debug("==============================================="); + } + } + try { + net.jpountz.util.Native.load(); + } catch (Throwable e) { + e.printStackTrace(); + debug("====== LZ4 COMPRESSION BINDING NOT FOUND ======"); + debug(e); + debug("==============================================="); + debug("FAWE will work but compression will be slower"); + debug(" - Try updating your JVM / OS"); + debug(" - Report this issue if you cannot resolve it"); + debug("==============================================="); + } + } + try { + String arch = System.getenv("PROCESSOR_ARCHITECTURE"); + String wow64Arch = System.getenv("PROCESSOR_ARCHITEW6432"); + boolean x86OS = arch.endsWith("64") || wow64Arch != null && wow64Arch.endsWith("64") ? false : true; + boolean x86JVM = System.getProperty("sun.arch.data.model").equals("32"); + if (x86OS != x86JVM) { + debug("====== UPGRADE TO 64-BIT JAVA ======"); + debug("You are running 32-bit Java on a 64-bit machine"); + debug(" - This is only a recommendation"); + debug("===================================="); + } + } catch (Throwable ignore) {} + } + + private void setupMemoryListener() { + if (Settings.IMP.MAX_MEMORY_PERCENT < 1 || Settings.IMP.MAX_MEMORY_PERCENT > 99) { + return; + } + try { + final MemoryMXBean memBean = ManagementFactory.getMemoryMXBean(); + final NotificationEmitter ne = (NotificationEmitter) memBean; + + ne.addNotificationListener(new NotificationListener() { + @Override + public void handleNotification(final Notification notification, final Object handback) { + final long heapSize = Runtime.getRuntime().totalMemory(); + final long heapMaxSize = Runtime.getRuntime().maxMemory(); + if (heapSize < heapMaxSize) { + return; + } + MemUtil.memoryLimitedTask(); + } + }, null, null); + + final List memPools = ManagementFactory.getMemoryPoolMXBeans(); + for (final MemoryPoolMXBean mp : memPools) { + if (mp.isUsageThresholdSupported()) { + final MemoryUsage mu = mp.getUsage(); + final long max = mu.getMax(); + if (max < 0) { + continue; + } + final long alert = (max * Settings.IMP.MAX_MEMORY_PERCENT) / 100; + mp.setUsageThreshold(alert); + } + } + } catch (Throwable e) { + debug("====== MEMORY LISTENER ERROR ======"); + MainUtil.handleError(e, false); + debug("==================================="); + debug("FAWE needs access to the JVM memory system:"); + debug(" - Change your Java security settings"); + debug(" - Disable this with `max-memory-percent: -1`"); + debug("==================================="); + } + } + + /** + * Get the main thread + * + * @return + */ + public Thread getMainThread() { + return this.thread; + } + + public static boolean isMainThread() { + return INSTANCE != null ? INSTANCE.thread == Thread.currentThread() : true; + } + + /** + * Sets the main thread to the current thread + * + * @return + */ + public Thread setMainThread() { + return this.thread = Thread.currentThread(); + } + + private ConcurrentHashMap players = new ConcurrentHashMap<>(8, 0.9f, 1); + private ConcurrentHashMap playersUUID = new ConcurrentHashMap<>(8, 0.9f, 1); + + public void register(FawePlayer player) { + players.put(player.getName(), player); + playersUUID.put(player.getUUID(), player); + + } + + public void unregister(String name) { + FawePlayer player = players.remove(name); + if (player != null) playersUUID.remove(player.getUUID()); + } + + public FawePlayer getCachedPlayer(String name) { + return players.get(name); + } + + public FawePlayer getCachedPlayer(UUID uuid) { + return playersUUID.get(uuid); + } + + public Collection getCachedPlayers() { + return players.values(); + } +} diff --git a/worldedit-core/src/main/java/com/boydti/fawe/FaweAPI.java b/worldedit-core/src/main/java/com/boydti/fawe/FaweAPI.java new file mode 100644 index 000000000..435524283 --- /dev/null +++ b/worldedit-core/src/main/java/com/boydti/fawe/FaweAPI.java @@ -0,0 +1,564 @@ +package com.boydti.fawe; + +import com.boydti.fawe.config.BBC; +import com.boydti.fawe.config.Settings; +import com.boydti.fawe.example.NMSMappedFaweQueue; +import com.boydti.fawe.example.NMSRelighter; +import com.boydti.fawe.object.FaweLocation; +import com.boydti.fawe.object.FawePlayer; +import com.boydti.fawe.object.FaweQueue; +import com.boydti.fawe.object.PseudoRandom; +import com.boydti.fawe.object.RegionWrapper; +import com.boydti.fawe.object.changeset.DiskStorageHistory; +import com.boydti.fawe.object.schematic.Schematic; +import com.boydti.fawe.regions.FaweMaskManager; +import com.boydti.fawe.util.EditSessionBuilder; +import com.boydti.fawe.util.MainUtil; +import com.boydti.fawe.util.MemUtil; +import com.boydti.fawe.util.SetQueue; +import com.boydti.fawe.util.TaskManager; +import com.boydti.fawe.util.WEManager; +import com.boydti.fawe.wrappers.WorldWrapper; +import com.sk89q.jnbt.ByteArrayTag; +import com.sk89q.jnbt.IntTag; +import com.sk89q.jnbt.NBTInputStream; +import com.sk89q.jnbt.ShortTag; +import com.sk89q.jnbt.Tag; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.extension.factory.DefaultMaskParser; +import com.sk89q.worldedit.extension.factory.DefaultTransformParser; +import com.sk89q.worldedit.extension.factory.HashTagPatternParser; +import com.sk89q.worldedit.extension.platform.Capability; +import com.sk89q.worldedit.extension.platform.CommandManager; +import com.sk89q.worldedit.extension.platform.Platform; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.extent.clipboard.Clipboard; +import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormat; +import com.sk89q.worldedit.internal.registry.AbstractFactory; +import com.sk89q.worldedit.internal.registry.InputParser; +import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.world.AbstractWorld; +import com.sk89q.worldedit.world.World; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Field; +import java.net.URL; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.zip.GZIPInputStream; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * The FaweAPI class offers a few useful functions.
+ * - This class is not intended to replace the WorldEdit API
+ * - With FAWE installed, you can use the EditSession and other WorldEdit classes from an async thread.
+ *
+ * FaweAPI.[some method] + */ +public class FaweAPI { + /** + * Offers a lot of options for building an EditSession + * + * @param world + * @return A new EditSessionBuilder + * @see com.boydti.fawe.util.EditSessionBuilder + */ + public static EditSessionBuilder getEditSessionBuilder(World world) { + return new EditSessionBuilder(world); + } + + /** + * The TaskManager has some useful methods for doing things asynchronously + * + * @return TaskManager + */ + public static TaskManager getTaskManager() { + return TaskManager.IMP; + } + + /** + * Add a custom mask for use in e.g {@literal //mask #id:} + * + * @param methods The class with a bunch of mask methods + * @return true if the mask was registered + * @see com.sk89q.worldedit.command.MaskCommands + */ + public static boolean registerMasks(Object methods) { + DefaultMaskParser parser = getParser(DefaultMaskParser.class); + if (parser != null) parser.register(methods); + return parser != null; + } + + /** + * Add a custom material for use in e.g {@literal //material #id:} + * + * @param methods The class with a bunch of pattern methods + * @return true if the mask was registered + * @see com.sk89q.worldedit.command.PatternCommands + */ + public static boolean registerPatterns(Object methods) { + HashTagPatternParser parser = getParser(HashTagPatternParser.class); + if (parser != null) parser.register(methods); + return parser != null; + } + + /** + * Add a custom transform for use in + * + * @param methods The class with a bunch of transform methods + * @return true if the transform was registered + * @see com.sk89q.worldedit.command.TransformCommands + */ + public static boolean registerTransforms(Object methods) { + DefaultTransformParser parser = Fawe.get().getTransformParser(); + if (parser != null) parser.register(methods); + return parser != null; + } + + public static T getParser(Class parserClass) { + try { + Field field = AbstractFactory.class.getDeclaredField("parsers"); + field.setAccessible(true); + ArrayList parsers = new ArrayList<>(); + parsers.addAll((List) field.get(WorldEdit.getInstance().getMaskFactory())); + parsers.addAll((List) field.get(WorldEdit.getInstance().getBlockFactory())); + parsers.addAll((List) field.get(WorldEdit.getInstance().getItemFactory())); + parsers.addAll((List) field.get(WorldEdit.getInstance().getPatternFactory())); + for (InputParser parser : parsers) { + if (parserClass.isAssignableFrom(parser.getClass())) { + return (T) parser; + } + } + return null; + } catch (Throwable e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } + + /** + * Create a command with the provided aliases and register all methods of the class as sub commands.
+ * - You should try to register commands during startup + * - If no aliases are specified, all methods become root commands + * + * @param clazz The class containing all the sub command methods + * @param aliases The aliases to give the command (or none) + */ + public static void registerCommands(Object clazz, String... aliases) { + CommandManager.getInstance().registerCommands(clazz, aliases); + } + + /** + * Wrap some object into a FawePlayer
+ * - org.bukkit.entity.Player + * - org.spongepowered.api.entity.living.player + * - com.sk89q.worldedit.entity.Player + * - String (name) + * - UUID (player UUID) + * + * @param obj + * @return + */ + public static FawePlayer wrapPlayer(Object obj) { + return FawePlayer.wrap(obj); + } + + public static FaweQueue createQueue(String worldName, boolean autoqueue) { + return SetQueue.IMP.getNewQueue(worldName, true, autoqueue); + } + + /** + * You can either use a FaweQueue or an EditSession to change blocks
+ * - The FaweQueue skips a bit of overhead so it's faster
+ * - The WorldEdit EditSession can do a lot more
+ * Remember to enqueue it when you're done!
+ * + * @param world The name of the world + * @param autoqueue If it should start dispatching before you enqueue it. + * @return + * @see com.boydti.fawe.object.FaweQueue#enqueue() + */ + public static FaweQueue createQueue(World world, boolean autoqueue) { + return SetQueue.IMP.getNewQueue(world, true, autoqueue); + } + + public static World getWorld(String worldName) { + Platform platform = WorldEdit.getInstance().getPlatformManager().queryCapability(Capability.WORLD_EDITING); + List worlds = platform.getWorlds(); + for (World current : worlds) { + if (Fawe.imp().getWorldName(current).equals(worldName)) { + return WorldWrapper.wrap((AbstractWorld) current); + } + } + for (World current : worlds) { + if (current.getName().equals(worldName)) { + return WorldWrapper.wrap((AbstractWorld) current); + } + } + return null; + } + + /** + * Upload the clipboard to the configured web interface + * + * @param clipboard The clipboard (may not be null) + * @param format The format to use (some formats may not be supported) + * @return The download URL or null + */ + public static URL upload(final Clipboard clipboard, final ClipboardFormat format) { + return format.uploadAnonymous(clipboard); + } + + /** + * Just forwards to ClipboardFormat.SCHEMATIC.load(file) + * + * @param file + * @return + * @see com.sk89q.worldedit.extent.clipboard.io.ClipboardFormat + * @see com.boydti.fawe.object.schematic.Schematic + */ + public static Schematic load(File file) throws IOException { + return ClipboardFormat.SCHEMATIC.load(file); + } + + /** + * Get a list of supported protection plugin masks. + * + * @return Set of FaweMaskManager + */ + public static Set getMaskManagers() { + return new HashSet<>(WEManager.IMP.managers); + } + + /** + * Check if the server has more than the configured low memory threshold + * + * @return True if the server has limited memory + */ + public static boolean isMemoryLimited() { + return MemUtil.isMemoryLimited(); + } + + /** + * Use ThreadLocalRandom instead + * + * @return + */ + @Deprecated + public static PseudoRandom getFastRandom() { + return new PseudoRandom(); + } + + /** + * Get a player's allowed WorldEdit region + * + * @param player + * @return + */ + public static Region[] getRegions(FawePlayer player) { + return WEManager.IMP.getMask(player); + } + + /** + * Cancel the edit with the following extent
+ * - The extent must be the one being used by an EditSession, otherwise an error may be thrown
+ * - Insert an extent into the EditSession using the EditSessionEvent: http://wiki.sk89q.com/wiki/WorldEdit/API/Hooking_EditSession
+ * + * @param extent + * @param reason + * @see com.sk89q.worldedit.EditSession#getRegionExtent() To get the FaweExtent for an EditSession + */ + public static void cancelEdit(Extent extent, BBC reason) { + try { + WEManager.IMP.cancelEdit(extent, reason); + } catch (WorldEditException ignore) { + } + } + + public static void addMaskManager(FaweMaskManager maskMan) { + WEManager.IMP.managers.add(maskMan); + } + + /** + * Get the DiskStorageHistory object representing a File + * + * @param file + * @return + */ + public static DiskStorageHistory getChangeSetFromFile(File file) { + if (!file.exists() || file.isDirectory()) { + throw new IllegalArgumentException("Not a file!"); + } + if (!file.getName().toLowerCase().endsWith(".bd")) { + throw new IllegalArgumentException("Not a BD file!"); + } + if (Settings.IMP.HISTORY.USE_DISK) { + throw new IllegalArgumentException("History on disk not enabled!"); + } + String[] path = file.getPath().split(File.separator); + if (path.length < 3) { + throw new IllegalArgumentException("Not in history directory!"); + } + String worldName = path[path.length - 3]; + String uuidString = path[path.length - 2]; + World world = getWorld(worldName); + if (world == null) { + throw new IllegalArgumentException("Corresponding world does not exist: " + worldName); + } + UUID uuid; + try { + uuid = UUID.fromString(uuidString); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("Invalid UUID from file path: " + uuidString); + } + DiskStorageHistory history = new DiskStorageHistory(world, uuid, Integer.parseInt(file.getName().split("\\.")[0])); + return history; + } + + /** + * Used in the RollBack to generate a list of DiskStorageHistory objects
+ * - Note: An edit outside the radius may be included if it overlaps with an edit inside that depends on it. + * + * @param origin - The origin location + * @param user - The uuid (may be null) + * @param radius - The radius from the origin of the edit + * @param timediff - The max age of the file in milliseconds + * @param shallow - If shallow is true, FAWE will only read the first Settings.IMP.BUFFER_SIZE bytes to obtain history info
+ * Reading only part of the file will result in unreliable bounds info for large edits + * @return + */ + public static List getBDFiles(FaweLocation origin, UUID user, int radius, long timediff, boolean shallow) { + File history = MainUtil.getFile(Fawe.imp().getDirectory(), Settings.IMP.PATHS.HISTORY + File.separator + origin.world); + if (!history.exists()) { + return new ArrayList<>(); + } + long now = System.currentTimeMillis(); + ArrayList files = new ArrayList<>(); + for (File userFile : history.listFiles()) { + if (!userFile.isDirectory()) { + continue; + } + UUID userUUID; + try { + userUUID = UUID.fromString(userFile.getName()); + } catch (IllegalArgumentException e) { + continue; + } + if (user != null && !userUUID.equals(user)) { + continue; + } + ArrayList ids = new ArrayList<>(); + for (File file : userFile.listFiles()) { + if (file.getName().endsWith(".bd")) { + if (timediff >= Integer.MAX_VALUE || now - file.lastModified() <= timediff) { + files.add(file); + if (files.size() > 2048) { + return null; + } + } + } + } + } + World world = origin.getWorld(); + Collections.sort(files, new Comparator() { + @Override + public int compare(File a, File b) { + String aName = a.getName(); + String bName = b.getName(); + int aI = Integer.parseInt(aName.substring(0, aName.length() - 3)); + int bI = Integer.parseInt(bName.substring(0, bName.length() - 3)); + long value = aI - bI; + return value == 0 ? 0 : value < 0 ? -1 : 1; + } + }); + RegionWrapper bounds = new RegionWrapper(origin.x - radius, origin.x + radius, origin.z - radius, origin.z + radius); + RegionWrapper boundsPlus = new RegionWrapper(bounds.minX - 64, bounds.maxX + 512, bounds.minZ - 64, bounds.maxZ + 512); + HashSet regionSet = new HashSet(Arrays.asList(bounds)); + ArrayList result = new ArrayList<>(); + for (File file : files) { + UUID uuid = UUID.fromString(file.getParentFile().getName()); + DiskStorageHistory dsh = new DiskStorageHistory(world, uuid, Integer.parseInt(file.getName().split("\\.")[0])); + DiskStorageHistory.DiskStorageSummary summary = dsh.summarize(boundsPlus, shallow); + RegionWrapper region = new RegionWrapper(summary.minX, summary.maxX, summary.minZ, summary.maxZ); + boolean encompassed = false; + boolean isIn = false; + for (RegionWrapper allowed : regionSet) { + isIn = isIn || allowed.intersects(region); + if (encompassed = allowed.isIn(region.minX, region.maxX) && allowed.isIn(region.minZ, region.maxZ)) { + break; + } + } + if (isIn) { + result.add(0, dsh); + if (!encompassed) { + regionSet.add(region); + } + if (shallow && result.size() > 64) { + return result; + } + } + } + return result; + } + + /** + * The DiskStorageHistory class is what FAWE uses to represent the undo on disk. + * + * @param world + * @param uuid + * @param index + * @return + * @see com.boydti.fawe.object.changeset.DiskStorageHistory#toEditSession(com.boydti.fawe.object.FawePlayer) + */ + public static DiskStorageHistory getChangeSetFromDisk(World world, UUID uuid, int index) { + return new DiskStorageHistory(world, uuid, index); + } + + /** + * Compare two versions + * + * @param version + * @param major + * @param minor + * @param minor2 + * @return true if version is >= major, minor, minor2 + */ + public static boolean checkVersion(final int[] version, final int major, final int minor, final int minor2) { + return (version[0] > major) || ((version[0] == major) && (version[1] > minor)) || ((version[0] == major) && (version[1] == minor) && (version[2] >= minor2)); + } + + @Deprecated + public static int fixLighting(String world, Region selection) { + return fixLighting(world, selection, FaweQueue.RelightMode.ALL); + } + + @Deprecated + public static int fixLighting(String world, Region selection, final FaweQueue.RelightMode mode) { + return fixLighting(world, selection, null, mode); + } + + @Deprecated + public static int fixLighting(String world, Region selection, @Nullable FaweQueue queue, final FaweQueue.RelightMode mode) { + return fixLighting(getWorld(world), selection, queue, mode); + } + + /** + * Fix the lighting in a selection
+ * - First removes all lighting, then relights + * - Relights in parallel (if enabled) for best performance
+ * - Also resends chunks
+ * + * @param world + * @param selection (assumes cuboid) + * @return + */ + public static int fixLighting(World world, Region selection, @Nullable FaweQueue queue, final FaweQueue.RelightMode mode) { + final Vector bot = selection.getMinimumPoint(); + final Vector top = selection.getMaximumPoint(); + + final int minX = bot.getBlockX() >> 4; + final int minZ = bot.getBlockZ() >> 4; + + final int maxX = top.getBlockX() >> 4; + final int maxZ = top.getBlockZ() >> 4; + + int count = 0; + if (queue == null) { + queue = SetQueue.IMP.getNewQueue(world, true, false); + } + // Remove existing lighting first + if (queue instanceof NMSMappedFaweQueue) { + final NMSMappedFaweQueue nmsQueue = (NMSMappedFaweQueue) queue; + NMSRelighter relighter = new NMSRelighter(nmsQueue); + for (int x = minX; x <= maxX; x++) { + for (int z = minZ; z <= maxZ; z++) { + relighter.addChunk(x, z, null, 65535); + count++; + } + } + if (mode != FaweQueue.RelightMode.NONE) { + boolean sky = nmsQueue.hasSky(); + if (sky) { + relighter.fixSkyLighting(); + } + relighter.fixBlockLighting(); + } else { + relighter.removeLighting(); + } + relighter.sendChunks(); + } + return count; + } + + /** + * Set a task to run when the global queue (SetQueue class) is empty + * + * @param whenDone + */ + public static void addTask(final Runnable whenDone) { + SetQueue.IMP.addEmptyTask(whenDone); + } + + /** + * Have a task run when the server is low on memory (configured threshold) + * + * @param run + */ + public static void addMemoryLimitedTask(Runnable run) { + MemUtil.addMemoryLimitedTask(run); + } + + /** + * Have a task run when the server is no longer low on memory (configured threshold) + * + * @param run + */ + public static void addMemoryPlentifulTask(Runnable run) { + MemUtil.addMemoryPlentifulTask(run); + } + + /** + * @return + * @see BBC + */ + public static BBC[] getTranslations() { + return BBC.values(); + } + + /** + * @see #getEditSessionBuilder(com.sk89q.worldedit.world.World) + * @deprecated + */ + @Deprecated + public static EditSession getNewEditSession(@Nonnull FawePlayer player) { + if (player == null) { + throw new IllegalArgumentException("Player may not be null"); + } + return player.getNewEditSession(); + } + + /** + * @see #getEditSessionBuilder(com.sk89q.worldedit.world.World) + * @deprecated + */ + @Deprecated + public static EditSession getNewEditSession(World world) { + return WorldEdit.getInstance().getEditSessionFactory().getEditSession(world, -1); + } + +} diff --git a/worldedit-core/src/main/java/com/boydti/fawe/FaweCache.java b/worldedit-core/src/main/java/com/boydti/fawe/FaweCache.java new file mode 100644 index 000000000..41c0c23ce --- /dev/null +++ b/worldedit-core/src/main/java/com/boydti/fawe/FaweCache.java @@ -0,0 +1,218 @@ +package com.boydti.fawe; + +import com.sk89q.jnbt.*; +import com.sk89q.worldedit.world.biome.BaseBiome; + +import java.lang.reflect.Field; +import java.util.*; + +public class FaweCache { + /** + * [ y | z | x ] => index + */ + public final static short[][][] CACHE_I = new short[256][16][16]; + /** + * [ y | z | x ] => index + */ + public final static short[][][] CACHE_J = new short[256][16][16]; + + /** + * [ i | j ] => x + */ + public final static byte[][] CACHE_X = new byte[16][]; + /** + * [ i | j ] => y + */ + public final static short[][] CACHE_Y = new short[16][4096]; + /** + * [ i | j ] => z + */ + public final static byte[][] CACHE_Z = new byte[16][]; + + /** + * Immutable biome cache + */ + public final static BaseBiome[] CACHE_BIOME = new BaseBiome[256]; + + public static final BaseBiome getBiome(int id) { + return CACHE_BIOME[id]; + } + + static { + for (int i = 0; i < 256; i++) { + CACHE_BIOME[i] = new BaseBiome(i) { + @Override + public void setId(int id) { + throw new IllegalStateException("Cannot set id"); + } + }; + } + CACHE_X[0] = new byte[4096]; + CACHE_Z[0] = new byte[4096]; + for (int y = 0; y < 16; y++) { + CACHE_X[y] = CACHE_X[0]; + CACHE_Z[y] = CACHE_Z[0]; + } + for (int x = 0; x < 16; x++) { + for (int z = 0; z < 16; z++) { + for (int y = 0; y < 16; y++) { + final short j = (short) (((y & 0xF) << 8) | (z << 4) | x); + CACHE_X[0][j] = (byte) x; + CACHE_Z[0][j] = (byte) z; + } + } + } + for (int x = 0; x < 16; x++) { + for (int z = 0; z < 16; z++) { + for (int y = 0; y < 256; y++) { + final short i = (short) (y >> 4); + final short j = (short) (((y & 0xF) << 8) | (z << 4) | x); + CACHE_I[y][z][x] = i; + CACHE_J[y][z][x] = j; + CACHE_Y[i][j] = (short) y; + } + } + } + } + + public static Map asMap(Object... pairs) { + HashMap map = new HashMap(pairs.length >> 1); + for (int i = 0; i < pairs.length; i += 2) { + String key = (String) pairs[i]; + Object value = pairs[i + 1]; + map.put(key, value); + } + return map; + } + + public static ShortTag asTag(short value) { + return new ShortTag(value); + } + + public static IntTag asTag(int value) { + return new IntTag(value); + } + + public static DoubleTag asTag(double value) { + return new DoubleTag(value); + } + + public static ByteTag asTag(byte value) { + return new ByteTag(value); + } + + public static FloatTag asTag(float value) { + return new FloatTag(value); + } + + public static LongTag asTag(long value) { + return new LongTag(value); + } + + public static ByteArrayTag asTag(byte[] value) { + return new ByteArrayTag(value); + } + + public static IntArrayTag asTag(int[] value) { + return new IntArrayTag(value); + } + + public static LongArrayTag asTag(long[] value) { + return new LongArrayTag(value); + } + + public static StringTag asTag(String value) { + return new StringTag(value); + } + + public static CompoundTag asTag(Map value) { + HashMap map = new HashMap<>(); + for (Map.Entry entry : value.entrySet()) { + Object child = entry.getValue(); + Tag tag = asTag(child); + map.put(entry.getKey(), tag); + } + return new CompoundTag(map); + } + + public static Tag asTag(Object value) { + if (value instanceof Integer) { + return asTag((int) value); + } else if (value instanceof Short) { + return asTag((short) value); + } else if (value instanceof Double) { + return asTag((double) value); + } else if (value instanceof Byte) { + return asTag((byte) value); + } else if (value instanceof Float) { + return asTag((float) value); + } else if (value instanceof Long) { + return asTag((long) value); + } else if (value instanceof String) { + return asTag((String) value); + } else if (value instanceof Map) { + return asTag((Map) value); + } else if (value instanceof Collection) { + return asTag((Collection) value); + } else if (value instanceof Object[]) { + return asTag((Object[]) value); + } else if (value instanceof byte[]) { + return asTag((byte[]) value); + } else if (value instanceof int[]) { + return asTag((int[]) value); + } else if (value instanceof long[]) { + return asTag((long[]) value); + } else if (value instanceof Tag) { + return (Tag) value; + } else if (value instanceof Boolean) { + return asTag((byte) ((boolean) value ? 1 : 0)); + } else if (value == null) { + System.out.println("Invalid nbt: " + value); + return null; + } else { + Class clazz = value.getClass(); + if (clazz.getName().startsWith("com.intellectualcrafters.jnbt")) { + try { + if (clazz.getName().equals("com.intellectualcrafters.jnbt.EndTag")) { + return new EndTag(); + } + Field field = clazz.getDeclaredField("value"); + field.setAccessible(true); + return asTag(field.get(value)); + } catch (Throwable e) { + e.printStackTrace(); + } + } + System.out.println("Invalid nbt: " + value); + return null; + } + } + + public static ListTag asTag(Object... values) { + Class clazz = null; + List list = new ArrayList<>(values.length); + for (Object value : values) { + Tag tag = asTag(value); + if (clazz == null) { + clazz = tag.getClass(); + } + list.add(tag); + } + if (clazz == null) clazz = EndTag.class; + return new ListTag(clazz, list); + } + + public static ListTag asTag(Collection values) { + Class clazz = null; + List list = new ArrayList<>(values.size()); + for (Object value : values) { + Tag tag = asTag(value); + if (clazz == null) { + clazz = tag.getClass(); + } + list.add(tag); + } + if (clazz == null) clazz = EndTag.class; + return new ListTag(clazz, list); + } +} diff --git a/worldedit-core/src/main/java/com/boydti/fawe/FaweVersion.java b/worldedit-core/src/main/java/com/boydti/fawe/FaweVersion.java new file mode 100644 index 000000000..c2a826ab7 --- /dev/null +++ b/worldedit-core/src/main/java/com/boydti/fawe/FaweVersion.java @@ -0,0 +1,32 @@ +package com.boydti.fawe; + +public class FaweVersion { + public final int year, month, day, hash, build, major, minor, patch; + + public FaweVersion(String version) { + String[] split = version.substring(version.indexOf('=') + 1).split("-"); + if (split[0].equals("unknown")) { + this.year = month = day = hash = build = major = minor = patch = 0; + return; + } + String[] date = split[0].split("\\."); + this.year = Integer.parseInt(date[0]); + this.month = Integer.parseInt(date[1]); + this.day = Integer.parseInt(date[2]); + this.hash = Integer.parseInt(split[1], 16); + this.build = Integer.parseInt(split[2]); + String[] semver = split[3].split("\\."); + this.major = Integer.parseInt(semver[0]); + this.minor = Integer.parseInt(semver[1]); + this.patch = Integer.parseInt(semver[2]); + } + + @Override + public String toString() { + return "FastAsyncWorldEdit-" + year + "." + month + "." + day + "-" + Integer.toHexString(hash) + "-" + build; + } + + public boolean isNewer(FaweVersion other) { + return other.build < this.build && (this.major > other.major || (this.major == other.major && this.minor > other.minor) || (this.major == other.major && this.minor == other.minor && this.patch > other.patch)); + } +} \ No newline at end of file diff --git a/worldedit-core/src/main/java/com/boydti/fawe/IFawe.java b/worldedit-core/src/main/java/com/boydti/fawe/IFawe.java new file mode 100644 index 000000000..5708ee912 --- /dev/null +++ b/worldedit-core/src/main/java/com/boydti/fawe/IFawe.java @@ -0,0 +1,68 @@ +package com.boydti.fawe; + +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.TaskManager; +import com.boydti.fawe.util.cui.CUI; +import com.boydti.fawe.util.gui.FormBuilder; +import com.boydti.fawe.util.image.ImageViewer; +import com.sk89q.worldedit.world.World; +import java.io.File; +import java.util.Collection; +import java.util.UUID; + +public interface IFawe { + public void debug(final String s); + + public File getDirectory(); + + public void setupCommand(final String label, final FaweCommand cmd); + + public FawePlayer wrap(final Object obj); + + public void setupVault(); + + public TaskManager getTaskManager(); + + public FaweQueue getNewQueue(World world, boolean fast); + + public FaweQueue getNewQueue(String world, boolean fast); + + public String getWorldName(World world); + + public Collection getMaskManagers(); + + public void startMetrics(); + + default CUI getCUI(FawePlayer player) { return null; } + + default ImageViewer getImageViewer(FawePlayer player) { return null; } + + public default void registerPacketListener() {} + + default int getPlayerCount() { + return Fawe.get().getCachedPlayers().size(); + } + + public String getPlatformVersion(); + + public boolean isOnlineMode(); + + public String getPlatform(); + + public UUID getUUID(String name); + + public String getName(UUID uuid); + + public Object getBlocksHubApi(); + + public default String getDebugInfo() { + return ""; + } + + public default FormBuilder getFormBuilder() { + return null; + } +} diff --git a/worldedit-core/src/main/java/com/boydti/fawe/command/AnvilCommands.java b/worldedit-core/src/main/java/com/boydti/fawe/command/AnvilCommands.java new file mode 100644 index 000000000..707bf8beb --- /dev/null +++ b/worldedit-core/src/main/java/com/boydti/fawe/command/AnvilCommands.java @@ -0,0 +1,693 @@ +package com.boydti.fawe.command; + +import com.boydti.fawe.Fawe; +import com.boydti.fawe.FaweAPI; +import com.boydti.fawe.FaweCache; +import com.boydti.fawe.config.BBC; +import com.boydti.fawe.jnbt.anvil.*; +import com.boydti.fawe.jnbt.anvil.filters.*; +import com.boydti.fawe.jnbt.anvil.history.IAnvilHistory; +import com.boydti.fawe.jnbt.anvil.history.NullAnvilHistory; +import com.boydti.fawe.object.FawePlayer; +import com.boydti.fawe.object.FaweQueue; +import com.boydti.fawe.object.RegionWrapper; +import com.boydti.fawe.object.RunnableVal4; +import com.boydti.fawe.object.changeset.AnvilHistory; +import com.boydti.fawe.object.clipboard.remap.ClipboardRemapper; +import com.boydti.fawe.object.mask.FaweBlockMatcher; +import com.boydti.fawe.util.MainUtil; +import com.boydti.fawe.util.SetQueue; +import com.boydti.fawe.util.StringMan; +import com.sk89q.minecraft.util.commands.Command; +import com.sk89q.minecraft.util.commands.CommandPermissions; +import com.sk89q.worldedit.*; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.blocks.BaseBlock; +import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.blocks.BlockType; +import com.sk89q.worldedit.entity.Player; +import com.sk89q.worldedit.function.pattern.Pattern; +import com.sk89q.worldedit.function.pattern.RandomPattern; +import com.sk89q.worldedit.internal.annotation.Selection; +import com.sk89q.worldedit.regions.CuboidRegion; +import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.util.command.binding.Switch; +import com.sk89q.worldedit.util.command.parametric.Optional; +import com.sk89q.worldedit.world.World; +import com.sk89q.worldedit.world.biome.BaseBiome; + +import java.io.IOException; +import java.io.RandomAccessFile; +import java.util.*; +import java.util.function.Consumer; + + +import static com.google.common.base.Preconditions.checkNotNull; + +@Command(aliases = {"/anvil"}, desc = "Manipulate billions of blocks: [More Info](https://github.com/boy0001/FastAsyncWorldedit/wiki/Anvil-API)") +public class AnvilCommands { + + private final WorldEdit worldEdit; + + /** + * Create a new instance. + * + * @param worldEdit reference to WorldEdit + */ + public AnvilCommands(WorldEdit worldEdit) { + checkNotNull(worldEdit); + this.worldEdit = worldEdit; + } + + /** + * Run safely on an unloaded world (no selection) + * + * @param player + * @param folder + * @param filter + * @param + * @param + * @return + */ + @Deprecated + public static > T runWithWorld(Player player, String folder, T filter, boolean force) { + return runWithWorld(player, folder, filter, force, false); + } + + + @Deprecated + public static > T runWithWorld(Player player, String folder, T filter, boolean force, boolean unsafe) { + boolean copy = false; + if (FaweAPI.getWorld(folder) != null) { + if (!force) { + BBC.WORLD_IS_LOADED.send(player); + return null; + } + copy = true; + } + FaweQueue defaultQueue = SetQueue.IMP.getNewQueue(folder, true, false); + MCAQueue queue = new MCAQueue(defaultQueue); + if (copy && !unsafe) { + return queue.filterCopy(filter, RegionWrapper.GLOBAL()); + } else { + return queue.filterWorld(filter); + } + } + + /** + * Run safely on an existing world within a selection + * + * @param player + * @param editSession + * @param selection + * @param filter + * @param + * @param + * @return + */ + @Deprecated + public static > T runWithSelection(Player player, EditSession editSession, Region selection, T filter) { + if (!(selection instanceof CuboidRegion)) { + BBC.NO_REGION.send(player); + return null; + } + CuboidRegion cuboid = (CuboidRegion) selection; + RegionWrapper wrappedRegion = new RegionWrapper(cuboid.getMinimumPoint(), cuboid.getMaximumPoint()); + String worldName = Fawe.imp().getWorldName(editSession.getWorld()); + FaweQueue tmp = SetQueue.IMP.getNewQueue(worldName, true, false); + MCAQueue queue = new MCAQueue(tmp); + FawePlayer fp = FawePlayer.wrap(player); + fp.checkAllowedRegion(selection); + recordHistory(fp, editSession.getWorld(), iAnvilHistory -> { + queue.filterCopy(filter, wrappedRegion, iAnvilHistory); + }); + return filter; + } + + public static void recordHistory(FawePlayer fp, World world, Consumer run) { + LocalSession session = fp.getSession(); + if (session == null || session.hasFastMode()) { + run.accept(new NullAnvilHistory()); + } else { + AnvilHistory history = new AnvilHistory(Fawe.imp().getWorldName(world), fp.getUUID()); + run.accept(history); + session.remember(fp.getPlayer(), world, history, fp.getLimit()); + } + } + +// @Command( +// aliases = {"replaceall", "rea", "repall"}, +// usage = " [from-block] ", +// desc = "Replace all blocks in the selection with another", +// help = "Replace all blocks in the selection with another\n" + +// "The -d flag disabled wildcard data matching\n", +// flags = "df", +// min = 2, +// max = 4 +// ) +// @CommandPermissions("worldedit.anvil.replaceall") +// public void replaceAll(Player player, String folder, @Optional String from, String to, @Switch('d') boolean useData) throws WorldEditException { +// final FaweBlockMatcher matchFrom; +// if (from == null) { +// matchFrom = FaweBlockMatcher.NOT_AIR; +// } else { +// if (from.contains(":")) { +// useData = true; //override d flag, if they specified data they want it +// } +// matchFrom = FaweBlockMatcher.fromBlocks(worldEdit.getBlocks(player, from, true), useData); +// } +// final FaweBlockMatcher matchTo = FaweBlockMatcher.setBlocks(worldEdit.getBlocks(player, to, true)); +// ReplaceSimpleFilter filter = new ReplaceSimpleFilter(matchFrom, matchTo); +// ReplaceSimpleFilter result = runWithWorld(player, folder, filter, true); +// if (result != null) player.print(BBC.getPrefix() + BBC.VISITOR_BLOCK.format(result.getTotal())); +// } + + @Command( + aliases = {"remapall"}, + usage = "", + help = "Remap the world between MCPE/PC values", + desc = "Remap the world between MCPE/PC values", + min = 1, + max = 1 + ) + @CommandPermissions("worldedit.anvil.remapall") + public void remapall(Player player, String folder) throws WorldEditException { + ClipboardRemapper mapper; + ClipboardRemapper.RemapPlatform from; + ClipboardRemapper.RemapPlatform to; + if (Fawe.imp().getPlatform().equalsIgnoreCase("nukkit")) { + from = ClipboardRemapper.RemapPlatform.PC; + to = ClipboardRemapper.RemapPlatform.PE; + } else { + from = ClipboardRemapper.RemapPlatform.PE; + to = ClipboardRemapper.RemapPlatform.PC; + } + RemapFilter filter = new RemapFilter(from, to); + RemapFilter result = runWithWorld(player, folder, filter, true); + if (result != null) player.print(BBC.getPrefix() + BBC.VISITOR_BLOCK.format(result.getTotal())); + } + + + @Command( + aliases = {"deleteallunvisited", "delunvisited" }, + usage = " [file-age=60000]", + desc = "Delete all chunks which haven't been occupied", + help = "Delete all chunks which haven't been occupied for `age-ticks` (20t = 1s) and \n" + + "Have not been accessed since `file-duration` (ms) after creation and\n" + + "Have not been used in the past `chunk-inactivity` (ms)" + + "The auto-save interval is the recommended value for `file-duration` and `chunk-inactivity`", + min = 2, + max = 3 + ) + @CommandPermissions("worldedit.anvil.deleteallunvisited") + public void deleteAllUnvisited(Player player, String folder, int inhabitedTicks, @Optional("60000") int fileDurationMillis) throws WorldEditException { + long chunkInactivityMillis = fileDurationMillis; // Use same value for now + DeleteUninhabitedFilter filter = new DeleteUninhabitedFilter(fileDurationMillis, inhabitedTicks, chunkInactivityMillis); + DeleteUninhabitedFilter result = runWithWorld(player, folder, filter, true); + if (result != null) player.print(BBC.getPrefix() + BBC.VISITOR_BLOCK.format(result.getTotal())); + } + + @Command( + aliases = {"deleteallunclaimed", "delallunclaimed" }, + usage = " [file-age=60000]", + desc = "(Supports: WG, P2, GP) Delete all chunks which haven't been occupied AND claimed", + help = "(Supports: WG, P2, GP) Delete all chunks which aren't claimed AND haven't been occupied for `age-ticks` (20t = 1s) and \n" + + "Have not been accessed since `file-duration` (ms) after creation and\n" + + "Have not been used in the past `chunk-inactivity` (ms)" + + "The auto-save interval is the recommended value for `file-duration` and `chunk-inactivity`", + min = 1, + max = 3 + ) + @CommandPermissions("worldedit.anvil.deleteallunclaimed") + public void deleteAllUnclaimed(Player player, int inhabitedTicks, @Optional("60000") int fileDurationMillis, @Switch('d') boolean debug) throws WorldEditException { + String folder = Fawe.imp().getWorldName(player.getWorld()); + long chunkInactivityMillis = fileDurationMillis; // Use same value for now + DeleteUnclaimedFilter filter = new DeleteUnclaimedFilter(player.getWorld(), fileDurationMillis, inhabitedTicks, chunkInactivityMillis); + if (debug) filter.enableDebug(); + DeleteUnclaimedFilter result = runWithWorld(player, folder, filter, true); + if (result != null) player.print(BBC.getPrefix() + BBC.VISITOR_BLOCK.format(result.getTotal())); + } + + @Command( + aliases = {"deleteunclaimed"}, + usage = " [file-age=60000]", + desc = "(Supports: WG, P2, GP) Delete all chunks which haven't been occupied AND claimed", + help = "(Supports: WG, P2, GP) Delete all chunks which aren't claimed AND haven't been occupied for `age-ticks` (20t = 1s) and \n" + + "Have not been accessed since `file-duration` (ms) after creation and\n" + + "Have not been used in the past `chunk-inactivity` (ms)" + + "The auto-save interval is the recommended value for `file-duration` and `chunk-inactivity`", + min = 1, + max = 3 + ) + @CommandPermissions("worldedit.anvil.deleteunclaimed") + public void deleteUnclaimed(Player player, EditSession editSession, @Selection Region selection, int inhabitedTicks, @Optional("60000") int fileDurationMillis, @Switch('d') boolean debug) throws WorldEditException { + String folder = Fawe.imp().getWorldName(player.getWorld()); + long chunkInactivityMillis = fileDurationMillis; // Use same value for now + DeleteUnclaimedFilter filter = new DeleteUnclaimedFilter(player.getWorld(), fileDurationMillis, inhabitedTicks, chunkInactivityMillis); + if (debug) filter.enableDebug(); + DeleteUnclaimedFilter result = runWithSelection(player, editSession, selection, filter); + if (result != null) player.print(BBC.getPrefix() + BBC.VISITOR_BLOCK.format(result.getTotal())); + } + + @Command( + aliases = {"deletealloldregions", "deloldreg" }, + usage = "