diff --git a/.gitignore b/.gitignore index ac3619cc..ba19d301 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ gradle.log /forge189/build /forge1710/build /sponge/build +/sponge111/build /bukkit/build /bukkit0/build /bukkit19/build @@ -27,4 +28,4 @@ build /mvn spigot-1.10 wiki_permissions.md -/textures \ No newline at end of file +/textures diff --git a/settings.gradle b/settings.gradle index 5f4acd2d..47c92782 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,3 +1,3 @@ rootProject.name = 'FastAsyncWorldEdit' -include 'core', 'bukkit', 'favs', 'nukkit', 'forge1710', 'forge189', 'forge194', 'forge110', 'forge111', 'sponge', 'forge112' \ No newline at end of file +include 'core', 'bukkit', 'favs', 'nukkit', 'forge1710', 'forge189', 'forge194', 'forge110', 'forge111', 'sponge', 'forge112', 'sponge111' \ No newline at end of file diff --git a/sponge111/.gitignore b/sponge111/.gitignore new file mode 100644 index 00000000..f547dc2c --- /dev/null +++ b/sponge111/.gitignore @@ -0,0 +1,2 @@ +/.gradle/ +/bin/ diff --git a/sponge111/build.gradle b/sponge111/build.gradle new file mode 100644 index 00000000..745aa077 --- /dev/null +++ b/sponge111/build.gradle @@ -0,0 +1,109 @@ +buildscript { + repositories { + jcenter() + maven { + name = "forge" + url = "http://files.minecraftforge.net/maven" + } + maven { + name = 'minecrell' + url = 'http://repo.minecrell.net/releases' + } + maven {url = "https://oss.sonatype.org/content/repositories/snapshots/"} + maven {url = "http://repo.minecrell.net/snapshots"} + maven { url = "http://files.minecraftforge.net/maven" } + maven { url = "http://repo.minecrell.net/releases" } + maven { url = "https://oss.sonatype.org/content/repositories/snapshots/" } + } + dependencies { + classpath 'net.minecrell:VanillaGradle:2.0.3_1' + classpath 'net.minecraftforge.gradle:ForgeGradle:2.2-SNAPSHOT' + } +} + +plugins { + id 'org.spongepowered.plugin' version '0.6' +} + +apply plugin: 'net.minecrell.vanilla.server.library' +apply plugin: 'com.github.johnrengelman.shadow' + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + +repositories { + flatDir {dirs 'lib'} + maven { + name = 'forge' + url = 'http://files.minecraftforge.net/maven' + } + maven { + name = "Sponge" + url = "https://repo.spongepowered.org/maven" + } + maven { + name = "Sponge Metrics" + url = "http://repo.mcstats.org/content/repositories/releases/" + } +} + +dependencies { + compile project(':core') + compile 'org.spongepowered:spongeapi:6.0.0-SNAPSHOT' + compile 'org.spongepowered:mixin:0.6.1-SNAPSHOT' + compile 'com.sk89q.worldedit:worldedit-sponge:6.1.7-SNAPSHOT' + compile name: 'worldedit-core-6.1.7-SNAPSHOT-dist' +} + +minecraft { + version = "1.11" + mappings = "snapshot_20161116" + runDir = 'run' +} + +project.archivesBaseName = "${project.archivesBaseName}-mc${minecraft.version}" + +processResources { + from(sourceSets.main.resources.srcDirs) { + expand 'version': project.version, + 'mcVersion': project.minecraft.version + exclude 'mcmod.info' + } +} + +shadowJar { + relocate 'org.yaml.snakeyaml', 'com.boydti.fawe.yaml' + dependencies { + include(dependency(':core')) + include(dependency('com.github.luben:zstd-jni:1.1.1')) +// include(dependency('org.javassist:javassist:3.22.0-CR1')) + include(dependency('co.aikar:fastutil-lite:1.0')) + include(dependency(name: 'worldedit-core-6.1.7-SNAPSHOT-dist')) + include(dependency('com.sk89q.worldedit:worldedit-sponge:6.1.7-SNAPSHOT')) + include(dependency('org.yaml:snakeyaml:1.16')) + } + archiveName = "${parent.name}-${project.name}-${parent.version}.jar" + destinationDir = file '../target' +} +shadowJar.doLast { + task -> + ant.checksum file: task.archivePath +} + + +reobf { + shadowJar { + mappingType = 'SEARGE' + } +} + +task deobfJar(type: Jar) { + from sourceSets.main.output + classifier = 'dev' +} + +artifacts { + archives deobfJar +} + +build.dependsOn(shadowJar) diff --git a/sponge111/lib/worldedit-core-6.1.7-SNAPSHOT-dist.jar b/sponge111/lib/worldedit-core-6.1.7-SNAPSHOT-dist.jar new file mode 100644 index 00000000..2551c6fd Binary files /dev/null and b/sponge111/lib/worldedit-core-6.1.7-SNAPSHOT-dist.jar differ diff --git a/sponge111/src/main/java/com/boydti/fawe/SpongeCommand.java b/sponge111/src/main/java/com/boydti/fawe/SpongeCommand.java new file mode 100644 index 00000000..5a5d5e29 --- /dev/null +++ b/sponge111/src/main/java/com/boydti/fawe/SpongeCommand.java @@ -0,0 +1,62 @@ +package com.boydti.fawe; + +import com.boydti.fawe.config.BBC; +import com.boydti.fawe.object.FaweCommand; +import com.boydti.fawe.object.FawePlayer; +import java.util.List; +import java.util.Optional; +import javax.annotation.Nullable; +import org.spongepowered.api.command.CommandCallable; +import org.spongepowered.api.command.CommandException; +import org.spongepowered.api.command.CommandResult; +import org.spongepowered.api.command.CommandSource; +import org.spongepowered.api.text.Text; +import org.spongepowered.api.world.Location; +import org.spongepowered.api.world.World; + +/** + * Created by Jesse on 4/2/2016. + */ +public class SpongeCommand implements CommandCallable { + + private final FaweCommand cmd; + + public SpongeCommand(final FaweCommand cmd) { + this.cmd = cmd; + } + + @Override + public CommandResult process(CommandSource source, String args) throws CommandException { + final FawePlayer plr = Fawe.imp().wrap(source); + if (!source.hasPermission(this.cmd.getPerm())) { + BBC.NO_PERM.send(plr, this.cmd.getPerm()); + return CommandResult.success(); + } + this.cmd.executeSafe(plr, args.split(" ")); + return CommandResult.success(); + } + + @Override + public List getSuggestions(CommandSource source, String arguments, @Nullable Location targetPosition) throws CommandException { + return null; + } + + + @Override + public boolean testPermission(CommandSource source) {return true;} + + @Override + public Optional getShortDescription(CommandSource source) { + return Optional.of(Text.of("Various")); + } + + @Override + public Optional getHelp(CommandSource source) { + return Optional.of(Text.of("/" + this.cmd)); + } + + @Override + public Text getUsage(final CommandSource cmd) { + return Text.of("/"); + } +} diff --git a/sponge111/src/main/java/com/boydti/fawe/sponge/FaweSponge.java b/sponge111/src/main/java/com/boydti/fawe/sponge/FaweSponge.java new file mode 100644 index 00000000..161bfe5c --- /dev/null +++ b/sponge111/src/main/java/com/boydti/fawe/sponge/FaweSponge.java @@ -0,0 +1,153 @@ +package com.boydti.fawe.sponge; + +import com.boydti.fawe.Fawe; +import com.boydti.fawe.IFawe; +import com.boydti.fawe.SpongeCommand; +import com.boydti.fawe.config.BBC; +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.MainUtil; +import com.boydti.fawe.util.TaskManager; +import com.sk89q.worldedit.sponge.chat.SpongeChatManager; +import com.sk89q.worldedit.world.World; +import java.io.File; +import java.util.ArrayList; +import java.util.Collection; +import java.util.UUID; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.profile.GameProfile; +import org.spongepowered.api.profile.GameProfileManager; +import org.spongepowered.api.text.serializer.TextSerializers; + + +import static org.spongepowered.api.Sponge.getGame; + +public class FaweSponge implements IFawe { + + public final SpongeMain plugin; + + public FaweSponge instance; + + public FaweSponge(SpongeMain plugin) { + instance = this; + this.plugin = plugin; + try { + Fawe.set(this); + Fawe.setupInjector(); + com.sk89q.worldedit.sponge.SpongePlayer.inject(); + Fawe.get().setChatManager(new SpongeChatManager()); + } catch (final Throwable e) { + MainUtil.handleError(e); + } + } + + @Override + public void debug(String message) { + message = BBC.color(message); + Sponge.getServer().getConsole().sendMessage(TextSerializers.LEGACY_FORMATTING_CODE.deserialize(BBC.color(message))); + } + + @Override + public File getDirectory() { + return new File("config/FastAsyncWorldEdit"); + } + + @Override + public void setupCommand(String label, FaweCommand cmd) { + getGame().getCommandManager().register(plugin, new SpongeCommand(cmd), label); + } + + @Override + public FawePlayer wrap(Object obj) { + if (obj.getClass() == String.class) { + String name = (String) obj; + FawePlayer existing = Fawe.get().getCachedPlayer(name); + if (existing != null) { + return existing; + } + Player player = Sponge.getServer().getPlayer(name).orElseGet(null); + return player != null ? new SpongePlayer(player) : null; + } else if (obj instanceof Player) { + Player player = (Player) obj; + FawePlayer existing = Fawe.get().getCachedPlayer(player.getName()); + return existing != null ? existing : new SpongePlayer(player); + } else { + return null; + } + } + + @Override + public void setupVault() { + debug("Permission hook not implemented yet!"); + } + + @Override + public TaskManager getTaskManager() { + return new SpongeTaskMan(plugin); + } + + @Override + public FaweQueue getNewQueue(World world, boolean fast) { + return new com.boydti.fawe.sponge.v1_11.SpongeQueue_1_11(getWorldName(world)); + } + + @Override + public FaweQueue getNewQueue(String world, boolean fast) { + return new com.boydti.fawe.sponge.v1_11.SpongeQueue_1_11(world); + } + + @Override + public String getWorldName(World world) { + return world.getName(); + } + + @Override + public Collection getMaskManagers() { + return new ArrayList<>(); + } + + @Override + public void startMetrics() { + try { + SpongeMetrics metrics = new SpongeMetrics(Sponge.getGame(), Sponge.getPluginManager().fromInstance(plugin).get()); + metrics.start(); + } catch (Throwable e) { + debug("[FAWE] &cFailed to load up metrics."); + } + } + + @Override + public String getPlatform() { + return "sponge"; + } + + @Override + public UUID getUUID(String name) { + try { + GameProfileManager pm = Sponge.getServer().getGameProfileManager(); + GameProfile profile = pm.get(name).get(); + return profile != null ? profile.getUniqueId() : null; + } catch (Exception e) { + return null; + } + } + + @Override + public String getName(UUID uuid) { + try { + GameProfileManager pm = Sponge.getServer().getGameProfileManager(); + GameProfile profile = pm.get(uuid).get(); + return profile != null ? profile.getName().orElse(null) : null; + } catch (Exception e) { + return null; + } + } + + @Override + public Object getBlocksHubApi() { + return null; + } +} diff --git a/sponge111/src/main/java/com/boydti/fawe/sponge/SpongeMain.java b/sponge111/src/main/java/com/boydti/fawe/sponge/SpongeMain.java new file mode 100644 index 00000000..4af9cd5e --- /dev/null +++ b/sponge111/src/main/java/com/boydti/fawe/sponge/SpongeMain.java @@ -0,0 +1,62 @@ +package com.boydti.fawe.sponge; + +import com.boydti.fawe.Fawe; +import com.boydti.fawe.config.Settings; +import com.boydti.fawe.object.FawePlayer; +import com.google.inject.Inject; +import org.slf4j.Logger; +import org.spongepowered.api.Game; +import org.spongepowered.api.Server; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.event.Listener; +import org.spongepowered.api.event.Order; +import org.spongepowered.api.event.game.state.GamePreInitializationEvent; +import org.spongepowered.api.event.network.ClientConnectionEvent; +import org.spongepowered.api.plugin.Plugin; +import org.spongepowered.api.plugin.PluginContainer; +import org.spongepowered.api.profile.GameProfileManager; + +@Plugin(id = "fastasyncworldedit", name = " FastAsyncWorldEdit", description = "fawe", url = "https://github.com/boy0001/FastAsyncWorldedit", version = "development", authors = "Empire92") +public class SpongeMain { + @Inject + public PluginContainer plugin; + + @Inject + private Logger logger; + + @Inject + private Game game; + private Server server; + + private GameProfileManager resolver; + + public Game getGame() { + return this.game; + } + + public Server getServer() { + return this.server; + } + + public GameProfileManager getResolver() { + if (this.resolver == null) { + this.resolver = this.game.getServer().getGameProfileManager(); + } + return this.resolver; + } + + @Listener(order = Order.PRE) + public void onGamePreInit(GamePreInitializationEvent event) { + this.server = this.game.getServer(); + new FaweSponge(this); + Settings.IMP.QUEUE.PARALLEL_THREADS = 1; + } + + @Listener + public void onQuit(ClientConnectionEvent.Disconnect event) { + Player player = event.getTargetEntity(); + FawePlayer fp = FawePlayer.wrap(player); + fp.unregister(); + Fawe.get().unregister(player.getName()); + } +} diff --git a/sponge111/src/main/java/com/boydti/fawe/sponge/SpongeMetrics.java b/sponge111/src/main/java/com/boydti/fawe/sponge/SpongeMetrics.java new file mode 100644 index 00000000..1daa6443 --- /dev/null +++ b/sponge111/src/main/java/com/boydti/fawe/sponge/SpongeMetrics.java @@ -0,0 +1,519 @@ +package com.boydti.fawe.sponge; + +/* + * Copyright 2011-2013 Tyler Blair. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and contributors and should not be interpreted as representing official policies, + * either expressed or implied, of anybody else. + */ + +import com.boydti.fawe.Fawe; +import com.boydti.fawe.object.io.FastByteArrayOutputStream; +import com.boydti.fawe.util.MainUtil; +import com.google.inject.Inject; +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.net.Proxy; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLEncoder; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.zip.GZIPOutputStream; +import ninja.leaping.configurate.commented.CommentedConfigurationNode; +import ninja.leaping.configurate.hocon.HoconConfigurationLoader; +import ninja.leaping.configurate.loader.ConfigurationLoader; +import org.spongepowered.api.Game; +import org.spongepowered.api.plugin.PluginContainer; +import org.spongepowered.api.scheduler.Task; + +public class SpongeMetrics { + + /** + * The current revision number + */ + private final static 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 game data is being sent for + */ + private final Game game; + + /** + * The plugin this metrics submits for + */ + private final PluginContainer plugin; + /** + * Lock for synchronization + */ + private final Object optOutLock = new Object(); + /** + * The plugin configuration file + */ + private CommentedConfigurationNode config; + /** + * The configuration loader + */ + private ConfigurationLoader configurationLoader; + /** + * The plugin configuration file + */ + private File configurationFile; + /** + * Unique server id + */ + private String guid; + /** + * Debug mode + */ + private boolean debug; + /** + * The scheduled task + */ + private volatile Task task = null; + + @Inject + public SpongeMetrics(final Game game, final PluginContainer plugin) throws IOException { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } + + this.game = game; + this.plugin = plugin; + + loadConfiguration(); + } + + /** + * GZip compress a string of bytes + * + * @param input + * @return + */ + public static byte[] gzip(final String input) { + final FastByteArrayOutputStream baos = new FastByteArrayOutputStream(); + GZIPOutputStream gzos = null; + + try { + gzos = new GZIPOutputStream(baos); + gzos.write(input.getBytes("UTF-8")); + } catch (final IOException e) { + MainUtil.handleError(e); + } finally { + if (gzos != null) { + try { + gzos.close(); + } catch (final IOException ignore) { + } + } + } + + return baos.toByteArray(); + } + + /** + * Appends a json encoded key/value pair to the given string builder. + * + * @param json + * @param key + * @param value + * @throws java.io.UnsupportedEncodingException + */ + private static void appendJSONPair(final StringBuilder json, final String key, final String value) throws UnsupportedEncodingException { + boolean isValueNumeric = false; + + try { + if (value.equals("0") || !value.endsWith("0")) { + Double.parseDouble(value); + isValueNumeric = true; + } + } catch (final 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 + */ + private static String escapeJSON(final String text) { + final StringBuilder builder = new StringBuilder(); + + builder.append('"'); + for (int index = 0; index < text.length(); index++) { + final 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 < ' ') { + final 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(final String text) throws UnsupportedEncodingException { + return URLEncoder.encode(text, "UTF-8"); + } + + /** + * Loads the configuration + */ + private void loadConfiguration() { + configurationFile = getConfigFile(); + configurationLoader = HoconConfigurationLoader.builder().setFile(configurationFile).build(); + + try { + if (!configurationFile.exists()) { + configurationFile.createNewFile(); + config = configurationLoader.load(); + + config.setComment("This contains settings for MCStats: http://mcstats.org"); + config.getNode("mcstats.guid").setValue(UUID.randomUUID().toString()); + config.getNode("mcstats.opt-out").setValue(false); + config.getNode("mcstats.debug").setValue(false); + + configurationLoader.save(config); + } else { + config = configurationLoader.load(); + } + + guid = config.getNode("mcstats.guid").getString(); + debug = config.getNode("mcstats.debug").getBoolean(); + } catch (final IOException e) { + MainUtil.handleError(e); + } + } + + /** + * 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() { + synchronized (optOutLock) { + // Did we opt out? + if (isOptOut()) { + return false; + } + + // Is metrics already running? + if (task != null) { + return true; + } + + // Begin hitting the server with glorious data + final Task.Builder builder = game.getScheduler().createTaskBuilder(); + builder.async().interval(PING_INTERVAL, TimeUnit.MINUTES).execute(new Runnable() { + + private boolean firstPost = true; + + @Override + public void run() { + try { + // This has to be synchronized or it can collide with the disable method. + synchronized (optOutLock) { + // Disable Task, if it is running and the server owner decided to opt-out + if (isOptOut() && (task != null)) { + task.cancel(); + task = null; + } + } + + // We use the inverse of firstPost because if it is the first time we are posting, + // it is not a interval ping, so it evaluates to FALSE + // Each time thereafter it will evaluate to TRUE, i.e PING! + postPlugin(!firstPost); + + // After the first post we set firstPost to false + // Each post thereafter will be a ping + firstPost = false; + } catch (final IOException e) { + if (debug) { + Fawe.debug("[Metrics] " + e.getMessage()); + } + } + } + }); + return true; + } + } + + /** + * Has the server owner denied plugin metrics? + * + * @return true if metrics should be opted out of it + */ + public boolean isOptOut() { + synchronized (optOutLock) { + loadConfiguration(); + + return config.getNode("mcstats.opt-out").getBoolean(); + } + } + + /** + * Enables metrics for the server by setting "opt-out" to false in the config file and starting the metrics task. + * + * @throws java.io.IOException + */ + public void enable() throws IOException { + // This has to be synchronized or it can collide with the check in the task. + synchronized (optOutLock) { + // Check if the server owner has already set opt-out, if not, set it. + if (isOptOut()) { + config.getNode("mcstats.opt-out").setValue(false); + configurationLoader.save(config); + } + + // Enable Task, if it is not running + if (task == null) { + start(); + } + } + } + + /** + * Disables metrics for the server by setting "opt-out" to true in the config file and canceling the metrics task. + * + * @throws java.io.IOException + */ + public void disable() throws IOException { + // This has to be synchronized or it can collide with the check in the task. + synchronized (optOutLock) { + // Check if the server owner has already set opt-out, if not, set it. + if (!isOptOut()) { + config.getNode("mcstats.opt-out").setValue(true); + configurationLoader.save(config); + } + + // Disable Task, if it is running + if (task != null) { + task.cancel(); + 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() { + // TODO configDir + final File configFolder = new File("config"); + + return new File(configFolder, "PluginMetrics.conf"); + } + + /** + * Generic method that posts a plugin to the metrics website + * + */ + private void postPlugin(final boolean isPing) throws IOException { + // Server software specific section + final String pluginName = plugin.getName(); + final boolean onlineMode = game.getServer().getOnlineMode(); // TRUE if online mode is enabled + final String pluginVersion = plugin.getVersion().get(); + // TODO no visible way to get MC version at the moment + // TODO added by game.getPlatform().getMinecraftVersion() -- impl in 2.1 + final String serverVersion = String.format("%s %s", "Sponge", game.getPlatform().getMinecraftVersion()); + final int playersOnline = game.getServer().getOnlinePlayers().size(); + + // END server software specific section -- all code below does not use any code outside of this class / Java + + // Construct the post data + final StringBuilder json = new StringBuilder(1024); + json.append('{'); + + // The plugin's description file containg all of the plugin data such as name, version, author, etc + appendJSONPair(json, "guid", guid); + appendJSONPair(json, "plugin_version", pluginVersion); + appendJSONPair(json, "server_version", serverVersion); + appendJSONPair(json, "players_online", Integer.toString(playersOnline)); + + // New data as of R6 + final String osname = System.getProperty("os.name"); + String osarch = System.getProperty("os.arch"); + final String osversion = System.getProperty("os.version"); + final String java_version = System.getProperty("java.version"); + final 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"); + } + + // close json + json.append('}'); + + // Create the url + final 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(); + } + + final byte[] uncompressed = json.toString().getBytes(); + final 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 (debug) { + Fawe.debug("[Metrics] Prepared request for " + pluginName + " uncompressed=" + uncompressed.length + " compressed=" + compressed.length); + } + + // Write the data + final OutputStream os = connection.getOutputStream(); + os.write(compressed); + os.flush(); + + // Now read the response + final BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream())); + String response = reader.readLine(); + + // close resources + os.close(); + reader.close(); + + 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); + } + } + + /** + * 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 (final Exception e) { + return false; + } + } + +} \ No newline at end of file diff --git a/sponge111/src/main/java/com/boydti/fawe/sponge/SpongePlayer.java b/sponge111/src/main/java/com/boydti/fawe/sponge/SpongePlayer.java new file mode 100644 index 00000000..3c0a4036 --- /dev/null +++ b/sponge111/src/main/java/com/boydti/fawe/sponge/SpongePlayer.java @@ -0,0 +1,92 @@ +package com.boydti.fawe.sponge; + +import com.boydti.fawe.config.BBC; +import com.boydti.fawe.object.FaweLocation; +import com.boydti.fawe.object.FawePlayer; +import com.boydti.fawe.wrappers.FakePlayer; +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.extension.platform.Capability; +import com.sk89q.worldedit.extension.platform.Platform; +import java.lang.reflect.Method; +import java.util.UUID; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.text.Text; +import org.spongepowered.api.text.serializer.TextSerializers; +import org.spongepowered.api.text.title.Title; +import org.spongepowered.api.world.Location; +import org.spongepowered.api.world.World; + +public class SpongePlayer extends FawePlayer { + public SpongePlayer(final Player parent) { + super(parent); + } + + @Override + public void sendTitle(String head, String sub) { // Not supported + Text headText = TextSerializers.LEGACY_FORMATTING_CODE.deserialize(BBC.color(head)); + Text subText = TextSerializers.LEGACY_FORMATTING_CODE.deserialize(BBC.color(sub)); + final Title title = Title.builder().title(headText).subtitle(subText).fadeIn(0).stay(60).fadeOut(20).build(); + parent.sendTitle(title); + } + + @Override + public void resetTitle() { // Not supported + parent.resetTitle(); + } + + @Override + public String getName() { + return this.parent.getName(); + } + + @Override + public UUID getUUID() { + return this.parent.getUniqueId(); + } + + @Override + public boolean hasPermission(final String perm) { + Object meta = getMeta(perm); + return meta instanceof Boolean ? (boolean) meta : this.parent.hasPermission(perm); + } + + @Override + public void setPermission(final String perm, final boolean flag) { + setMeta(perm, flag); + } + + @Override + public void sendMessage(final String message) { + this.parent.sendMessage(TextSerializers.LEGACY_FORMATTING_CODE.deserialize(BBC.color(message))); + } + + @Override + public void executeCommand(final String cmd) { + Sponge.getGame().getCommandManager().process(this.parent, cmd); + } + + @Override + public FaweLocation getLocation() { + Location loc = this.parent.getLocation(); + return new FaweLocation(loc.getExtent().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()); + } + + @Override + public com.sk89q.worldedit.entity.Player toWorldEditPlayer() { + if (WorldEdit.getInstance().getPlatformManager().queryCapability(Capability.USER_COMMANDS) != null) { + for (Platform platform : WorldEdit.getInstance().getPlatformManager().getPlatforms()) { + return platform.matchPlayer(new FakePlayer(getName(), getUUID(), null)); + } + } + try { + Class clazz = Class.forName("com.sk89q.worldedit.sponge.SpongeWorldEdit"); + Object spongeWorldEdit = clazz.getDeclaredMethod("inst").invoke(null); + Method methodGetPlayer = clazz.getMethod("wrapPlayer", org.spongepowered.api.entity.living.player.Player.class); + return (com.sk89q.worldedit.entity.Player) methodGetPlayer.invoke(spongeWorldEdit, this.parent); + } catch (Throwable e) { + e.printStackTrace(); + return null; + } + } +} diff --git a/sponge111/src/main/java/com/boydti/fawe/sponge/SpongeTaskMan.java b/sponge111/src/main/java/com/boydti/fawe/sponge/SpongeTaskMan.java new file mode 100644 index 00000000..fb95eb94 --- /dev/null +++ b/sponge111/src/main/java/com/boydti/fawe/sponge/SpongeTaskMan.java @@ -0,0 +1,72 @@ +package com.boydti.fawe.sponge; + +import com.boydti.fawe.util.TaskManager; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.scheduler.Task; + +public class SpongeTaskMan extends TaskManager { + + private final SpongeMain plugin; + + public SpongeTaskMan(SpongeMain plugin) { + this.plugin = plugin; + } + + private final AtomicInteger i = new AtomicInteger(); + + private final ConcurrentHashMap tasks = new ConcurrentHashMap<>(); + + @Override + public int repeat(Runnable runnable, int interval) { + int val = this.i.incrementAndGet(); + Task.Builder builder = Sponge.getGame().getScheduler().createTaskBuilder(); + Task.Builder built = builder.delayTicks(interval).intervalTicks(interval).execute(runnable); + Task task = built.submit(plugin); + this.tasks.put(val, task); + return val; + } + + @Override + public int repeatAsync(Runnable runnable, int interval) { + int val = this.i.incrementAndGet(); + Task.Builder builder = Sponge.getGame().getScheduler().createTaskBuilder(); + Task.Builder built = builder.delayTicks(interval).async().intervalTicks(interval).execute(runnable); + Task task = built.submit(plugin); + this.tasks.put(val, task); + return val; + } + + @Override + public void async(Runnable runnable) { + Task.Builder builder = Sponge.getGame().getScheduler().createTaskBuilder(); + builder.async().execute(runnable).submit(plugin); + } + + @Override + public void task(Runnable runnable) { + Task.Builder builder = Sponge.getGame().getScheduler().createTaskBuilder(); + builder.execute(runnable).submit(plugin); + } + + @Override + public void later(Runnable runnable, int delay) { + Task.Builder builder = Sponge.getGame().getScheduler().createTaskBuilder(); + builder.delayTicks(delay).execute(runnable).submit(plugin); + } + + @Override + public void laterAsync(Runnable runnable, int delay) { + Task.Builder builder = Sponge.getGame().getScheduler().createTaskBuilder(); + builder.async().delayTicks(delay).execute(runnable).submit(plugin); + } + + @Override + public void cancel(int i) { + Task task = this.tasks.remove(i); + if (task != null) { + task.cancel(); + } + } +} \ No newline at end of file diff --git a/sponge111/src/main/java/com/boydti/fawe/sponge/v1_11/MutableGenLayer.java b/sponge111/src/main/java/com/boydti/fawe/sponge/v1_11/MutableGenLayer.java new file mode 100644 index 00000000..14e13bea --- /dev/null +++ b/sponge111/src/main/java/com/boydti/fawe/sponge/v1_11/MutableGenLayer.java @@ -0,0 +1,26 @@ +package com.boydti.fawe.sponge.v1_11; + +import java.util.Arrays; +import net.minecraft.world.gen.layer.GenLayer; +import net.minecraft.world.gen.layer.IntCache; + +public class MutableGenLayer extends GenLayer { + + private int biome; + + public MutableGenLayer(long seed) { + super(seed); + } + + public MutableGenLayer set(int biome) { + this.biome = biome; + return this; + } + + @Override + public int[] getInts(int areaX, int areaY, int areaWidth, int areaHeight) { + int[] biomes = IntCache.getIntCache(areaWidth * areaHeight); + Arrays.fill(biomes, biome); + return biomes; + } +} diff --git a/sponge111/src/main/java/com/boydti/fawe/sponge/v1_11/SpongeChunk_1_11.java b/sponge111/src/main/java/com/boydti/fawe/sponge/v1_11/SpongeChunk_1_11.java new file mode 100644 index 00000000..2537860c --- /dev/null +++ b/sponge111/src/main/java/com/boydti/fawe/sponge/v1_11/SpongeChunk_1_11.java @@ -0,0 +1,402 @@ +package com.boydti.fawe.sponge.v1_11; + +import com.boydti.fawe.Fawe; +import com.boydti.fawe.FaweCache; +import com.boydti.fawe.example.CharFaweChunk; +import com.boydti.fawe.object.FaweQueue; +import com.boydti.fawe.util.MainUtil; +import com.sk89q.jnbt.CompoundTag; +import com.sk89q.jnbt.DoubleTag; +import com.sk89q.jnbt.FloatTag; +import com.sk89q.jnbt.ListTag; +import com.sk89q.jnbt.StringTag; +import com.sk89q.jnbt.Tag; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import net.minecraft.block.Block; +import net.minecraft.block.state.IBlockState; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityList; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.BitArray; +import net.minecraft.util.ClassInheritanceMultiMap; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import net.minecraft.world.chunk.BlockStateContainer; +import net.minecraft.world.chunk.BlockStatePaletteRegistry; +import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.chunk.IBlockStatePalette; +import net.minecraft.world.chunk.storage.ExtendedBlockStorage; + +public class SpongeChunk_1_11 extends CharFaweChunk { + + public BlockStateContainer[] sectionPalettes; + + public static Map entityKeys; + + /** + * A FaweSections object represents a chunk and the blocks that you wish to change in it. + * + * @param parent + * @param x + * @param z + */ + public SpongeChunk_1_11(FaweQueue parent, int x, int z) { + super(parent, x, z); + } + + public SpongeChunk_1_11(FaweQueue parent, int x, int z, char[][] ids, short[] count, short[] air, byte[] heightMap) { + super(parent, x, z, ids, count, air, heightMap); + } + + @Override + public CharFaweChunk copy(boolean shallow) { + SpongeChunk_1_11 copy; + if (shallow) { + copy = new SpongeChunk_1_11(getParent(), getX(), getZ(), ids, count, air, heightMap); + copy.biomes = biomes; + copy.chunk = chunk; + } else { + copy = new SpongeChunk_1_11(getParent(), getX(), getZ(), (char[][]) MainUtil.copyNd(ids), count.clone(), air.clone(), heightMap.clone()); + copy.biomes = biomes; + copy.chunk = chunk; + copy.biomes = biomes.clone(); + copy.chunk = chunk; + } + if (sectionPalettes != null) { + copy.sectionPalettes = new BlockStateContainer[16]; + try { + Field fieldBits = BlockStateContainer.class.getDeclaredField("field_186021_b"); + fieldBits.setAccessible(true); + Field fieldPalette = BlockStateContainer.class.getDeclaredField("field_186022_c"); + fieldPalette.setAccessible(true); + Field fieldSize = BlockStateContainer.class.getDeclaredField("field_186024_e"); + fieldSize.setAccessible(true); + for (int i = 0; i < sectionPalettes.length; i++) { + BlockStateContainer current = sectionPalettes[i]; + if (current == null) { + continue; + } + // Clone palette + IBlockStatePalette currentPalette = (IBlockStatePalette) fieldPalette.get(current); + if (!(currentPalette instanceof BlockStatePaletteRegistry)) { + current.onResize(128, null); + } + BlockStateContainer paletteBlock = new BlockStateContainer(); + currentPalette = (IBlockStatePalette) fieldPalette.get(current); + if (!(currentPalette instanceof BlockStatePaletteRegistry)) { + throw new RuntimeException("Palette must be global!"); + } + fieldPalette.set(paletteBlock, currentPalette); + // Clone size + fieldSize.set(paletteBlock, fieldSize.get(current)); + // Clone palette + BitArray currentBits = (BitArray) fieldBits.get(current); + BitArray newBits = new BitArray(1, 0); + for (Field field : BitArray.class.getDeclaredFields()) { + field.setAccessible(true); + Object currentValue = field.get(currentBits); + if (currentValue instanceof long[]) { + currentValue = ((long[]) currentValue).clone(); + } + field.set(newBits, currentValue); + } + fieldBits.set(paletteBlock, newBits); + copy.sectionPalettes[i] = paletteBlock; + } + } catch (Throwable e) { + MainUtil.handleError(e); + } + } + return copy; + } + + @Override + public Chunk getNewChunk() { + World world = ((SpongeQueue_1_11) getParent()).getWorld(); + return world.getChunkProvider().provideChunk(getX(), getZ()); + } + + public void optimize() { + if (sectionPalettes != null) { + return; + } + char[][] arrays = getCombinedIdArrays(); + char lastChar = Character.MAX_VALUE; + for (int layer = 0; layer < 16; layer++) { + if (getCount(layer) > 0) { + if (sectionPalettes == null) { + sectionPalettes = new BlockStateContainer[16]; + } + BlockStateContainer palette = new BlockStateContainer(); + char[] blocks = getIdArray(layer); + for (int y = 0; y < 16; y++) { + for (int z = 0; z < 16; z++) { + for (int x = 0; x < 16; x++) { + char combinedId = blocks[FaweCache.CACHE_J[y][z][x]]; + if (combinedId > 1) { + palette.set(x, y, z, Block.getBlockById(combinedId >> 4).getStateFromMeta(combinedId & 0xF)); + } + } + } + } + } + } + } + + @Override + public SpongeChunk_1_11 call() { + net.minecraft.world.chunk.Chunk nmsChunk = this.getChunk(); + int bx = this.getX() << 4; + int bz = this.getZ() << 4; + nmsChunk.setModified(true); + net.minecraft.world.World nmsWorld = getParent().getWorld(); + try { + boolean flag = !nmsWorld.provider.getHasNoSky(); + // Sections + ExtendedBlockStorage[] sections = nmsChunk.getBlockStorageArray(); + Map tiles = nmsChunk.getTileEntityMap(); + ClassInheritanceMultiMap[] entities = nmsChunk.getEntityLists(); + + // Set heightmap + getParent().setHeightMap(this, heightMap); + + // Remove entities + for (int i = 0; i < 16; i++) { + int count = this.getCount(i); + if (count == 0) { + continue; + } else if (count >= 4096) { + Collection ents = entities[i]; + if (!ents.isEmpty()) { + synchronized (SpongeChunk_1_11.class) { + entities[i] = new ClassInheritanceMultiMap<>(Entity.class); + } + } + } else { + char[] array = this.getIdArray(i); + Collection ents = new ArrayList<>(entities[i]); + synchronized (SpongeChunk_1_11.class) { + for (Entity entity : ents) { + if (entity instanceof EntityPlayer) { + continue; + } + int x = ((int) Math.round(entity.posX) & 15); + int z = ((int) Math.round(entity.posZ) & 15); + int y = (int) Math.round(entity.posY); + if (array == null) { + continue; + } + if (y < 0 || y > 255 || array[FaweCache.CACHE_J[y][z][x]] != 0) { + nmsWorld.removeEntity(entity); + } + } + } + } + } + // Set entities + Set createdEntities = new HashSet<>(); + Set entitiesToSpawn = this.getEntities(); + if (!entitiesToSpawn.isEmpty()) { + synchronized (SpongeChunk_1_11.class) { + for (CompoundTag nativeTag : entitiesToSpawn) { + Map entityTagMap = nativeTag.getValue(); + StringTag idTag = (StringTag) entityTagMap.get("Id"); + ListTag posTag = (ListTag) entityTagMap.get("Pos"); + ListTag rotTag = (ListTag) entityTagMap.get("Rotation"); + if (idTag == null || posTag == null || rotTag == null) { + Fawe.debug("Unknown entity tag: " + nativeTag); + continue; + } + List value = posTag.getValue(); + double x = ((DoubleTag) value.get(0)).getValue(); + double y = ((DoubleTag) value.get(1)).getValue(); + double z = ((DoubleTag) value.get(2)).getValue(); + value = rotTag.getValue(); + float yaw = ((FloatTag) value.get(0)).getValue(); + float pitch = ((FloatTag) value.get(1)).getValue(); + String id = idTag.getValue(); + if (entityKeys == null) { + entityKeys = new HashMap<>(); + for (ResourceLocation key : EntityList.getEntityNameList()) { + String currentId = EntityList.func_191302_a(key); + entityKeys.put(currentId, key); + entityKeys.put(key.getResourcePath(), key); + } + } + ResourceLocation entityKey = entityKeys.get(id); + if (entityKey != null) { + Entity entity = EntityList.createEntityByIDFromName(entityKey, nmsWorld); + if (entity != null) { + NBTTagCompound tag = (NBTTagCompound) SpongeQueue_1_11.methodFromNative.invoke(SpongeQueue_1_11.adapter, nativeTag); + entity.readFromNBT(tag); + tag.removeTag("UUIDMost"); + tag.removeTag("UUIDLeast"); + entity.setPositionAndRotation(x, y, z, yaw, pitch); + nmsWorld.spawnEntityInWorld(entity); + } + } + } + } + } + // Run change task if applicable + if (getParent().getChangeTask() != null) { + CharFaweChunk previous = getParent().getPrevious(this, sections, tiles, entities, createdEntities, false); + getParent().getChangeTask().run(previous, this); + } + // Trim tiles + if (!tiles.isEmpty()) { + Set> entryset = tiles.entrySet(); + Iterator> iterator = entryset.iterator(); + while (iterator.hasNext()) { + Map.Entry tile = iterator.next(); + BlockPos pos = tile.getKey(); + int lx = pos.getX() & 15; + int ly = pos.getY(); + int lz = pos.getZ() & 15; + int j = FaweCache.CACHE_I[ly][lz][lx]; + char[] array = this.getIdArray(j); + if (array == null) { + continue; + } + int k = FaweCache.CACHE_J[ly][lz][lx]; + if (array[k] != 0) { + synchronized (SpongeChunk_1_11.class) { + tile.getValue().invalidate(); + iterator.remove(); + } + } + } + } + HashSet entsToRemove = this.getEntityRemoves(); + if (!entsToRemove.isEmpty()) { + synchronized (SpongeChunk_1_11.class) { + for (int i = 0; i < entities.length; i++) { + Collection ents = new ArrayList<>(entities[i]); + for (Entity entity : ents) { + if (entsToRemove.contains(entity.getUniqueID())) { + nmsWorld.removeEntity(entity); + } + } + } + } + } + // Efficiently merge sections + for (int j = 0; j < sections.length; j++) { + int count = this.getCount(j); + if (count == 0) { + continue; + } + final char[] array = this.getIdArray(j); + if (array == null) { + continue; + } + int countAir = this.getAir(j); + ExtendedBlockStorage section = sections[j]; + if (section == null) { + if (count == countAir) { + continue; + } + if (this.sectionPalettes != null && this.sectionPalettes[j] != null) { + section = sections[j] = new ExtendedBlockStorage(j << 4, flag); + getParent().setPalette(section, this.sectionPalettes[j]); + getParent().setCount(0, count - this.getAir(j), section); + continue; + } else { + sections[j] = section = new ExtendedBlockStorage(j << 4, flag); + } + } else if (count >= 4096) { + if (count == countAir) { + sections[j] = null; + continue; + } + if (this.sectionPalettes != null && this.sectionPalettes[j] != null) { + getParent().setPalette(section, this.sectionPalettes[j]); + getParent().setCount(0, count - this.getAir(j), section); + continue; + } else { + sections[j] = section = new ExtendedBlockStorage(j << 4, flag); + } + } + IBlockState existing; + int by = j << 4; + BlockStateContainer nibble = section.getData(); + int nonEmptyBlockCount = 0; + for (int y = 0; y < 16; y++) { + for (int z = 0; z < 16; z++) { + for (int x = 0; x < 16; x++) { + char combinedId = array[FaweCache.CACHE_J[y][z][x]]; + switch (combinedId) { + case 0: + continue; + case 1: + existing = nibble.get(x, y, z); + if (existing != SpongeQueue_1_11.air) { + if (existing.getLightValue() > 0) { + getParent().getRelighter().addLightUpdate(bx + x, by + y, bz + z); + } + nonEmptyBlockCount--; + } + nibble.set(x, y, z, SpongeQueue_1_11.air); + continue; + default: + existing = nibble.get(x, y, z); + if (existing != SpongeQueue_1_11.air) { + if (existing.getLightValue() > 0) { + getParent().getRelighter().addLightUpdate(bx + x, by + y, bz + z); + } + } else { + nonEmptyBlockCount++; + } + nibble.set(x, y, z, Block.getBlockById(combinedId >> 4).getStateFromMeta(combinedId & 0xF)); + } + } + } + } + getParent().setCount(0, getParent().getNonEmptyBlockCount(section) + nonEmptyBlockCount, section); + } + // Set biomes + if (this.biomes != null) { + byte[] currentBiomes = nmsChunk.getBiomeArray(); + for (int i = 0 ; i < this.biomes.length; i++) { + if (this.biomes[i] != 0) { + currentBiomes[i] = this.biomes[i]; + } + } + } + // Set tiles + Map tilesToSpawn = this.getTiles(); + + for (Map.Entry entry : tilesToSpawn.entrySet()) { + CompoundTag nativeTag = entry.getValue(); + short blockHash = entry.getKey(); + int x = (blockHash >> 12 & 0xF) + bx; + int y = (blockHash & 0xFF); + int z = (blockHash >> 8 & 0xF) + bz; + BlockPos pos = new BlockPos(x, y, z); // Set pos + TileEntity tileEntity = nmsWorld.getTileEntity(pos); + if (tileEntity != null) { + NBTTagCompound tag = (NBTTagCompound) SpongeQueue_1_11.methodFromNative.invoke(SpongeQueue_1_11.adapter, nativeTag); + tag.setInteger("x", pos.getX()); + tag.setInteger("y", pos.getY()); + tag.setInteger("z", pos.getZ()); + tileEntity.readFromNBT(tag); // ReadTagIntoTile + } + } + } catch (Throwable e) { + MainUtil.handleError(e); + } + return this; + } +} diff --git a/sponge111/src/main/java/com/boydti/fawe/sponge/v1_11/SpongeQueue_1_11.java b/sponge111/src/main/java/com/boydti/fawe/sponge/v1_11/SpongeQueue_1_11.java new file mode 100644 index 00000000..a210f461 --- /dev/null +++ b/sponge111/src/main/java/com/boydti/fawe/sponge/v1_11/SpongeQueue_1_11.java @@ -0,0 +1,671 @@ +package com.boydti.fawe.sponge.v1_11; + +import com.boydti.fawe.FaweCache; +import com.boydti.fawe.example.CharFaweChunk; +import com.boydti.fawe.example.NMSMappedFaweQueue; +import com.boydti.fawe.object.FaweChunk; +import com.boydti.fawe.object.FawePlayer; +import com.boydti.fawe.object.brush.visualization.VisualChunk; +import java.util.concurrent.atomic.LongAdder; +import com.boydti.fawe.object.visitor.FaweChunkVisitor; +import com.boydti.fawe.sponge.SpongePlayer; +import com.boydti.fawe.util.MainUtil; +import com.boydti.fawe.util.MathMan; +import com.boydti.fawe.util.ReflectionUtils; +import com.sk89q.jnbt.CompoundTag; +import com.sk89q.jnbt.StringTag; +import com.sk89q.jnbt.Tag; +import com.sk89q.worldedit.sponge.SpongeWorldEdit; +import com.sk89q.worldedit.sponge.adapter.SpongeImplAdapter; +import com.sk89q.worldedit.world.biome.BaseBiome; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayDeque; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import net.minecraft.block.Block; +import net.minecraft.block.BlockFalling; +import net.minecraft.block.state.IBlockState; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityList; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.init.Blocks; +import net.minecraft.nbt.NBTBase; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.network.PacketBuffer; +import net.minecraft.network.play.server.SPacketChunkData; +import net.minecraft.network.play.server.SPacketMultiBlockChange; +import net.minecraft.server.management.PlayerChunkMap; +import net.minecraft.server.management.PlayerChunkMapEntry; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.ClassInheritanceMultiMap; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.world.EnumSkyBlock; +import net.minecraft.world.World; +import net.minecraft.world.WorldServer; +import net.minecraft.world.biome.BiomeCache; +import net.minecraft.world.biome.BiomeProvider; +import net.minecraft.world.chunk.BlockStateContainer; +import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.chunk.IChunkGenerator; +import net.minecraft.world.chunk.IChunkProvider; +import net.minecraft.world.chunk.NibbleArray; +import net.minecraft.world.chunk.storage.ExtendedBlockStorage; +import net.minecraft.world.gen.ChunkProviderOverworld; +import net.minecraft.world.gen.ChunkProviderServer; +import net.minecraft.world.storage.WorldInfo; +import org.spongepowered.api.Sponge; + +public class SpongeQueue_1_11 extends NMSMappedFaweQueue { + + protected final static Method methodFromNative; + protected final static Method methodToNative; + protected final static Field fieldTickingBlockCount; + protected final static Field fieldNonEmptyBlockCount; + + protected final static Field fieldId2ChunkMap; + protected final static Field fieldChunkGenerator; + + protected static Field fieldBiomes; + protected static Field fieldSeed; + protected static Field fieldBiomeCache; + protected static Field fieldBiomes2; + protected static Field fieldGenLayer1; + protected static Field fieldGenLayer2; + protected static ExtendedBlockStorage emptySection; + + private static MutableGenLayer genLayer; + + protected static final SpongeImplAdapter adapter; + + static { + try { + emptySection = new ExtendedBlockStorage(0, true); + adapter = SpongeWorldEdit.inst().getAdapter(); + methodFromNative = adapter.getClass().getDeclaredMethod("toNative", Tag.class); + methodToNative = adapter.getClass().getDeclaredMethod("fromNative", NBTBase.class); + methodFromNative.setAccessible(true); + methodToNative.setAccessible(true); + + fieldId2ChunkMap = ChunkProviderServer.class.getDeclaredField("field_73244_f"); + fieldChunkGenerator = ChunkProviderServer.class.getDeclaredField("field_186029_c"); + fieldId2ChunkMap.setAccessible(true); + fieldChunkGenerator.setAccessible(true); + + fieldBiomes = ChunkProviderOverworld.class.getDeclaredField("field_185981_C"); // biomesForGeneration + fieldBiomes.setAccessible(true); + fieldSeed = WorldInfo.class.getDeclaredField("field_76100_a"); // randomSeed + fieldSeed.setAccessible(true); + fieldBiomeCache = BiomeProvider.class.getDeclaredField("field_76942_f"); // biomeCache + fieldBiomeCache.setAccessible(true); + fieldBiomes2 = BiomeProvider.class.getDeclaredField("field_76943_g"); // biomesToSpawnIn + fieldBiomes2.setAccessible(true); + fieldGenLayer1 = BiomeProvider.class.getDeclaredField("field_76944_d"); // genBiomes + fieldGenLayer2 = BiomeProvider.class.getDeclaredField("field_76945_e"); // biomeIndexLayer + fieldGenLayer1.setAccessible(true); + fieldGenLayer2.setAccessible(true); + + fieldTickingBlockCount = ExtendedBlockStorage.class.getDeclaredField("field_76683_c"); + fieldNonEmptyBlockCount = ExtendedBlockStorage.class.getDeclaredField("field_76682_b"); + fieldTickingBlockCount.setAccessible(true); + fieldNonEmptyBlockCount.setAccessible(true); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + + public SpongeQueue_1_11(com.sk89q.worldedit.world.World world) { + super(world); + getImpWorld(); + } + + public SpongeQueue_1_11(String world) { + super(world); + getImpWorld(); + } + + @Override + public void sendBlockUpdate(FaweChunk chunk, FawePlayer... players) { + try { + PlayerChunkMap playerManager = ((WorldServer) getWorld()).getPlayerChunkMap(); + boolean watching = false; + for (int i = 0; i < players.length; i++) { + EntityPlayerMP player = (EntityPlayerMP) ((SpongePlayer) players[i]).parent; + if (!playerManager.isPlayerWatchingChunk(player, chunk.getX(), chunk.getZ())) { + players[i] = null; + } else { + watching = true; + } + } + if (!watching) return; + final LongAdder size = new LongAdder(); + if (chunk instanceof VisualChunk) { + size.add(((VisualChunk) chunk).size()); + } else if (chunk instanceof CharFaweChunk) { + size.add(((CharFaweChunk) chunk).getTotalCount()); + } else { + chunk.forEachQueuedBlock(new FaweChunkVisitor() { + @Override + public void run(int localX, int y, int localZ, int combined) { + size.add(1); + } + }); + } + if (size.intValue() == 0) return; + SPacketMultiBlockChange packet = new SPacketMultiBlockChange(); + ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(); + final PacketBuffer buffer = new PacketBuffer(byteBuf); + buffer.writeInt(chunk.getX()); + buffer.writeInt(chunk.getZ()); + buffer.writeVarIntToBuffer(size.intValue()); + chunk.forEachQueuedBlock(new FaweChunkVisitor() { + @Override + public void run(int localX, int y, int localZ, int combined) { + short index = (short) (localX << 12 | localZ << 8 | y); + buffer.writeShort(index); + buffer.writeVarIntToBuffer(combined); + } + }); + packet.readPacketData(buffer); + for (FawePlayer player : players) { + if (player != null) ((EntityPlayerMP) ((SpongePlayer) player).parent).connection.sendPacket(packet); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public void saveChunk(Chunk chunk) { + chunk.setChunkModified(); + } + + @Override + public ExtendedBlockStorage[] getSections(Chunk chunk) { + return chunk.getBlockStorageArray(); + } + + @Override + public int getBiome(Chunk chunk, int x, int z) { + return chunk.getBiomeArray()[((z & 15) << 4) + (x & 15)]; + } + + @Override + public Chunk loadChunk(World world, int x, int z, boolean generate) { + ChunkProviderServer provider = (ChunkProviderServer) world.getChunkProvider(); + if (generate) { + return provider.provideChunk(x, z); + } else { + return provider.loadChunk(x, z); + } + } + + @Override + public ExtendedBlockStorage[] getCachedSections(World world, int cx, int cz) { + Chunk chunk = world.getChunkProvider().getLoadedChunk(cx, cz); + if (chunk != null) { + return chunk.getBlockStorageArray(); + } + return null; + } + + @Override + public Chunk getCachedChunk(World world, int cx, int cz) { + return world.getChunkProvider().getLoadedChunk(cx, cz); + } + + @Override + public ExtendedBlockStorage getCachedSection(ExtendedBlockStorage[] ExtendedBlockStorages, int cy) { + return ExtendedBlockStorages[cy]; + } + + @Override + public void setHeightMap(FaweChunk chunk, byte[] heightMap) { + Chunk forgeChunk = (Chunk) chunk.getChunk(); + if (forgeChunk != null) { + int[] otherMap = forgeChunk.getHeightMap(); + for (int i = 0; i < heightMap.length; i++) { + int newHeight = heightMap[i] & 0xFF; + int currentHeight = otherMap[i]; + if (newHeight > currentHeight) { + otherMap[i] = newHeight; + } + } + } + } + + protected BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(0, 0, 0); + + @Override + public CompoundTag getTileEntity(Chunk chunk, int x, int y, int z) { + Map tiles = chunk.getTileEntityMap(); + pos.setPos(x, y, z); + TileEntity tile = tiles.get(pos); + return tile != null ? getTag(tile) : null; + } + + public CompoundTag getTag(TileEntity tile) { + try { + NBTTagCompound tag = new NBTTagCompound(); + tile.writeToNBT(tag); // readTagIntoEntity + CompoundTag result = (CompoundTag) methodToNative.invoke(SpongeQueue_1_11.adapter, tag); + return result; + } catch (Exception e) { + MainUtil.handleError(e); + return null; + } + } + + public boolean regenerateChunk(net.minecraft.world.World world, int x, int z) { + IChunkProvider provider = world.getChunkProvider(); + if (!(provider instanceof ChunkProviderServer)) { + return false; + } + BlockFalling.fallInstantly = true; + try { + ChunkProviderServer chunkServer = (ChunkProviderServer) provider; + IChunkGenerator gen = (IChunkGenerator) fieldChunkGenerator.get(chunkServer); + long pos = ChunkPos.asLong(x, z); + Chunk mcChunk; + if (chunkServer.chunkExists(x, z)) { + mcChunk = chunkServer.loadChunk(x, z); + mcChunk.onChunkUnload(); + } + PlayerChunkMap playerManager = ((WorldServer) getWorld()).getPlayerChunkMap(); + List oldWatchers = null; + if (chunkServer.chunkExists(x, z)) { + mcChunk = chunkServer.loadChunk(x, z); + PlayerChunkMapEntry entry = playerManager.getEntry(x, z); + if (entry != null) { + Field fieldPlayers = PlayerChunkMapEntry.class.getDeclaredField("field_187283_c"); + fieldPlayers.setAccessible(true); + oldWatchers = (List) fieldPlayers.get(entry); + playerManager.removeEntry(entry); + } + mcChunk.onChunkUnload(); + } + try { + Field droppedChunksSetField = chunkServer.getClass().getDeclaredField("field_73248_b"); + droppedChunksSetField.setAccessible(true); + Set droppedChunksSet = (Set) droppedChunksSetField.get(chunkServer); + droppedChunksSet.remove(pos); + } catch (Throwable e) { + MainUtil.handleError(e); + } + Long2ObjectMap id2ChunkMap = (Long2ObjectMap) fieldId2ChunkMap.get(chunkServer); + id2ChunkMap.remove(pos); + mcChunk = gen.provideChunk(x, z); + id2ChunkMap.put(pos, mcChunk); + if (mcChunk != null) { + mcChunk.onChunkLoad(); + mcChunk.populateChunk(chunkServer, gen); + } + if (oldWatchers != null) { + for (EntityPlayerMP player : oldWatchers) { + playerManager.addPlayer(player); + } + } + return true; + } catch (Throwable t) { + MainUtil.handleError(t); + return false; + } finally { + BlockFalling.fallInstantly = false; + } + } + + @Override + public boolean regenerateChunk(net.minecraft.world.World world, int x, int z, BaseBiome biome, Long seed) { + if (biome != null) { + try { + if (seed == null) { + seed = world.getSeed(); + } + nmsWorld.getWorldInfo().getSeed(); + boolean result; + ChunkProviderOverworld generator = new ChunkProviderOverworld(nmsWorld, seed, false, ""); + net.minecraft.world.biome.Biome base = net.minecraft.world.biome.Biome.getBiome(biome.getId()); + net.minecraft.world.biome.Biome[] existingBiomes = new net.minecraft.world.biome.Biome[256]; + Arrays.fill(existingBiomes, base); + fieldBiomes.set(generator, existingBiomes); + boolean cold = base.getTemperature() <= 1; + IChunkGenerator existingGenerator = (IChunkGenerator) fieldChunkGenerator.get(nmsWorld.getChunkProvider()); + long existingSeed = world.getSeed(); + { + if (genLayer == null) genLayer = new MutableGenLayer(seed); + genLayer.set(biome.getId()); + Object existingGenLayer1 = fieldGenLayer1.get(nmsWorld.provider.getBiomeProvider()); + Object existingGenLayer2 = fieldGenLayer2.get(nmsWorld.provider.getBiomeProvider()); + fieldGenLayer1.set(nmsWorld.provider.getBiomeProvider(), genLayer); + fieldGenLayer2.set(nmsWorld.provider.getBiomeProvider(), genLayer); + + fieldSeed.set(nmsWorld.getWorldInfo(), seed); + + ReflectionUtils.setFailsafeFieldValue(fieldBiomeCache, this.nmsWorld.provider.getBiomeProvider(), new BiomeCache(this.nmsWorld.provider.getBiomeProvider())); + + ReflectionUtils.setFailsafeFieldValue(fieldChunkGenerator, this.nmsWorld.getChunkProvider(), generator); + + result = regenerateChunk(world, x, z); + + ReflectionUtils.setFailsafeFieldValue(fieldChunkGenerator, this.nmsWorld.getChunkProvider(), existingGenerator); + + fieldSeed.set(nmsWorld.getWorldInfo(), existingSeed); + + fieldGenLayer1.set(nmsWorld.provider.getBiomeProvider(), existingGenLayer1); + fieldGenLayer2.set(nmsWorld.provider.getBiomeProvider(), existingGenLayer2); + } + return result; + } catch (Throwable e) { + e.printStackTrace(); + } + } + return regenerateChunk(world, x, z); + } + + @Override + public int getCombinedId4Data(ExtendedBlockStorage section, int x, int y, int z) { + IBlockState ibd = section.getData().get(x & 15, y & 15, z & 15); + Block block = ibd.getBlock(); + int id = Block.getIdFromBlock(block); + if (FaweCache.hasData(id)) { + return (id << 4) + block.getMetaFromState(ibd); + } else { + return id << 4; + } + } + + public int getNonEmptyBlockCount(ExtendedBlockStorage section) throws IllegalAccessException { + return (int) fieldNonEmptyBlockCount.get(section); + } + + public void setCount(int tickingBlockCount, int nonEmptyBlockCount, ExtendedBlockStorage section) throws NoSuchFieldException, IllegalAccessException { + fieldTickingBlockCount.set(section, tickingBlockCount); + fieldNonEmptyBlockCount.set(section, nonEmptyBlockCount); + } + + @Override + public CharFaweChunk getPrevious(CharFaweChunk fs, ExtendedBlockStorage[] sections, Map tilesGeneric, Collection[] entitiesGeneric, Set createdEntities, boolean all) throws Exception { + Map tiles = (Map) tilesGeneric; + ClassInheritanceMultiMap[] entities = (ClassInheritanceMultiMap[]) entitiesGeneric; + CharFaweChunk previous = (CharFaweChunk) getFaweChunk(fs.getX(), fs.getZ()); + char[][] idPrevious = previous.getCombinedIdArrays(); + for (int layer = 0; layer < sections.length; layer++) { + if (fs.getCount(layer) != 0 || all) { + ExtendedBlockStorage section = sections[layer]; + if (section != null) { + short solid = 0; + char[] previousLayer = idPrevious[layer] = new char[4096]; + BlockStateContainer blocks = section.getData(); + for (int j = 0; j < 4096; j++) { + int x = FaweCache.CACHE_X[0][j]; + int y = FaweCache.CACHE_Y[0][j]; + int z = FaweCache.CACHE_Z[0][j]; + IBlockState ibd = blocks.get(x, y, z); + Block block = ibd.getBlock(); + int combined = Block.getIdFromBlock(block); + if (FaweCache.hasData(combined)) { + combined = (combined << 4) + block.getMetaFromState(ibd); + } else { + combined = combined << 4; + } + if (combined > 1) { + solid++; + } + previousLayer[j] = (char) combined; + } + previous.count[layer] = solid; + previous.air[layer] = (short) (4096 - solid); + } + } + } + if (tiles != null) { + for (Map.Entry entry : tiles.entrySet()) { + TileEntity tile = entry.getValue(); + NBTTagCompound tag = new NBTTagCompound(); + tile.writeToNBT(tag); // readTileEntityIntoTag + BlockPos pos = entry.getKey(); + CompoundTag nativeTag = (CompoundTag) methodToNative.invoke(SpongeQueue_1_11.adapter, tag); + previous.setTile(pos.getX(), pos.getY(), pos.getZ(), nativeTag); + } + } + if (entities != null) { + for (Collection entityList : entities) { + for (Entity ent : entityList) { + if (ent instanceof EntityPlayer || (!createdEntities.isEmpty() && createdEntities.contains(ent.getUniqueID()))) { + continue; + } + int x = ((int) Math.round(ent.posX) & 15); + int z = ((int) Math.round(ent.posZ) & 15); + int y = (int) Math.round(ent.posY); + int i = FaweCache.CACHE_I[y][z][x]; + char[] array = fs.getIdArray(i); + if (array == null) { + continue; + } + int j = FaweCache.CACHE_J[y][z][x]; + if (array[j] != 0) { + String id = EntityList.getEntityString(ent); + if (id != null) { + NBTTagCompound tag = new NBTTagCompound(); + ent.writeToNBT(tag); // readEntityIntoTag + CompoundTag nativeTag = (CompoundTag) methodToNative.invoke(SpongeQueue_1_11.adapter, tag); + Map map = ReflectionUtils.getMap(nativeTag.getValue()); + map.put("Id", new StringTag(id)); + previous.setEntity(nativeTag); + } + } + } + } + } + return previous; + } + + protected final static IBlockState air = Blocks.AIR.getDefaultState(); + + public void setPalette(ExtendedBlockStorage section, BlockStateContainer palette) throws NoSuchFieldException, IllegalAccessException { + Field fieldSection = ExtendedBlockStorage.class.getDeclaredField("data"); + fieldSection.setAccessible(true); + fieldSection.set(section, palette); + } + + @Override + public void sendChunk(int x, int z, int bitMask) { + Chunk chunk = getCachedChunk(getWorld(), x, z); + if (chunk != null) { + sendChunk(chunk, bitMask); + } + } + + @Override + public void refreshChunk(FaweChunk fc) { + Chunk chunk = getCachedChunk(getWorld(), fc.getX(), fc.getZ()); + if (chunk != null) { + sendChunk(chunk, fc.getBitMask()); + } + } + + public void sendChunk(Chunk nmsChunk, int mask) { + if (!nmsChunk.isLoaded()) { + return; + } + try { + ChunkPos pos = nmsChunk.getChunkCoordIntPair(); + WorldServer w = (WorldServer) nmsChunk.getWorld(); + PlayerChunkMap chunkMap = w.getPlayerChunkMap(); + int x = pos.chunkXPos; + int z = pos.chunkZPos; + PlayerChunkMapEntry chunkMapEntry = chunkMap.getEntry(x, z); + if (chunkMapEntry == null) { + return; + } + final ArrayDeque players = new ArrayDeque<>(); + chunkMapEntry.hasPlayerMatching(input -> { + players.add(input); + return false; + }); + boolean empty = false; + ExtendedBlockStorage[] sections = nmsChunk.getBlockStorageArray(); + for (int i = 0; i < sections.length; i++) { + if (sections[i] == null) { + sections[i] = emptySection; + empty = true; + } + } + if (mask == 0 || mask == 65535 && hasEntities(nmsChunk)) { + SPacketChunkData packet = new SPacketChunkData(nmsChunk, 65280); + for (EntityPlayerMP player : players) { + player.connection.sendPacket(packet); + } + mask = 255; + } + SPacketChunkData packet = new SPacketChunkData(nmsChunk, mask); + for (EntityPlayerMP player : players) { + player.connection.sendPacket(packet); + } + if (empty) { + for (int i = 0; i < sections.length; i++) { + if (sections[i] == emptySection) { + sections[i] = null; + } + } + } + } catch (Throwable e) { + MainUtil.handleError(e); + } + } + + public boolean hasEntities(Chunk nmsChunk) { + ClassInheritanceMultiMap[] entities = nmsChunk.getEntityLists(); + for (int i = 0; i < entities.length; i++) { + ClassInheritanceMultiMap slice = entities[i]; + if (slice != null && !slice.isEmpty()) { + return true; + } + } + return false; + } + + + @Override + public FaweChunk getFaweChunk(int x, int z) { + return new SpongeChunk_1_11(this, x, z); + } + + @Override + public boolean removeLighting(ExtendedBlockStorage[] sections, RelightMode mode, boolean sky) { + if (mode == RelightMode.ALL) { + for (int i = 0; i < sections.length; i++) { + ExtendedBlockStorage section = sections[i]; + if (section != null) { + section.setBlocklightArray(new NibbleArray()); + if (sky) { + section.setSkylightArray(new NibbleArray()); + } + } + } + } + return true; + } + + @Override + public boolean hasSky() { + return !nmsWorld.provider.getHasNoSky(); + } + + @Override + public void setFullbright(ExtendedBlockStorage[] sections) { + for (int i = 0; i < sections.length; i++) { + ExtendedBlockStorage section = sections[i]; + if (section != null) { + byte[] bytes = section.getSkylightArray().getData(); + Arrays.fill(bytes, (byte) 255); + } + } + } + + @Override + public void relight(int x, int y, int z) { + pos.setPos(x, y, z); + nmsWorld.checkLight(pos); + } + + protected WorldServer nmsWorld; + + @Override + public net.minecraft.world.World getImpWorld() { + if (nmsWorld != null || getWorldName() == null) { + return nmsWorld; + } + nmsWorld = (WorldServer) Sponge.getServer().getWorld(getWorldName()).get(); + return nmsWorld; + } + + @Override + public void setSkyLight(ExtendedBlockStorage section, int x, int y, int z, int value) { + section.getSkylightArray().set(x & 15, y & 15, z & 15, value); + } + + @Override + public void setBlockLight(ExtendedBlockStorage section, int x, int y, int z, int value) { + section.getBlocklightArray().set(x & 15, y & 15, z & 15, value); + } + + @Override + public int getSkyLight(ExtendedBlockStorage section, int x, int y, int z) { + return section.getExtSkylightValue(x & 15, y & 15, z & 15); + } + + @Override + public int getEmmittedLight(ExtendedBlockStorage section, int x, int y, int z) { + return section.getExtBlocklightValue(x & 15, y & 15, z & 15); + } + + @Override + public int getOpacity(ExtendedBlockStorage section, int x, int y, int z) { + BlockStateContainer dataPalette = section.getData(); + IBlockState ibd = dataPalette.get(x & 15, y & 15, z & 15); + return ibd.getLightOpacity(); + } + + @Override + public int getBrightness(ExtendedBlockStorage section, int x, int y, int z) { + BlockStateContainer dataPalette = section.getData(); + IBlockState ibd = dataPalette.get(x & 15, y & 15, z & 15); + return ibd.getLightValue(); + } + + @Override + public int getOpacityBrightnessPair(ExtendedBlockStorage section, int x, int y, int z) { + BlockStateContainer dataPalette = section.getData(); + IBlockState ibd = dataPalette.get(x & 15, y & 15, z & 15); + return MathMan.pair16(ibd.getLightOpacity(), ibd.getLightValue()); + } + + @Override + public void relightBlock(int x, int y, int z) { + pos.setPos(x, y, z); + nmsWorld.checkLightFor(EnumSkyBlock.BLOCK, pos); + } + + @Override + public void relightSky(int x, int y, int z) { + pos.setPos(x, y, z); + nmsWorld.checkLightFor(EnumSkyBlock.SKY, pos); + } + + @Override + public File getSaveFolder() { + return new File(((WorldServer) getWorld()).getSaveHandler().getWorldDirectory(), "region"); + } +} diff --git a/sponge111/src/main/java/com/boydti/fawe/sponge/v1_11/SpongeQueue_ALL.java.unused b/sponge111/src/main/java/com/boydti/fawe/sponge/v1_11/SpongeQueue_ALL.java.unused new file mode 100644 index 00000000..5efd2b19 --- /dev/null +++ b/sponge111/src/main/java/com/boydti/fawe/sponge/v1_11/SpongeQueue_ALL.java.unused @@ -0,0 +1,329 @@ +package com.boydti.fawe.sponge.v1_11; + +import com.boydti.fawe.FaweCache; +import com.boydti.fawe.config.Settings; +import com.boydti.fawe.example.CharFaweChunk; +import com.boydti.fawe.example.NMSMappedFaweQueue; +import com.boydti.fawe.object.FaweChunk; +import com.boydti.fawe.object.RunnableVal; +import com.boydti.fawe.util.MainUtil; +import com.boydti.fawe.util.TaskManager; +import com.sk89q.jnbt.CompoundTag; +import com.sk89q.worldedit.world.biome.BaseBiome; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import net.minecraft.block.Block; +import net.minecraft.block.state.IBlockState; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityTracker; +import net.minecraft.entity.EntityTrackerEntry; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.nbt.NBTBase; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.network.play.server.S13PacketDestroyEntities; +import net.minecraft.network.play.server.S21PacketChunkData; +import net.minecraft.server.management.PlayerManager; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.BlockPos; +import net.minecraft.util.ClassInheritanceMultiMap; +import net.minecraft.util.IntHashMap; +import net.minecraft.util.LongHashMap; +import net.minecraft.world.ChunkCoordIntPair; +import net.minecraft.world.WorldServer; +import net.minecraft.world.chunk.IChunkProvider; +import net.minecraft.world.chunk.storage.ExtendedBlockStorage; +import net.minecraft.world.gen.ChunkProviderServer; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.block.BlockState; +import org.spongepowered.api.block.BlockTypes; +import org.spongepowered.api.world.Chunk; +import org.spongepowered.api.world.World; +import org.spongepowered.api.world.extent.UnmodifiableBlockVolume; +import org.spongepowered.api.world.extent.worker.MutableBlockVolumeWorker; +import org.spongepowered.api.world.extent.worker.procedure.BlockVolumeMapper; + +public class SpongeQueue_ALL extends NMSMappedFaweQueue { + private Method methodToNative; + + public SpongeQueue_ALL(String world) { + super(world); + try { + Class converter = Class.forName("com.sk89q.worldedit.forge.NBTConverter"); + this.methodToNative = converter.getDeclaredMethod("fromNative", NBTBase.class); + methodToNative.setAccessible(true); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + + @Override + public void refreshChunk(FaweChunk fs) { + + } + + @Override + public void refreshChunk(World world, net.minecraft.world.chunk.Chunk nmsChunk) { + if (!nmsChunk.isLoaded()) { + return; + } + try { + ChunkCoordIntPair pos = nmsChunk.getChunkCoordIntPair(); + WorldServer w = (WorldServer) nmsChunk.getWorld(); + PlayerManager chunkMap = w.getPlayerManager(); + int x = pos.chunkXPos; + int z = pos.chunkZPos; + if (!chunkMap.hasPlayerInstance(x, z)) { + return; + } + EntityTracker tracker = w.getEntityTracker(); + HashSet players = new HashSet<>(); + for (EntityPlayer player : w.playerEntities) { + if (player instanceof EntityPlayerMP) { + if (chunkMap.isPlayerWatchingChunk((EntityPlayerMP) player, x, z)) { + players.add((EntityPlayerMP) player); + } + } + } + if (players.size() == 0) { + return; + } + HashSet entities = new HashSet<>(); + ClassInheritanceMultiMap[] entitieSlices = nmsChunk.getEntityLists(); + IntHashMap entries = null; + for (Field field : tracker.getClass().getDeclaredFields()) { + if (field.getType() == IntHashMap.class) { + field.setAccessible(true); + entries = (IntHashMap) field.get(tracker); + } + } + for (ClassInheritanceMultiMap slice : entitieSlices) { + if (slice == null) { + continue; + } + for (Entity ent : slice) { + EntityTrackerEntry entry = entries != null ? entries.lookup(ent.getEntityId()) : null; + if (entry == null) { + continue; + } + entities.add(entry); + S13PacketDestroyEntities packet = new S13PacketDestroyEntities(ent.getEntityId()); + for (EntityPlayerMP player : players) { + player.playerNetServerHandler.sendPacket(packet); + } + } + } + // Send chunks + S21PacketChunkData packet = new S21PacketChunkData(nmsChunk, false, 65535); + for (EntityPlayerMP player : players) { + player.playerNetServerHandler.sendPacket(packet); + } + // send ents + for (EntityTrackerEntry entry : entities) { + try { + TaskManager.IMP.later(new Runnable() { + @Override + public void run() { + for (EntityPlayerMP player : players) { + boolean result = entry.trackingPlayers.remove(player); + if (result && entry.trackedEntity != player) { + entry.updatePlayerEntity(player); + } + } + } + }, 2); + } catch (Throwable e) { + MainUtil.handleError(e); + } + } + } catch (Throwable e) { + MainUtil.handleError(e); + } + } + + @Override + public CompoundTag getTileEntity(net.minecraft.world.chunk.Chunk chunk, int x, int y, int z) { + Map tiles = chunk.getTileEntityMap(); + TileEntity tile = tiles.get(new BlockPos(x, y, z)); + return tile != null ? getTag(tile) : null; + } + + public CompoundTag getTag(TileEntity tile) { + try { + NBTTagCompound tag = new NBTTagCompound(); + tile.readFromNBT(tag); // readTagIntoEntity + return (CompoundTag) methodToNative.invoke(null, tag); + } catch (Exception e) { + MainUtil.handleError(e); + return null; + } + } + + @Override + public net.minecraft.world.chunk.Chunk getChunk(World world, int x, int z) { + net.minecraft.world.chunk.Chunk chunk = ((net.minecraft.world.World) world).getChunkProvider().provideChunk(x, z); + if (chunk != null && !chunk.isLoaded()) { + chunk.onChunkLoad(); + } + return chunk; + } + + @Override + public char[] getCachedSection(ExtendedBlockStorage[] chunk, int cy) { + ExtendedBlockStorage value = chunk[cy]; + return value == null ? null : value.getData(); + } + + @Override + public World getWorld(String world) { + return Sponge.getServer().getWorld(super.getWorldName()).get(); + } + + @Override + public boolean isChunkLoaded(World world, int x, int z) { + net.minecraft.world.World nmsWorld = (net.minecraft.world.World) world; + IChunkProvider provider = nmsWorld.getChunkProvider(); + return provider.chunkExists(x, z); + } + + @Override + public boolean regenerateChunk(World world, int x, int z, BaseBiome biome, Long seed) { + try { + net.minecraft.world.World nmsWorld = (net.minecraft.world.World) world; + IChunkProvider provider = nmsWorld.getChunkProvider(); + if (!(provider instanceof ChunkProviderServer)) { + return false; + } + ChunkProviderServer chunkServer = (ChunkProviderServer) provider; + Field chunkProviderField = chunkServer.getClass().getDeclaredField("field_73246_d"); + chunkProviderField.setAccessible(true); + IChunkProvider chunkProvider = (IChunkProvider) chunkProviderField.get(chunkServer); + long pos = ChunkCoordIntPair.chunkXZ2Int(x, z); + net.minecraft.world.chunk.Chunk mcChunk; + if (chunkServer.chunkExists(x, z)) { + mcChunk = chunkServer.loadChunk(x, z); + mcChunk.onChunkUnload(); + } + Field droppedChunksSetField = chunkServer.getClass().getDeclaredField("field_73248_b"); + droppedChunksSetField.setAccessible(true); + Set droppedChunksSet = (Set) droppedChunksSetField.get(chunkServer); + droppedChunksSet.remove(pos); + Field id2ChunkMapField = chunkServer.getClass().getDeclaredField("field_73244_f"); + id2ChunkMapField.setAccessible(true); + LongHashMap id2ChunkMap = (LongHashMap) id2ChunkMapField.get(chunkServer); + id2ChunkMap.remove(pos); + mcChunk = chunkProvider.provideChunk(x, z); + id2ChunkMap.add(pos, mcChunk); + List loadedChunks = chunkServer.func_152380_a(); + loadedChunks.add(mcChunk); + if (mcChunk != null) { + mcChunk.onChunkLoad(); + mcChunk.populateChunk(chunkProvider, chunkProvider, x, z); + } + return true; + } catch (Throwable e) { + MainUtil.handleError(e); + } + return false; + } + + private BlockState AIR = BlockTypes.AIR.getDefaultState(); + + @Override + public boolean setComponents(FaweChunk fc, RunnableVal changeTask) { + if (changeTask != null) { + Settings.IMP.HISTORY.COMBINE_STAGES = false; + throw new UnsupportedOperationException("Combine stages not supported"); + } + SpongeChunk_1_8 fs = (SpongeChunk_1_8) fc; + net.minecraft.world.chunk.Chunk nmsChunk = fs.getChunk(); + Chunk spongeChunk = (Chunk) nmsChunk; + + char[][] ids = ((SpongeChunk_1_8) fc).getCombinedIdArrays(); + MutableBlockVolumeWorker blockWorker = spongeChunk.getBlockWorker(); + blockWorker.map(new BlockVolumeMapper() { + @Override + public BlockState map(UnmodifiableBlockVolume volume, int xx, int y, int zz) { + int x = xx & 15; + int z = zz & 15; + int i = FaweCache.CACHE_I[y][z][x]; + char[] array = ids[i]; + if (array == null) { + return null; + } + int combinedId = array[FaweCache.CACHE_J[y][z][x]]; + switch (combinedId) { + case 0: + return null; + case 1: + return AIR; + default: + int id = combinedId >> 4; + Block block = Block.getBlockById(id); + int data = combinedId & 0xf; + IBlockState ibd; + if (data != 0) { + ibd = block.getStateFromMeta(data); + } else { + ibd = block.getDefaultState(); + } + return (BlockState) ibd; + } + } + }); + sendChunk(fs, null); + return true; + } + + public void setCount(int tickingBlockCount, int nonEmptyBlockCount, ExtendedBlockStorage section) throws NoSuchFieldException, IllegalAccessException { + Class clazz = section.getClass(); + Field fieldTickingBlockCount = clazz.getDeclaredField("field_76683_c"); + Field fieldNonEmptyBlockCount = clazz.getDeclaredField("field_76682_b"); + fieldTickingBlockCount.setAccessible(true); + fieldNonEmptyBlockCount.setAccessible(true); + fieldTickingBlockCount.set(section, tickingBlockCount); + fieldNonEmptyBlockCount.set(section, nonEmptyBlockCount); + } + + @Override + public FaweChunk getFaweChunk(int x, int z) { + return new SpongeChunk_1_8(this, x, z); + } + + @Override + public CharFaweChunk getPrevious(CharFaweChunk fs, ExtendedBlockStorage[] sections, Map tilesGeneric, Collection[] entitiesGeneric, Set createdEntities, boolean all) throws Exception { + Settings.IMP.HISTORY.COMBINE_STAGES = false; + throw new UnsupportedOperationException("Combine stages not supported"); + } + + @Override + public boolean loadChunk(World world, int x, int z, boolean generate) { + return getCachedSections(world, x, z) != null; + } + + @Override + public ExtendedBlockStorage[] getCachedSections(World world, int cx, int cz) { + net.minecraft.world.World nmsWorld = (net.minecraft.world.World) world; + IChunkProvider provider = nmsWorld.getChunkProvider(); + net.minecraft.world.chunk.Chunk chunk = provider.provideChunk(cx, cz); + if (chunk == null) { + return null; + } + if (!chunk.isLoaded()) { + chunk.onChunkLoad(); + } + return chunk.getBlockStorageArray(); + } + + + @Override + public int getCombinedId4Data(char[] chars, int x, int y, int z) { + return chars[FaweCache.CACHE_J[y][z & 15][x & 15]]; + } +} diff --git a/sponge111/src/main/java/com/sk89q/worldedit/sponge/SpongePlayer.java b/sponge111/src/main/java/com/sk89q/worldedit/sponge/SpongePlayer.java new file mode 100644 index 00000000..6edc0edb --- /dev/null +++ b/sponge111/src/main/java/com/sk89q/worldedit/sponge/SpongePlayer.java @@ -0,0 +1,233 @@ +/* + * 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.sponge; + +import com.flowpowered.math.vector.Vector3d; +import com.sk89q.util.StringUtil; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.WorldVector; +import com.sk89q.worldedit.blocks.BaseBlock; +import com.sk89q.worldedit.entity.BaseEntity; +import com.sk89q.worldedit.extension.platform.AbstractPlayerActor; +import com.sk89q.worldedit.extent.inventory.BlockBag; +import com.sk89q.worldedit.internal.LocalWorldAdapter; +import com.sk89q.worldedit.internal.cui.CUIEvent; +import com.sk89q.worldedit.session.SessionKey; +import com.sk89q.worldedit.util.Location; +import org.spongepowered.api.data.type.HandTypes; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.item.inventory.ItemStack; +import org.spongepowered.api.text.Text; +import org.spongepowered.api.text.format.TextColor; +import org.spongepowered.api.text.format.TextColors; +import org.spongepowered.api.text.serializer.TextSerializers; +import org.spongepowered.api.world.World; + +import javax.annotation.Nullable; +import java.nio.charset.StandardCharsets; +import java.util.Optional; +import java.util.UUID; + +public class SpongePlayer extends AbstractPlayerActor { + + private final Player player; + + public SpongePlayer(SpongePlatform platform, Player player) { + this.player = player; + ThreadSafeCache.getInstance().getOnlineIds().add(getUniqueId()); + } + + @Override + public UUID getUniqueId() { + return player.getUniqueId(); + } + + @Override + public int getItemInHand() { + Optional is = this.player.getItemInHand(HandTypes.MAIN_HAND); + return is.isPresent() ? SpongeWorldEdit.inst().getAdapter().resolve(is.get().getItem()) : 0; + } + + @Override + public BaseBlock getBlockInHand() throws WorldEditException { + return new BaseBlock(getItemInHand(), 0); + } + + @Override + public String getName() { + return this.player.getName(); + } + + @Override + public BaseEntity getState() { + throw new UnsupportedOperationException("Cannot create a state from this object"); + } + + @Override + public Location getLocation() { + org.spongepowered.api.world.Location entityLoc = this.player.getLocation(); + Vector3d entityRot = this.player.getRotation(); + + return SpongeWorldEdit.inst().getAdapter().adapt(entityLoc, entityRot); + } + + @Override + public WorldVector getPosition() { + Vector3d pos = this.player.getLocation().getPosition(); + return new WorldVector(LocalWorldAdapter.adapt(SpongeWorldEdit.inst().getAdapter().getWorld(this.player.getWorld())), pos.getX(), pos.getY(), pos.getZ()); + } + + @Override + public com.sk89q.worldedit.world.World getWorld() { + return SpongeWorldEdit.inst().getAdapter().getWorld(player.getWorld()); + } + + @Override + public double getPitch() { + return getLocation().getPitch(); + } + + @Override + public double getYaw() { + return getLocation().getYaw(); + } + + @Override + public void giveItem(int type, int amt) { + this.player.getInventory().offer(ItemStack.of(SpongeWorldEdit.inst().getAdapter().resolveItem(type), amt)); + } + + @Override + public void dispatchCUIEvent(CUIEvent event) { + String[] params = event.getParameters(); + String send = event.getTypeId(); + if (params.length > 0) { + send = send + "|" + StringUtil.joinString(params, "|"); + } + + String finalData = send; + CUIChannelHandler.getActiveChannel().sendTo(player, buffer -> buffer.writeBytes(finalData.getBytes(StandardCharsets.UTF_8))); + } + + @Override + public void printRaw(String msg) { + for (String part : msg.split("\n")) { + this.player.sendMessage(TextSerializers.LEGACY_FORMATTING_CODE.deserialize(part)); + } + } + + @Override + public void printDebug(String msg) { + sendColorized(msg, TextColors.GRAY); + } + + @Override + public void print(String msg) { + sendColorized(msg, TextColors.LIGHT_PURPLE); + } + + @Override + public void printError(String msg) { + sendColorized(msg, TextColors.RED); + } + + private void sendColorized(String msg, TextColor formatting) { + for (String part : msg.split("\n")) { + this.player.sendMessage(Text.of(formatting, TextSerializers.LEGACY_FORMATTING_CODE.deserialize(part))); + } + } + + @Override + public void setPosition(Vector pos, float pitch, float yaw) { + org.spongepowered.api.world.Location loc = new org.spongepowered.api.world.Location<>( + this.player.getWorld(), pos.getX(), pos.getY(), pos.getZ() + ); + + this.player.setLocationAndRotation(loc, new Vector3d(pitch, yaw, 0)); + } + + @Override + public String[] getGroups() { + return SpongeWorldEdit.inst().getPermissionsProvider().getGroups(this.player); + } + + @Override + public BlockBag getInventoryBlockBag() { + return null; + } + + @Override + public boolean hasPermission(String perm) { + return SpongeWorldEdit.inst().getPermissionsProvider().hasPermission(player, perm); + } + + @Nullable + @Override + public T getFacet(Class cls) { + return null; + } + + @Override + public SessionKey getSessionKey() { + return new SessionKeyImpl(player.getUniqueId(), player.getName()); + } + + private static class SessionKeyImpl implements SessionKey { + // If not static, this will leak a reference + + private final UUID uuid; + private final String name; + + private SessionKeyImpl(UUID uuid, String name) { + this.uuid = uuid; + this.name = name; + } + + @Override + public UUID getUniqueId() { + return uuid; + } + + @Nullable + @Override + public String getName() { + return name; + } + + @Override + public boolean isActive() { + // We can't directly check if the player is online because + // the list of players is not thread safe + return ThreadSafeCache.getInstance().getOnlineIds().contains(uuid); + } + + @Override + public boolean isPersistent() { + return true; + } + + } + + public static Class inject() { + return SpongePlayer.class; + } + +} \ No newline at end of file diff --git a/sponge111/src/main/java/com/sk89q/worldedit/sponge/chat/SpongeChatManager.java b/sponge111/src/main/java/com/sk89q/worldedit/sponge/chat/SpongeChatManager.java new file mode 100644 index 00000000..d343f637 --- /dev/null +++ b/sponge111/src/main/java/com/sk89q/worldedit/sponge/chat/SpongeChatManager.java @@ -0,0 +1,167 @@ +package com.sk89q.worldedit.sponge.chat; + +import com.boydti.fawe.config.BBC; +import com.boydti.fawe.object.FawePlayer; +import com.boydti.fawe.sponge.SpongePlayer; +import com.boydti.fawe.util.chat.ChatManager; +import com.boydti.fawe.util.chat.Message; +import com.boydti.fawe.wrappers.FakePlayer; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.List; +import org.spongepowered.api.text.Text; +import org.spongepowered.api.text.action.TextActions; +import org.spongepowered.api.text.format.TextColor; +import org.spongepowered.api.text.format.TextColors; +import org.spongepowered.api.text.format.TextStyle; +import org.spongepowered.api.text.format.TextStyles; +import org.spongepowered.api.text.serializer.TextSerializers; + +public class SpongeChatManager implements ChatManager { + + @Override + public Text.Builder builder() { + return Text.builder(); + } + + @Override + public void color(Message message, String color) { + TextColor tc = null; + TextStyle ts = null; + switch (color.charAt(1)) { + case 'a': + tc = TextColors.GREEN; + break; + case 'b': + tc = TextColors.AQUA; + break; + case 'c': + tc = TextColors.RED; + break; + case 'd': + tc = TextColors.LIGHT_PURPLE; + break; + case 'e': + tc = TextColors.YELLOW; + break; + case 'f': + tc = TextColors.WHITE; + break; + case '1': + tc = TextColors.DARK_BLUE; + break; + case '2': + tc = TextColors.DARK_GREEN; + break; + case '3': + tc = TextColors.DARK_AQUA; + break; + case '4': + tc = TextColors.DARK_RED; + break; + case '5': + tc = TextColors.DARK_PURPLE; + break; + case '6': + tc = TextColors.GOLD; + break; + case '7': + tc = TextColors.GRAY; + break; + case '8': + tc = TextColors.DARK_GRAY; + break; + case '9': + tc = TextColors.BLUE; + break; + case '0': + tc = TextColors.BLACK; + break; + case 'k': + ts = TextStyles.OBFUSCATED; + break; + case 'l': + ts = TextStyles.BOLD; + break; + case 'm': + ts = TextStyles.UNDERLINE; + break; + case 'n': + ts = TextStyles.STRIKETHROUGH; + break; + case 'o': + ts = TextStyles.ITALIC; + break; + case 'r': + tc = TextColors.RESET; + break; + } + if (tc != null) { + apply(message, getChild(message).color(tc)); + } + if (ts != null) { + apply(message, getChild(message).style(ts)); + } + } + + public Text.Builder getChild(Message m) { + Text.Builder builder = m.$(this); + List children = builder.getChildren(); + Text last = children.get(children.size() - 1); + builder.remove(last); + return Text.builder().append(last); + } + + public void apply(Message m, Text.Builder builder) { + m.$(this).append(builder.build()); + } + + @Override + public void tooltip(Message message, Message... tooltips) { + Text.Builder builder = Text.builder(); + boolean lb = false; + for (Message tooltip : tooltips) { + if (lb) { + builder.append(Text.of("\n")); + } + builder.append(tooltip.$(this).build()); + lb = true; + } + apply(message, getChild(message).onHover(TextActions.showText(builder.toText()))); + } + + @Override + public void command(Message message, String command) { + apply(message, getChild(message).onClick(TextActions.runCommand(command))); + } + + @Override + public void text(Message message, String text) { + message.$(this).append(TextSerializers.LEGACY_FORMATTING_CODE.deserialize(BBC.color(text))); + } + + @Override + public void send(Message Message, FawePlayer player) { + if (player == FakePlayer.getConsole().toFawePlayer()) { + player.sendMessage(Message.$(this).build().toPlain()); + } else { + ((SpongePlayer) player).parent.sendMessage(Message.$(this).build()); + } + } + + @Override + public void suggest(Message Message, String command) { + apply(Message, getChild(Message).onClick(TextActions.suggestCommand(command))); + } + + @Override + public void link(Message message, String url) { + try { + if (!url.isEmpty()) { + apply(message, getChild(message).onClick(TextActions.openUrl(new URL(url)))); + } + } catch (MalformedURLException e) { + e.printStackTrace(); + } + } +} diff --git a/sponge111/src/main/resources/config.yml b/sponge111/src/main/resources/config.yml new file mode 100644 index 00000000..e69de29b