diff --git a/.gitignore b/.gitignore index b83d222..accafde 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,5 @@ /target/ +.idea/discord.xml +.idea/workspace.xml +.idea/PixelPrinter.iml +.idea/misc.xml diff --git a/pom.xml b/pom.xml index 5d03bdc..97d443b 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 me.zombie_striker PixelPrinter - 1.0.48 + 1.0.5 spigot-repo @@ -37,10 +37,10 @@ maven-compiler-plugin - 3.7.0 + 3.10.1 - 1.7 - 1.7 + 17 + 17 @@ -55,13 +55,18 @@ ${project.basedir}/libs/PluginConstructorAPI.jar + + com.googlecode.json-simple + json-simple + 1.1.1 + org.spigotmc - spigot - 1.16.1-R0.1-SNAPSHOT + spigot-api + 1.18.2-R0.1-SNAPSHOT provided diff --git a/resources/plugin.yml b/resources/plugin.yml index 22bf067..28a593c 100644 --- a/resources/plugin.yml +++ b/resources/plugin.yml @@ -22,5 +22,4 @@ permissions: description: Allows you to stop gifs on the server pixelprinter.delete: description: Allows you to delete images inside the /Pixelprinter/images/ directory -author: - Zombie_Striker \ No newline at end of file +author: Zombie_Striker, Kaspian \ No newline at end of file diff --git a/src/me/zombie_striker/pixelprinter/Metrics.java b/src/me/zombie_striker/pixelprinter/Metrics.java index 7cd7983..71cc69b 100644 --- a/src/me/zombie_striker/pixelprinter/Metrics.java +++ b/src/me/zombie_striker/pixelprinter/Metrics.java @@ -26,649 +26,648 @@ /** * bStats collects some data for plugin authors. *

- * Check out https://bStats.org/ to learn more about bStats! + * Check out https://bStats.org/ to learn more about bStats! */ public class Metrics { - // The version of this bStats class - public static final int B_STATS_VERSION = 1; - // The url to which the data is sent - private static final String URL = "https://bStats.org/submitData/bukkit"; - // Should failed requests be logged? - private static boolean logFailedRequests; - // The uuid of the server - private static String serverUUID; - - static { - // You can use the property to disable the check in your test environment - if (System.getProperty("bstats.relocatecheck") == null || !System.getProperty("bstats.relocatecheck").equals("false")) { - // Maven's Relocate is clever and changes strings, too. So we have to use this little "trick" ... :D - final String defaultPackage = new String( - new byte[]{'o', 'r', 'g', '.', 'b', 's', 't', 'a', 't', 's', '.', 'b', 'u', 'k', 'k', 'i', 't'}); - final String examplePackage = new String(new byte[]{'y', 'o', 'u', 'r', '.', 'p', 'a', 'c', 'k', 'a', 'g', 'e'}); - // We want to make sure nobody just copy & pastes the example and use the wrong package names - if (Metrics.class.getPackage().getName().equals(defaultPackage) || Metrics.class.getPackage().getName().equals(examplePackage)) { - throw new IllegalStateException("bStats Metrics class has not been relocated correctly!"); - } - } - } - - // The plugin - private final JavaPlugin plugin; - - // A list with all custom charts - private final List charts = new ArrayList<>(); - - /** - * Class constructor. - * - * @param plugin The plugin which stats should be submitted. - */ - public Metrics(JavaPlugin plugin) { - if (plugin == null) { - throw new IllegalArgumentException("Plugin cannot be null!"); - } - this.plugin = plugin; - - // Get the config file - File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats"); - File configFile = new File(bStatsFolder, "config.yml"); - YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile); - - // Check if the config file exists - if (!config.isSet("serverUuid")) { - - // Add default values - config.addDefault("enabled", true); - // Every server gets it's unique random id. - config.addDefault("serverUuid", UUID.randomUUID().toString()); - // Should failed request be logged? - config.addDefault("logFailedRequests", false); - - // Inform the server owners about bStats - config.options().header( - "bStats collects some data for plugin authors like how many servers are using their plugins.\n" + - "To honor their work, you should not disable it.\n" + - "This has nearly no effect on the server performance!\n" + - "Check out https://bStats.org/ to learn more :)" - ).copyDefaults(true); - try { - config.save(configFile); - } catch (IOException ignored) { - } - } - - // Load the data - serverUUID = config.getString("serverUuid"); - logFailedRequests = config.getBoolean("logFailedRequests", false); - if (config.getBoolean("enabled", true)) { - boolean found = false; - // Search for all other bStats Metrics classes to see if we are the first one - for (Class service : Bukkit.getServicesManager().getKnownServices()) { - try { - service.getField("B_STATS_VERSION"); // Our identifier :) - found = true; // We aren't the first - break; - } catch (NoSuchFieldException ignored) { - } - } - // Register our service - Bukkit.getServicesManager().register(Metrics.class, this, plugin, ServicePriority.Normal); - if (!found) { - // We are the first! - startSubmitting(); - } - } - } - - /** - * Sends the data to the bStats server. - * - * @param data The data to send. - * @throws Exception If the request failed. - */ - private static void sendData(JSONObject data) throws Exception { - if (data == null) { - throw new IllegalArgumentException("Data cannot be null!"); - } - if (Bukkit.isPrimaryThread()) { - throw new IllegalAccessException("This method must not be called from the main thread!"); - } - HttpsURLConnection connection = (HttpsURLConnection) new URL(URL).openConnection(); - - // Compress the data to save bandwidth - byte[] compressedData = compress(data.toString()); - - // Add headers - connection.setRequestMethod("POST"); - connection.addRequestProperty("Accept", "application/json"); - connection.addRequestProperty("Connection", "close"); - connection.addRequestProperty("Content-Encoding", "gzip"); // We gzip our request - connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length)); - connection.setRequestProperty("Content-Type", "application/json"); // We send our data in JSON format - connection.setRequestProperty("User-Agent", "MC-Server/" + B_STATS_VERSION); - - // Send data - connection.setDoOutput(true); - DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream()); - outputStream.write(compressedData); - outputStream.flush(); - outputStream.close(); - - connection.getInputStream().close(); // We don't care about the response - Just send our data :) - } - - /** - * Gzips the given String. - * - * @param str The string to gzip. - * @return The gzipped String. - * @throws IOException If the compression failed. - */ - private static byte[] compress(final String str) throws IOException { - if (str == null) { - return null; - } - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - GZIPOutputStream gzip = new GZIPOutputStream(outputStream); - gzip.write(str.getBytes(StandardCharsets.UTF_8)); - gzip.close(); - return outputStream.toByteArray(); - } - - /** - * Adds a custom chart. - * - * @param chart The chart to add. - */ - public void addCustomChart(CustomChart chart) { - if (chart == null) { - throw new IllegalArgumentException("Chart cannot be null!"); - } - charts.add(chart); - } - - /** - * Starts the Scheduler which submits our data every 30 minutes. - */ - private void startSubmitting() { - final Timer timer = new Timer(true); // We use a timer cause the Bukkit scheduler is affected by server lags - timer.scheduleAtFixedRate(new TimerTask() { - @Override - public void run() { - if (!plugin.isEnabled()) { // Plugin was disabled - timer.cancel(); - return; - } - // Nevertheless we want our code to run in the Bukkit main thread, so we have to use the Bukkit scheduler - // Don't be afraid! The connection to the bStats server is still async, only the stats collection is sync ;) - Bukkit.getScheduler().runTask(plugin, new Runnable() { - @Override - public void run() { - submitData(); - } - }); - } - }, 1000 * 60 * 5, 1000 * 60 * 30); - // Submit the data every 30 minutes, first time after 5 minutes to give other plugins enough time to start - // WARNING: Changing the frequency has no effect but your plugin WILL be blocked/deleted! - // WARNING: Just don't do it! - } - - /** - * Gets the plugin specific data. - * This method is called using Reflection. - * - * @return The plugin specific data. - */ - @SuppressWarnings("unchecked") - public JSONObject getPluginData() { - JSONObject data = new JSONObject(); - - String pluginName = plugin.getDescription().getName(); - String pluginVersion = plugin.getDescription().getVersion(); - - data.put("pluginName", pluginName); // Append the name of the plugin - data.put("pluginVersion", pluginVersion); // Append the version of the plugin - JSONArray customCharts = new JSONArray(); - for (CustomChart customChart : charts) { - // Add the data of the custom charts - JSONObject chart = customChart.getRequestJsonObject(); - if (chart == null) { // If the chart is null, we skip it - continue; - } - customCharts.add(chart); - } - data.put("customCharts", customCharts); - - return data; - } - - /** - * Gets the server specific data. - * - * @return The server specific data. - */ - @SuppressWarnings("unchecked") - private JSONObject getServerData() { - // Minecraft specific data - int playerAmount; - try { - // Around MC 1.8 the return type was changed to a collection from an array, - // This fixes java.lang.NoSuchMethodError: org.bukkit.Bukkit.getOnlinePlayers()Ljava/util/Collection; - Method onlinePlayersMethod = Class.forName("org.bukkit.Server").getMethod("getOnlinePlayers"); - playerAmount = onlinePlayersMethod.getReturnType().equals(Collection.class) - ? ((Collection) onlinePlayersMethod.invoke(Bukkit.getServer())).size() - : ((Player[]) onlinePlayersMethod.invoke(Bukkit.getServer())).length; - } catch (Exception e) { - playerAmount = Bukkit.getOnlinePlayers().size(); // Just use the new method if the Reflection failed - } - int onlineMode = Bukkit.getOnlineMode() ? 1 : 0; - String bukkitVersion = org.bukkit.Bukkit.getVersion(); - bukkitVersion = bukkitVersion.substring(bukkitVersion.indexOf("MC: ") + 4, bukkitVersion.length() - 1); - - // OS/Java specific data - String javaVersion = System.getProperty("java.version"); - String osName = System.getProperty("os.name"); - String osArch = System.getProperty("os.arch"); - String osVersion = System.getProperty("os.version"); - int coreCount = Runtime.getRuntime().availableProcessors(); - - JSONObject data = new JSONObject(); - - data.put("serverUUID", serverUUID); - - data.put("playerAmount", playerAmount); - data.put("onlineMode", onlineMode); - data.put("bukkitVersion", bukkitVersion); - - data.put("javaVersion", javaVersion); - data.put("osName", osName); - data.put("osArch", osArch); - data.put("osVersion", osVersion); - data.put("coreCount", coreCount); - - return data; - } - - /** - * Collects the data and sends it afterwards. - */ - @SuppressWarnings("unchecked") - private void submitData() { - final JSONObject data = getServerData(); - - JSONArray pluginData = new JSONArray(); - // Search for all other bStats Metrics classes to get their plugin data - for (Class service : Bukkit.getServicesManager().getKnownServices()) { - try { - service.getField("B_STATS_VERSION"); // Our identifier :) - - for (RegisteredServiceProvider provider : Bukkit.getServicesManager().getRegistrations(service)) { - try { - pluginData.add(provider.getService().getMethod("getPluginData").invoke(provider.getProvider())); - } catch (NullPointerException | NoSuchMethodException | IllegalAccessException | InvocationTargetException ignored) { - } - } - } catch (NoSuchFieldException ignored) { - } - } - - data.put("plugins", pluginData); - - // Create a new thread for the connection to the bStats server - new Thread(new Runnable() { - @Override - public void run() { - try { - // Send the data - sendData(data); - } catch (Exception e) { - // Something went wrong! :( - if (logFailedRequests) { - plugin.getLogger().log(Level.WARNING, "Could not submit plugin stats of " + plugin.getName(), e); - } - } - } - }).start(); - } - - /** - * Represents a custom chart. - */ - public static abstract class CustomChart { - - // The id of the chart - final String chartId; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - */ - CustomChart(String chartId) { - if (chartId == null || chartId.isEmpty()) { - throw new IllegalArgumentException("ChartId cannot be null or empty!"); - } - this.chartId = chartId; - } - - @SuppressWarnings("unchecked") - private JSONObject getRequestJsonObject() { - JSONObject chart = new JSONObject(); - chart.put("chartId", chartId); - try { - JSONObject data = getChartData(); - if (data == null) { - // If the data is null we don't send the chart. - return null; - } - chart.put("data", data); - } catch (Throwable t) { - if (logFailedRequests) { - Bukkit.getLogger().log(Level.WARNING, "Failed to get data for custom chart with id " + chartId, t); - } - return null; - } - return chart; - } - - protected abstract JSONObject getChartData() throws Exception; - - } - - /** - * Represents a custom simple pie. - */ - public static class SimplePie extends CustomChart { - - private final Callable callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public SimplePie(String chartId, Callable callable) { - super(chartId); - this.callable = callable; - } - - @SuppressWarnings("unchecked") - @Override - protected JSONObject getChartData() throws Exception { - JSONObject data = new JSONObject(); - String value = callable.call(); - if (value == null || value.isEmpty()) { - // Null = skip the chart - return null; - } - data.put("value", value); - return data; - } - } - - /** - * Represents a custom advanced pie. - */ - public static class AdvancedPie extends CustomChart { - - private final Callable> callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public AdvancedPie(String chartId, Callable> callable) { - super(chartId); - this.callable = callable; - } - - @SuppressWarnings("unchecked") - @Override - protected JSONObject getChartData() throws Exception { - JSONObject data = new JSONObject(); - JSONObject values = new JSONObject(); - Map map = callable.call(); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - boolean allSkipped = true; - for (Map.Entry entry : map.entrySet()) { - if (entry.getValue() == 0) { - continue; // Skip this invalid - } - allSkipped = false; - values.put(entry.getKey(), entry.getValue()); - } - if (allSkipped) { - // Null = skip the chart - return null; - } - data.put("values", values); - return data; - } - } - - /** - * Represents a custom drilldown pie. - */ - public static class DrilldownPie extends CustomChart { - - private final Callable>> callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public DrilldownPie(String chartId, Callable>> callable) { - super(chartId); - this.callable = callable; - } - - @SuppressWarnings("unchecked") - @Override - public JSONObject getChartData() throws Exception { - JSONObject data = new JSONObject(); - JSONObject values = new JSONObject(); - Map> map = callable.call(); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - boolean reallyAllSkipped = true; - for (Map.Entry> entryValues : map.entrySet()) { - JSONObject value = new JSONObject(); - boolean allSkipped = true; - for (Map.Entry valueEntry : map.get(entryValues.getKey()).entrySet()) { - value.put(valueEntry.getKey(), valueEntry.getValue()); - allSkipped = false; - } - if (!allSkipped) { - reallyAllSkipped = false; - values.put(entryValues.getKey(), value); - } - } - if (reallyAllSkipped) { - // Null = skip the chart - return null; - } - data.put("values", values); - return data; - } - } - - /** - * Represents a custom single line chart. - */ - public static class SingleLineChart extends CustomChart { - - private final Callable callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public SingleLineChart(String chartId, Callable callable) { - super(chartId); - this.callable = callable; - } - - @SuppressWarnings("unchecked") - @Override - protected JSONObject getChartData() throws Exception { - JSONObject data = new JSONObject(); - int value = callable.call(); - if (value == 0) { - // Null = skip the chart - return null; - } - data.put("value", value); - return data; - } - - } - - /** - * Represents a custom multi line chart. - */ - public static class MultiLineChart extends CustomChart { - - private final Callable> callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public MultiLineChart(String chartId, Callable> callable) { - super(chartId); - this.callable = callable; - } - - @SuppressWarnings("unchecked") - @Override - protected JSONObject getChartData() throws Exception { - JSONObject data = new JSONObject(); - JSONObject values = new JSONObject(); - Map map = callable.call(); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - boolean allSkipped = true; - for (Map.Entry entry : map.entrySet()) { - if (entry.getValue() == 0) { - continue; // Skip this invalid - } - allSkipped = false; - values.put(entry.getKey(), entry.getValue()); - } - if (allSkipped) { - // Null = skip the chart - return null; - } - data.put("values", values); - return data; - } - - } - - /** - * Represents a custom simple bar chart. - */ - public static class SimpleBarChart extends CustomChart { - - private final Callable> callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public SimpleBarChart(String chartId, Callable> callable) { - super(chartId); - this.callable = callable; - } - - @SuppressWarnings("unchecked") - @Override - protected JSONObject getChartData() throws Exception { - JSONObject data = new JSONObject(); - JSONObject values = new JSONObject(); - Map map = callable.call(); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - for (Map.Entry entry : map.entrySet()) { - JSONArray categoryValues = new JSONArray(); - categoryValues.add(entry.getValue()); - values.put(entry.getKey(), categoryValues); - } - data.put("values", values); - return data; - } - - } - - /** - * Represents a custom advanced bar chart. - */ - public static class AdvancedBarChart extends CustomChart { - - private final Callable> callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public AdvancedBarChart(String chartId, Callable> callable) { - super(chartId); - this.callable = callable; - } - - @SuppressWarnings("unchecked") - @Override - protected JSONObject getChartData() throws Exception { - JSONObject data = new JSONObject(); - JSONObject values = new JSONObject(); - Map map = callable.call(); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - boolean allSkipped = true; - for (Map.Entry entry : map.entrySet()) { - if (entry.getValue().length == 0) { - continue; // Skip this invalid - } - allSkipped = false; - JSONArray categoryValues = new JSONArray(); - for (int categoryValue : entry.getValue()) { - categoryValues.add(categoryValue); - } - values.put(entry.getKey(), categoryValues); - } - if (allSkipped) { - // Null = skip the chart - return null; - } - data.put("values", values); - return data; - } - - } + // The version of this bStats class + public static final int B_STATS_VERSION = 1; + // The url to which the data is sent + private static final String URL = "https://bStats.org/submitData/bukkit"; + // Should failed requests be logged? + private static boolean logFailedRequests; + // The uuid of the server + private static String serverUUID; + + static { + // You can use the property to disable the check in your test environment + if (System.getProperty("bstats.relocatecheck") == null || !System.getProperty("bstats.relocatecheck").equals("false")) { + // Maven's Relocate is clever and changes strings, too. So we have to use this little "trick" ... :D + final String defaultPackage = new String( + new byte[]{'o', 'r', 'g', '.', 'b', 's', 't', 'a', 't', 's', '.', 'b', 'u', 'k', 'k', 'i', 't'}); + final String examplePackage = new String(new byte[]{'y', 'o', 'u', 'r', '.', 'p', 'a', 'c', 'k', 'a', 'g', 'e'}); + // We want to make sure nobody just copy & pastes the example and use the wrong package names + if (Metrics.class.getPackage().getName().equals(defaultPackage) || Metrics.class.getPackage().getName().equals(examplePackage)) { + throw new IllegalStateException("bStats Metrics class has not been relocated correctly!"); + } + } + } + + // The plugin + private final JavaPlugin plugin; + + // A list with all custom charts + private final List charts = new ArrayList<>(); + + /** + * Class constructor. + * + * @param plugin The plugin which stats should be submitted. + */ + public Metrics(JavaPlugin plugin) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null!"); + } + this.plugin = plugin; + + // Get the config file + File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats"); + File configFile = new File(bStatsFolder, "config.yml"); + YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile); + + // Check if the config file exists + if (!config.isSet("serverUuid")) { + + // Add default values + config.addDefault("enabled", true); + // Every server gets it's unique random id. + config.addDefault("serverUuid", UUID.randomUUID().toString()); + // Should failed request be logged? + config.addDefault("logFailedRequests", false); + + // Inform the server owners about bStats + config.options().setHeader( + List.of( + """ + bStats collects some data for plugin authors like how many servers are using their plugins. + To honor their work, you should not disable it. + This has nearly no effect on the server performance! + Check out https://bStats.org/ to learn more :)""" + ) + ).copyDefaults(true); + try { + config.save(configFile); + } catch (IOException ignored) { + } + } + + // Load the data + serverUUID = config.getString("serverUuid"); + logFailedRequests = config.getBoolean("logFailedRequests", false); + if (config.getBoolean("enabled", true)) { + boolean found = false; + // Search for all other bStats Metrics classes to see if we are the first one + for (Class service : Bukkit.getServicesManager().getKnownServices()) { + try { + service.getField("B_STATS_VERSION"); // Our identifier :) + found = true; // We aren't the first + break; + } catch (NoSuchFieldException ignored) { + } + } + // Register our service + Bukkit.getServicesManager().register(Metrics.class, this, plugin, ServicePriority.Normal); + if (!found) { + // We are the first! + startSubmitting(); + } + } + } + + /** + * Sends the data to the bStats server. + * + * @param data The data to send. + * @throws Exception If the request failed. + */ + private static void sendData(JSONObject data) throws Exception { + if (data == null) { + throw new IllegalArgumentException("Data cannot be null!"); + } + if (Bukkit.isPrimaryThread()) { + throw new IllegalAccessException("This method must not be called from the main thread!"); + } + HttpsURLConnection connection = (HttpsURLConnection) new URL(URL).openConnection(); + + // Compress the data to save bandwidth + byte[] compressedData = compress(data.toString()); + + // Add headers + connection.setRequestMethod("POST"); + connection.addRequestProperty("Accept", "application/json"); + connection.addRequestProperty("Connection", "close"); + connection.addRequestProperty("Content-Encoding", "gzip"); // We gzip our request + connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length)); + connection.setRequestProperty("Content-Type", "application/json"); // We send our data in JSON format + connection.setRequestProperty("User-Agent", "MC-Server/" + B_STATS_VERSION); + + // Send data + connection.setDoOutput(true); + DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream()); + outputStream.write(compressedData); + outputStream.flush(); + outputStream.close(); + + connection.getInputStream().close(); // We don't care about the response - Just send our data :) + } + + /** + * Gzips the given String. + * + * @param str The string to gzip. + * @return The gzipped String. + * @throws IOException If the compression failed. + */ + private static byte[] compress(final String str) throws IOException { + if (str == null) { + return null; + } + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + GZIPOutputStream gzip = new GZIPOutputStream(outputStream); + gzip.write(str.getBytes(StandardCharsets.UTF_8)); + gzip.close(); + return outputStream.toByteArray(); + } + + /** + * Adds a custom chart. + * + * @param chart The chart to add. + */ + public void addCustomChart(CustomChart chart) { + if (chart == null) { + throw new IllegalArgumentException("Chart cannot be null!"); + } + charts.add(chart); + } + + /** + * Starts the Scheduler which submits our data every 30 minutes. + */ + private void startSubmitting() { + final Timer timer = new Timer(true); // We use a timer cause the Bukkit scheduler is affected by server lags + timer.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + if (!plugin.isEnabled()) { // Plugin was disabled + timer.cancel(); + return; + } + // Nevertheless we want our code to run in the Bukkit main thread, so we have to use the Bukkit scheduler + // Don't be afraid! The connection to the bStats server is still async, only the stats collection is sync ;) + Bukkit.getScheduler().runTask(plugin, () -> submitData()); + } + }, 1000 * 60 * 5, 1000 * 60 * 30); + // Submit the data every 30 minutes, first time after 5 minutes to give other plugins enough time to start + // WARNING: Changing the frequency has no effect but your plugin WILL be blocked/deleted! + // WARNING: Just don't do it! + } + + /** + * Gets the plugin specific data. + * This method is called using Reflection. + * + * @return The plugin specific data. + */ + @SuppressWarnings("unchecked") + public JSONObject getPluginData() { + JSONObject data = new JSONObject(); + + String pluginName = plugin.getDescription().getName(); + String pluginVersion = plugin.getDescription().getVersion(); + + data.put("pluginName", pluginName); // Append the name of the plugin + data.put("pluginVersion", pluginVersion); // Append the version of the plugin + JSONArray customCharts = new JSONArray(); + for (CustomChart customChart : charts) { + // Add the data of the custom charts + JSONObject chart = customChart.getRequestJsonObject(); + if (chart == null) { // If the chart is null, we skip it + continue; + } + customCharts.add(chart); + } + data.put("customCharts", customCharts); + + return data; + } + + /** + * Gets the server specific data. + * + * @return The server specific data. + */ + @SuppressWarnings("unchecked") + private JSONObject getServerData() { + // Minecraft specific data + int playerAmount; + try { + // Around MC 1.8 the return type was changed to a collection from an array, + // This fixes java.lang.NoSuchMethodError: org.bukkit.Bukkit.getOnlinePlayers()Ljava/util/Collection; + Method onlinePlayersMethod = Class.forName("org.bukkit.Server").getMethod("getOnlinePlayers"); + playerAmount = onlinePlayersMethod.getReturnType().equals(Collection.class) + ? ((Collection) onlinePlayersMethod.invoke(Bukkit.getServer())).size() + : ((Player[]) onlinePlayersMethod.invoke(Bukkit.getServer())).length; + } catch (Exception e) { + playerAmount = Bukkit.getOnlinePlayers().size(); // Just use the new method if the Reflection failed + } + int onlineMode = Bukkit.getOnlineMode() ? 1 : 0; + String bukkitVersion = org.bukkit.Bukkit.getVersion(); + bukkitVersion = bukkitVersion.substring(bukkitVersion.indexOf("MC: ") + 4, bukkitVersion.length() - 1); + + // OS/Java specific data + String javaVersion = System.getProperty("java.version"); + String osName = System.getProperty("os.name"); + String osArch = System.getProperty("os.arch"); + String osVersion = System.getProperty("os.version"); + int coreCount = Runtime.getRuntime().availableProcessors(); + + JSONObject data = new JSONObject(); + + data.put("serverUUID", serverUUID); + + data.put("playerAmount", playerAmount); + data.put("onlineMode", onlineMode); + data.put("bukkitVersion", bukkitVersion); + + data.put("javaVersion", javaVersion); + data.put("osName", osName); + data.put("osArch", osArch); + data.put("osVersion", osVersion); + data.put("coreCount", coreCount); + + return data; + } + + /** + * Collects the data and sends it afterwards. + */ + @SuppressWarnings("unchecked") + private void submitData() { + final JSONObject data = getServerData(); + + JSONArray pluginData = new JSONArray(); + // Search for all other bStats Metrics classes to get their plugin data + for (Class service : Bukkit.getServicesManager().getKnownServices()) { + try { + service.getField("B_STATS_VERSION"); // Our identifier :) + + for (RegisteredServiceProvider provider : Bukkit.getServicesManager().getRegistrations(service)) { + try { + pluginData.add(provider.getService().getMethod("getPluginData").invoke(provider.getProvider())); + } catch (NullPointerException | NoSuchMethodException | IllegalAccessException | + InvocationTargetException ignored) { + } + } + } catch (NoSuchFieldException ignored) { + } + } + + data.put("plugins", pluginData); + + // Create a new thread for the connection to the bStats server + new Thread(new Runnable() { + @Override + public void run() { + try { + // Send the data + sendData(data); + } catch (Exception e) { + // Something went wrong! :( + if (logFailedRequests) { + plugin.getLogger().log(Level.WARNING, "Could not submit plugin stats of " + plugin.getName(), e); + } + } + } + }).start(); + } + + /** + * Represents a custom chart. + */ + public static abstract class CustomChart { + + // The id of the chart + final String chartId; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + */ + CustomChart(String chartId) { + if (chartId == null || chartId.isEmpty()) { + throw new IllegalArgumentException("ChartId cannot be null or empty!"); + } + this.chartId = chartId; + } + + @SuppressWarnings("unchecked") + private JSONObject getRequestJsonObject() { + JSONObject chart = new JSONObject(); + chart.put("chartId", chartId); + try { + JSONObject data = getChartData(); + if (data == null) { + // If the data is null we don't send the chart. + return null; + } + chart.put("data", data); + } catch (Throwable t) { + if (logFailedRequests) { + Bukkit.getLogger().log(Level.WARNING, "Failed to get data for custom chart with id " + chartId, t); + } + return null; + } + return chart; + } + + protected abstract JSONObject getChartData() throws Exception; + + } + + /** + * Represents a custom simple pie. + */ + public static class SimplePie extends CustomChart { + + private final Callable callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public SimplePie(String chartId, Callable callable) { + super(chartId); + this.callable = callable; + } + + @SuppressWarnings("unchecked") + @Override + protected JSONObject getChartData() throws Exception { + JSONObject data = new JSONObject(); + String value = callable.call(); + if (value == null || value.isEmpty()) { + // Null = skip the chart + return null; + } + data.put("value", value); + return data; + } + } + + /** + * Represents a custom advanced pie. + */ + public static class AdvancedPie extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public AdvancedPie(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @SuppressWarnings("unchecked") + @Override + protected JSONObject getChartData() throws Exception { + JSONObject data = new JSONObject(); + JSONObject values = new JSONObject(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean allSkipped = true; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue() == 0) { + continue; // Skip this invalid + } + allSkipped = false; + values.put(entry.getKey(), entry.getValue()); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + data.put("values", values); + return data; + } + } + + /** + * Represents a custom drilldown pie. + */ + public static class DrilldownPie extends CustomChart { + + private final Callable>> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public DrilldownPie(String chartId, Callable>> callable) { + super(chartId); + this.callable = callable; + } + + @SuppressWarnings("unchecked") + @Override + public JSONObject getChartData() throws Exception { + JSONObject data = new JSONObject(); + JSONObject values = new JSONObject(); + Map> map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean reallyAllSkipped = true; + for (Map.Entry> entryValues : map.entrySet()) { + JSONObject value = new JSONObject(); + boolean allSkipped = true; + for (Map.Entry valueEntry : map.get(entryValues.getKey()).entrySet()) { + value.put(valueEntry.getKey(), valueEntry.getValue()); + allSkipped = false; + } + if (!allSkipped) { + reallyAllSkipped = false; + values.put(entryValues.getKey(), value); + } + } + if (reallyAllSkipped) { + // Null = skip the chart + return null; + } + data.put("values", values); + return data; + } + } + + /** + * Represents a custom single line chart. + */ + public static class SingleLineChart extends CustomChart { + + private final Callable callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public SingleLineChart(String chartId, Callable callable) { + super(chartId); + this.callable = callable; + } + + @SuppressWarnings("unchecked") + @Override + protected JSONObject getChartData() throws Exception { + JSONObject data = new JSONObject(); + int value = callable.call(); + if (value == 0) { + // Null = skip the chart + return null; + } + data.put("value", value); + return data; + } + + } + + /** + * Represents a custom multi line chart. + */ + public static class MultiLineChart extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public MultiLineChart(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @SuppressWarnings("unchecked") + @Override + protected JSONObject getChartData() throws Exception { + JSONObject data = new JSONObject(); + JSONObject values = new JSONObject(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean allSkipped = true; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue() == 0) { + continue; // Skip this invalid + } + allSkipped = false; + values.put(entry.getKey(), entry.getValue()); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + data.put("values", values); + return data; + } + + } + + /** + * Represents a custom simple bar chart. + */ + public static class SimpleBarChart extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public SimpleBarChart(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @SuppressWarnings("unchecked") + @Override + protected JSONObject getChartData() throws Exception { + JSONObject data = new JSONObject(); + JSONObject values = new JSONObject(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + for (Map.Entry entry : map.entrySet()) { + JSONArray categoryValues = new JSONArray(); + categoryValues.add(entry.getValue()); + values.put(entry.getKey(), categoryValues); + } + data.put("values", values); + return data; + } + + } + + /** + * Represents a custom advanced bar chart. + */ + public static class AdvancedBarChart extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public AdvancedBarChart(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @SuppressWarnings("unchecked") + @Override + protected JSONObject getChartData() throws Exception { + JSONObject data = new JSONObject(); + JSONObject values = new JSONObject(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean allSkipped = true; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue().length == 0) { + continue; // Skip this invalid + } + allSkipped = false; + JSONArray categoryValues = new JSONArray(); + for (int categoryValue : entry.getValue()) { + categoryValues.add(categoryValue); + } + values.put(entry.getKey(), categoryValues); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + data.put("values", values); + return data; + } + + } } \ No newline at end of file diff --git a/src/me/zombie_striker/pixelprinter/util/SkinCreator.java b/src/me/zombie_striker/pixelprinter/util/SkinCreator.java index 4a5ea4d..8f66b78 100644 --- a/src/me/zombie_striker/pixelprinter/util/SkinCreator.java +++ b/src/me/zombie_striker/pixelprinter/util/SkinCreator.java @@ -1,6 +1,5 @@ package me.zombie_striker.pixelprinter.util; -import com.sun.org.apache.xerces.internal.impl.dv.util.Base64; import me.zombie_striker.pixelprinter.data.Direction; import me.zombie_striker.pixelprinter.util.RGBBlockColor.Pixel; import org.bukkit.Location; @@ -11,6 +10,7 @@ import java.io.InputStreamReader; import java.lang.reflect.Method; import java.net.URL; +import java.util.Base64; public class SkinCreator { @@ -30,7 +30,7 @@ public static BufferedImage[] getSkin(String uuid) String[] aaaa = code.toString().split("\"value\" : \""); if (aaaa.length == 1) { System.out.println("The user does not exist- AAAA does not contain value. Response:"); - System.out.println(code.toString()); + System.out.println(code); throw new NullPointerException(); } String decode; @@ -43,7 +43,7 @@ public static BufferedImage[] getSkin(String uuid) .split("\"}],\"legacy\"")[0].split("\"}}}")[0].split("\"")[0]); } catch (Error | Exception e4) { linecode = 45; - decode = new String(Base64.decode(aaaa[1] + decode = new String(Base64.getDecoder().decode(aaaa[1] .split("\"}],\"legacy\"")[0].split("\"}}}")[0].split("\"")[0])); } linecode = 49; @@ -76,52 +76,51 @@ public static void createStatue(BufferedImage[] images, Location center, BufferedImage skin = images[0]; BufferedImage cape = images[1]; - Direction front = dir; Direction back = null; Direction left = null; Direction right = null; Direction flat = null; - switch (front) { - case UP_EAST: + switch (dir) { + case UP_EAST -> { back = Direction.UP_WEST; right = Direction.UP_NORTH; left = Direction.UP_SOUTH; flat = Direction.FLAT_SOUTHEAST; - break; - case UP_WEST: + } + case UP_WEST -> { back = Direction.UP_EAST; right = Direction.UP_SOUTH; left = Direction.UP_NORTH; flat = Direction.FLAT_NORTHWEST; - break; - case UP_NORTH: + } + case UP_NORTH -> { back = Direction.UP_SOUTH; right = Direction.UP_EAST; left = Direction.UP_WEST; flat = Direction.FLAT_SOUTHEAST; - break; - case UP_SOUTH: + } + case UP_SOUTH -> { back = Direction.UP_NORTH; right = Direction.UP_WEST; left = Direction.UP_EAST; flat = Direction.FLAT_NORTHWEST; - break; - default: - break; + } + default -> { + } } /** * * This code would be right, If the direction the block is facing is the * same as the direction of the wall. - * + *

* switch (front) { - * + *

* case UP_SOUTH: back = Direction.UP_NORTH; right = Direction.UP_WEST; * left = Direction.UP_EAST; flat = Direction.FLAT_SOUTHWEST; break; * case UP_NORTH: back = Direction.UP_SOUTH; right = Direction.UP_EAST; * left = Direction.UP_WEST; flat = Direction.FLAT_NORTHEAST; - * + *

* break; case UP_EAST: back = Direction.UP_WEST; right = * Direction.UP_SOUTH; left = Direction.UP_NORTH; flat = * Direction.FLAT_SOUTHEAST; break; case UP_WEST: back = @@ -137,7 +136,7 @@ public static void createStatue(BufferedImage[] images, Location center, -3, loc, left, - front, + dir, RGBBlockColor.createResizedCopy( skin.getSubimage(0, 32 - 12, 4, 12), 24, false)); a(-1, @@ -145,19 +144,19 @@ public static void createStatue(BufferedImage[] images, Location center, 0, loc, right, - front, + dir, RGBBlockColor.createResizedCopy( - skin.getSubimage(0 + 8, 32 - 12, 4, 12), 24, false)); + skin.getSubimage(8, 32 - 12, 4, 12), 24, false)); a(2, 0, -3, loc, - front, - front, + dir, + dir, RGBBlockColor.createResizedCopy( - skin.getSubimage(0 + 4, 32 - 12, 4, 12), 24, false)); - a(-1, 0, 0, loc, back, front, RGBBlockColor.createResizedCopy( - skin.getSubimage(0 + 12, 32 - 12, 4, 12), 24, false)); + skin.getSubimage(4, 32 - 12, 4, 12), 24, false)); + a(-1, 0, 0, loc, back, dir, RGBBlockColor.createResizedCopy( + skin.getSubimage(12, 32 - 12, 4, 12), 24, false)); } // Legs (right) @@ -173,7 +172,7 @@ public static void createStatue(BufferedImage[] images, Location center, 1, loc, left, - front, + dir, RGBBlockColor.createResizedCopy( skin.getSubimage(x, y - 12, 4, 12), 24, false)); a(-1, @@ -181,15 +180,15 @@ public static void createStatue(BufferedImage[] images, Location center, 4, loc, right, - front, + dir, RGBBlockColor.createResizedCopy( skin.getSubimage(x + 8, y - 12, 4, 12), 24, false)); a(2, 0, 1, loc, - front, - front, + dir, + dir, RGBBlockColor.createResizedCopy( skin.getSubimage(x + 4, y - 12, 4, 12), 24, false)); a(-1, @@ -197,7 +196,7 @@ public static void createStatue(BufferedImage[] images, Location center, 4, loc, back, - front, + dir, RGBBlockColor.createResizedCopy( skin.getSubimage(x + 12, y - 12, 4, 12), 24, false)); } @@ -209,7 +208,7 @@ public static void createStatue(BufferedImage[] images, Location center, -3 + 8, loc, flat, - front, + dir, RGBBlockColor.createResizedCopy( skin.getSubimage(40 + 4, 16, 4, 4), 8, false)); a(-1, @@ -217,7 +216,7 @@ public static void createStatue(BufferedImage[] images, Location center, -3 + 8, loc, flat, - front, + dir, RGBBlockColor.createResizedCopy( skin.getSubimage(40 + 4 + 4, 16, 4, 4), 8, false)); @@ -226,7 +225,7 @@ public static void createStatue(BufferedImage[] images, Location center, -3 + 8, loc, left, - front, + dir, RGBBlockColor.createResizedCopy( skin.getSubimage(40, 20, 4, 12), 24, false)); a(-1, @@ -234,15 +233,15 @@ public static void createStatue(BufferedImage[] images, Location center, 8, loc, right, - front, + dir, RGBBlockColor.createResizedCopy( skin.getSubimage(40 + 8, 20, 4, 12), 24, false)); a(2, 12, -3 + 8, loc, - front, - front, + dir, + dir, RGBBlockColor.createResizedCopy( skin.getSubimage(40 + 4, 20, 4, 12), 24, false)); a(-1, @@ -250,7 +249,7 @@ public static void createStatue(BufferedImage[] images, Location center, 8, loc, back, - front, + dir, RGBBlockColor.createResizedCopy( skin.getSubimage(40 + 12, 20, 4, 12), 24, false)); } @@ -258,10 +257,6 @@ public static void createStatue(BufferedImage[] images, Location center, { int x = 32; int y = 20; - if (skin.getHeight() == 32) { - x = 32; - y = 20; - } // Tops and bottoms @@ -270,7 +265,7 @@ public static void createStatue(BufferedImage[] images, Location center, -3 - 4, loc, left, - front, + dir, RGBBlockColor.createResizedCopy( skin.getSubimage(x, y, 4, 12), 24, false)); a(-1, @@ -278,15 +273,15 @@ public static void createStatue(BufferedImage[] images, Location center, -4, loc, right, - front, + dir, RGBBlockColor.createResizedCopy( skin.getSubimage(x + 8, y, 4, 12), 24, false)); a(-1, 12, -3 - 4, loc, - front, - front, + dir, + dir, RGBBlockColor.createResizedCopy( skin.getSubimage(x + 4, y, 4, 12), 24, false)); a(2, @@ -294,7 +289,7 @@ public static void createStatue(BufferedImage[] images, Location center, -4, loc, back, - front, + dir, RGBBlockColor.createResizedCopy( skin.getSubimage(x + 12, y, 4, 12), 24, false)); @@ -303,7 +298,7 @@ public static void createStatue(BufferedImage[] images, Location center, -3 - 4, loc, flat, - front, + dir, RGBBlockColor.createResizedCopy( skin.getSubimage(x + 4, y - 4, 4, 4), 8, false)); a(-1, @@ -311,7 +306,7 @@ public static void createStatue(BufferedImage[] images, Location center, -3 - 4, loc, flat, - front, + dir, RGBBlockColor.createResizedCopy( skin.getSubimage(x + 4 + 4, y - 4, 4, 4), 8, false)); } @@ -322,7 +317,7 @@ public static void createStatue(BufferedImage[] images, Location center, -3, loc, left, - front, + dir, RGBBlockColor.createResizedCopy( skin.getSubimage(16, 20, 4, 12), 24, false)); a(-1, @@ -330,15 +325,15 @@ public static void createStatue(BufferedImage[] images, Location center, 4, loc, right, - front, + dir, RGBBlockColor.createResizedCopy( skin.getSubimage(16 + 12, 20, 4, 12), 24, false)); a(2, 12, -3, loc, - front, - front, + dir, + dir, RGBBlockColor.createResizedCopy( skin.getSubimage(16 + 4, 20, 8, 12), 24, false)); a(-1, @@ -346,7 +341,7 @@ public static void createStatue(BufferedImage[] images, Location center, 4, loc, back, - front, + dir, RGBBlockColor.createResizedCopy( skin.getSubimage(16 + 16, 20, 8, 12), 24, false)); } @@ -357,7 +352,7 @@ public static void createStatue(BufferedImage[] images, Location center, -3, loc, flat, - front, + dir, RGBBlockColor.createResizedCopy( skin.getSubimage(32 + 16, 0, 8, 8), 16, true), true); a(-3, @@ -365,7 +360,7 @@ public static void createStatue(BufferedImage[] images, Location center, -3, loc, flat, - front, + dir, RGBBlockColor.createResizedCopy( skin.getSubimage(32 + 8, 0, 8, 8), 16, true), true); } @@ -377,7 +372,7 @@ public static void createStatue(BufferedImage[] images, Location center, -3, loc, flat, - front, + dir, RGBBlockColor.createResizedCopy( skin.getSubimage(16, 0, 8, 8), 16, false)); a(-3, @@ -385,7 +380,7 @@ public static void createStatue(BufferedImage[] images, Location center, -3, loc, flat, - front, + dir, RGBBlockColor.createResizedCopy( skin.getSubimage(8, 0, 8, 8), 16, false)); @@ -394,7 +389,7 @@ public static void createStatue(BufferedImage[] images, Location center, -3, loc, right, - front, + dir, RGBBlockColor.createResizedCopy( skin.getSubimage(0, 8, 8, 8), 16, false)); a(4, @@ -402,7 +397,7 @@ public static void createStatue(BufferedImage[] images, Location center, 4, loc, left, - front, + dir, RGBBlockColor.createResizedCopy( skin.getSubimage(16, 8, 8, 8), 16, false)); a(-3, @@ -410,15 +405,15 @@ public static void createStatue(BufferedImage[] images, Location center, 4, loc, back, - front, + dir, RGBBlockColor.createResizedCopy( skin.getSubimage(24, 8, 8, 8), 16, false)); a(4, 24, -3, loc, - front, - front, + dir, + dir, RGBBlockColor.createResizedCopy( skin.getSubimage(8, 8, 8, 8), 16, false)); } @@ -430,15 +425,15 @@ public static void createStatue(BufferedImage[] images, Location center, -4,//-3 loc, right, - front, + dir, RGBBlockColor.createResizedCopy( - skin.getSubimage(32 + 0, 8, 8, 8), 16, true), true); + skin.getSubimage(32, 8, 8, 8), 16, true), true); a(4, 24, 5,//4, loc, left, - front, + dir, RGBBlockColor.createResizedCopy( skin.getSubimage(32 + 16, 8, 8, 8), 16, true), true); a(-4,//-3, @@ -446,15 +441,15 @@ public static void createStatue(BufferedImage[] images, Location center, 4, loc, back, - front, + dir, RGBBlockColor.createResizedCopy( skin.getSubimage(32 + 24, 8, 8, 8), 16, true), true); a(5,//4, 24, -3, loc, - front, - front, + dir, + dir, RGBBlockColor.createResizedCopy( skin.getSubimage(32 + 8, 8, 8, 8), 16, true), true); } @@ -464,8 +459,8 @@ public static void createStatue(BufferedImage[] images, Location center, 6, -9 + 4, loc, - front, - front, + dir, + dir, RGBBlockColor.createResizedCopy( cape.getSubimage(0, 0, 12, 18), 18 * 2, true), true); } @@ -497,9 +492,8 @@ private static void a(int x, int y, int z, Location loc, Direction d, * Direction.UP_WEST; } else { d = Direction.UP_EAST; } */ } - BufferedImage temp = skin2; - Pixel[][] result = RGBBlockColor.convertTo2DWithoutUsingGetRGB(temp); - new AsyncImageHolder(result, null, getOffset(loc, f, x, y, z), d, temp, + Pixel[][] result = RGBBlockColor.convertTo2DWithoutUsingGetRGB(skin2); + new AsyncImageHolder(result, null, getOffset(loc, f, x, y, z), d, skin2, enableTrans).loadImage(false); }