package com.intellectualcrafters.plot.util; import com.google.common.base.Optional; import com.intellectualcrafters.plot.PS; import com.intellectualcrafters.plot.config.C; import com.intellectualcrafters.plot.config.Settings; import com.intellectualcrafters.plot.database.DBFunc; import com.intellectualcrafters.plot.flag.Flag; import com.intellectualcrafters.plot.flag.FlagManager; import com.intellectualcrafters.plot.flag.Flags; import com.intellectualcrafters.plot.object.ConsolePlayer; import com.intellectualcrafters.plot.object.Location; import com.intellectualcrafters.plot.object.Plot; import com.intellectualcrafters.plot.object.PlotArea; import com.intellectualcrafters.plot.object.PlotId; import com.intellectualcrafters.plot.object.PlotPlayer; import com.intellectualcrafters.plot.object.RegionWrapper; import com.intellectualcrafters.plot.object.RunnableVal; import com.intellectualcrafters.plot.object.stream.AbstractDelegateOutputStream; import com.intellectualcrafters.plot.util.expiry.ExpireManager; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; import java.nio.charset.StandardCharsets; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.UUID; /** * plot functions * */ public class MainUtil { /** * If the NMS code for sending chunk updates is functional<br> * - E.g. If using an older version of Bukkit, or before the plugin is updated to 1.5<br> * - Slower fallback code will be used if not.<br> */ public static boolean canSendChunk = false; /** * Cache of mapping x,y,z coordinates to the chunk array<br> * - Used for efficent world generation<br> */ public static short[][] x_loc; public static short[][] y_loc; public static short[][] z_loc; public static short[][][] CACHE_I = null; public static short[][][] CACHE_J = null; /** * * @deprecated * @param location * @return */ @Deprecated public static PlotId getPlotId(Location location) { PlotArea area = location.getPlotArea(); return area == null ? null : area.getPlotManager().getPlotId(area, location.getX(), location.getY(), location.getZ()); } /** * This cache is used for world generation and just saves a bit of calculation time when checking if something is in the plot area. */ public static void initCache() { if (x_loc == null) { x_loc = new short[16][4096]; y_loc = new short[16][4096]; z_loc = new short[16][4096]; for (int i = 0; i < 16; i++) { int i4 = i << 4; for (int j = 0; j < 4096; j++) { int y = i4 + (j >> 8); int a = j - ((y & 0xF) << 8); int z1 = a >> 4; int x1 = a - (z1 << 4); x_loc[i][j] = (short) x1; y_loc[i][j] = (short) y; z_loc[i][j] = (short) z1; } } } if (CACHE_I == null) { CACHE_I = new short[256][16][16]; CACHE_J = new short[256][16][16]; for (int x = 0; x < 16; x++) { for (int z = 0; z < 16; z++) { for (int y = 0; y < 256; y++) { short i = (short) (y >> 4); short j = (short) ((y & 0xF) << 8 | z << 4 | x); CACHE_I[y][x][z] = i; CACHE_J[y][x][z] = j; } } } } } public static void upload(UUID uuid, String file, String extension, final RunnableVal<OutputStream> writeTask, final RunnableVal<URL> whenDone) { if (writeTask == null) { PS.debug("&cWrite task cannot be null"); TaskManager.runTask(whenDone); return; } final String filename; final String website; if (uuid == null) { uuid = UUID.randomUUID(); website = Settings.Web.URL + "upload.php?" + uuid; filename = "plot." + extension; } else { website = Settings.Web.URL + "save.php?" + uuid; filename = file + '.' + extension; } final URL url; try { url = new URL(Settings.Web.URL + "?key=" + uuid + "&type=" + extension); } catch (MalformedURLException e) { e.printStackTrace(); whenDone.run(); return; } TaskManager.runTaskAsync(new Runnable() { @Override public void run() { try { String boundary = Long.toHexString(System.currentTimeMillis()); URLConnection con = new URL(website).openConnection(); con.setDoOutput(true); con.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary); try (OutputStream output = con.getOutputStream(); PrintWriter writer = new PrintWriter(new OutputStreamWriter(output, StandardCharsets.UTF_8), true)) { String CRLF = "\r\n"; writer.append("--" + boundary).append(CRLF); writer.append("Content-Disposition: form-data; name=\"param\"").append(CRLF); writer.append("Content-Type: text/plain; charset=" + StandardCharsets.UTF_8.displayName()).append(CRLF); String param = "value"; writer.append(CRLF).append(param).append(CRLF).flush(); writer.append("--" + boundary).append(CRLF); writer.append("Content-Disposition: form-data; name=\"schematicFile\"; filename=\"" + filename + '"').append(CRLF); writer.append("Content-Type: " + URLConnection.guessContentTypeFromName(filename)).append(CRLF); writer.append("Content-Transfer-Encoding: binary").append(CRLF); writer.append(CRLF).flush(); writeTask.value = new AbstractDelegateOutputStream(output) { @Override public void close() { } // Don't close }; writeTask.run(); output.flush(); writer.append(CRLF).flush(); writer.append("--" + boundary + "--").append(CRLF).flush(); } // try (Reader response = new InputStreamReader(con.getInputStream(), StandardCharsets.UTF_8)) { // final char[] buffer = new char[256]; // final StringBuilder result = new StringBuilder(); // while (true) { // final int r = response.read(buffer); // if (r < 0) { // break; // } // result.append(buffer, 0, r); // } // if (!result.toString().startsWith("Success")) { // PS.debug(result); // } // } catch (IOException e) { // e.printStackTrace(); // } int responseCode = ((HttpURLConnection) con).getResponseCode(); if (responseCode == 200) { whenDone.value = url; } TaskManager.runTask(whenDone); } catch (IOException e) { e.printStackTrace(); TaskManager.runTask(whenDone); } } }); } /** * Resets the biome if it was modified * @param area * @param pos1 * @param pos2 * @return true if any changes were made */ public static boolean resetBiome(PlotArea area, Location pos1, Location pos2) { String biome = area.PLOT_BIOME; if (!StringMan.isEqual(WorldUtil.IMP.getBiome(area.worldname, (pos1.getX() + pos2.getX()) / 2, (pos1.getZ() + pos2.getZ()) / 2), biome)) { MainUtil.setBiome(area.worldname, pos1.getX(), pos1.getZ(), pos2.getX(), pos2.getZ(), biome); return true; } return false; } public static String secToTime(long time) { StringBuilder toreturn = new StringBuilder(); if (time >= 33868800) { int years = (int) (time / 33868800); time -= years * 33868800; toreturn.append(years+"y "); } if (time >= 604800) { int weeks = (int) (time / 604800); time -= weeks * 604800; toreturn.append(weeks+"w "); } if (time >= 86400) { int days = (int) (time / 86400); time -= days * 86400; toreturn.append(days+"d "); } if (time >= 3600) { int hours = (int) (time / 3600); time -= hours * 3600; toreturn.append(hours+"h "); } if (time>=60) { int minutes = (int) (time / 60); time -= minutes * 60; toreturn.append(minutes+"m "); } if (toreturn.equals("")||time>0){ toreturn.append((time)+"s "); } return toreturn.toString().trim(); } public static long timeToSec(String string) { if (MathMan.isInteger(string)) { return Long.parseLong(string); } string = string.toLowerCase().trim().toLowerCase(); if (string.equalsIgnoreCase("false")) { return 0; } String[] split = string.split(" "); long time = 0; for (String value : split) { int nums = Integer.parseInt(value.replaceAll("[^\\d]", "")); String letters = value.replaceAll("[^a-z]", ""); switch (letters) { case "week": case "weeks": case "wks": case "w": time += 604800 * nums; case "days": case "day": case "d": time += 86400 * nums; case "hour": case "hr": case "hrs": case "hours": case "h": time += 3600 * nums; case "minutes": case "minute": case "mins": case "min": case "m": time += 60 * nums; case "seconds": case "second": case "secs": case "sec": case "s": time += nums; } } return time; } /** * Hashcode of a boolean array.<br> * - Used for traversing mega plots quickly. * @param array * @return hashcode */ public static int hash(boolean[] array) { if (array.length == 4) { if (!array[0] && !array[1] && !array[2] && !array[3]) { return 0; } return ((array[0] ? 1 : 0) << 3) + ((array[1] ? 1 : 0) << 2) + ((array[2] ? 1 : 0) << 1) + (array[3] ? 1 : 0); } int n = 0; for (boolean anArray : array) { n = (n << 1) + (anArray ? 1 : 0); } return n; } /** * Get a list of plot ids within a selection. * @param pos1 * @param pos2 * @return */ public static ArrayList<PlotId> getPlotSelectionIds(PlotId pos1, PlotId pos2) { ArrayList<PlotId> myPlots = new ArrayList<>(); for (int x = pos1.x; x <= pos2.x; x++) { for (int y = pos1.y; y <= pos2.y; y++) { myPlots.add(new PlotId(x, y)); } } return myPlots; } /** * Get the name from a UUID. * @param owner * @return The player's name, None, Everyone or Unknown */ public static String getName(UUID owner) { if (owner == null) { return C.NONE.s(); } if (owner.equals(DBFunc.everyone)) { return C.EVERYONE.s(); } String name = UUIDHandler.getName(owner); if (name == null) { return C.UNKNOWN.s(); } return name; } /** * Get the corner locations for a list of regions. * @see Plot#getCorners() * @param world * @param regions * @return */ public static Location[] getCorners(String world, Collection<RegionWrapper> regions) { Location min = null; Location max = null; for (RegionWrapper region : regions) { Location[] corners = region.getCorners(world); if (min == null) { min = corners[0]; max = corners[1]; continue; } Location pos1 = corners[0]; Location pos2 = corners[1]; if (pos2.getX() > max.getX()) { max.setX(pos2.getX()); } if (pos1.getX() < min.getX()) { min.setX(pos1.getX()); } if (pos2.getZ() > max.getZ()) { max.setZ(pos2.getZ()); } if (pos1.getZ() < min.getZ()) { min.setZ(pos1.getZ()); } } return new Location[]{min, max}; } /** * Fuzzy plot search with spaces separating terms. * - Terms: type, alias, world, owner, trusted, member * @param search * @return */ public static List<Plot> getPlotsBySearch(String search) { String[] split = search.split(" "); int size = split.length * 2; List<UUID> uuids = new ArrayList<>(); PlotId id = null; PlotArea area = null; String alias = null; for (String term : split) { try { UUID uuid = UUIDHandler.getUUID(term, null); if (uuid == null) { uuid = UUID.fromString(term); } uuids.add(uuid); } catch (Exception ignored) { id = PlotId.fromString(term); if (id != null) { continue; } area = PS.get().getPlotAreaByString(term); if (area == null) { alias = term; } } } ArrayList<ArrayList<Plot>> plotList = new ArrayList<>(size); for (int i = 0; i < size; i++) { plotList.add(new ArrayList<Plot>()); } for (Plot plot : PS.get().getPlots()) { int count = 0; if (!uuids.isEmpty()) { for (UUID uuid : uuids) { if (plot.isOwner(uuid)) { count += 2; } else if (plot.isAdded(uuid)) { count++; } } } if (id != null) { if (plot.getId().equals(id)) { count++; } } if (area != null && plot.getArea().equals(area)) { count++; } if (alias != null && alias.equals(plot.getAlias())) { count += 2; } if (count != 0) { plotList.get(count - 1).add(plot); } } List<Plot> plots = new ArrayList<>(); for (int i = plotList.size() - 1; i >= 0; i--) { if (!plotList.get(i).isEmpty()) { plots.addAll(plotList.get(i)); } } return plots; } /** * Get the plot from a string. * @param player Provides a context for what world to search in. Prefixing the term with 'world_name;' will override this context. * @param arg The search term * @param message If a message should be sent to the player if a plot cannot be found * @return The plot if only 1 result is found, or null */ public static Plot getPlotFromString(PlotPlayer player, String arg, boolean message) { if (arg == null) { if (player == null) { if (message) { PS.log(C.NOT_VALID_PLOT_WORLD); } return null; } return player.getCurrentPlot(); } PlotArea area; if (player != null) { area = PS.get().getPlotAreaByString(arg); if (area == null) { area = player.getApplicablePlotArea(); } } else { area = ConsolePlayer.getConsole().getApplicablePlotArea(); } String[] split = arg.split(";|,"); PlotId id; if (split.length == 4) { area = PS.get().getPlotAreaByString(split[0] + ';' + split[1]); id = PlotId.fromString(split[2] + ';' + split[3]); } else if (split.length == 3) { area = PS.get().getPlotAreaByString(split[0]); id = PlotId.fromString(split[1] + ';' + split[2]); } else if (split.length == 2) { id = PlotId.fromString(arg); } else { Collection<Plot> plots; if (area == null) { plots = PS.get().getPlots(); } else { plots = area.getPlots(); } for (Plot p : plots) { String name = p.getAlias(); if (!name.isEmpty() && StringMan.isEqualIgnoreCase(name, arg)) { return p; } } if (message) { MainUtil.sendMessage(player, C.NOT_VALID_PLOT_ID); } return null; } if (id == null) { if (message) { MainUtil.sendMessage(player, C.NOT_VALID_PLOT_ID); } return null; } if (area == null) { if (message) { MainUtil.sendMessage(player, C.NOT_VALID_PLOT_WORLD); } return null; } return area.getPlotAbs(id); } public static File getFile(File base, String path) { if (Paths.get(path).isAbsolute()) { return new File(path); } return new File(base, path); } /** * Synchronously set the biome in a selection. * @param world * @param p1x * @param p1z * @param p2x * @param p2z * @param biome */ public static void setBiome(String world, int p1x, int p1z, int p2x, int p2z, String biome) { RegionWrapper region = new RegionWrapper(p1x, p2x, p1z, p2z); WorldUtil.IMP.setBiomes(world, region, biome); } /** * Get the highest block at a location. * @param world * @param x * @param z * @return */ public static int getHeighestBlock(String world, int x, int z) { int result = WorldUtil.IMP.getHighestBlock(world, x, z); if (result == 0) { return 64; } return result; } /** * Send a message to the player. * * @param player Player to receive message * @param msg Message to send * * @return true Can be used in things such as commands (return PlayerFunctions.sendMessage(...)) */ public static boolean sendMessage(PlotPlayer player, String msg) { return sendMessage(player, msg, true); } /** * Send a message to console. * @param caption * @param args */ public static void sendConsoleMessage(C caption, String... args) { sendMessage(null, caption, args); } /** * Send a message to a player. * @param player Can be null to represent console, or use ConsolePlayer.getConsole() * @param msg * @param prefix If the message should be prefixed with the configured prefix * @return */ public static boolean sendMessage(PlotPlayer player, String msg, boolean prefix) { if (!msg.isEmpty()) { if (player == null) { String message = (prefix ? C.PREFIX.s() : "") + msg; PS.log(message); } else { player.sendMessage((prefix ? C.PREFIX.s() : "") + C.color(msg)); } } return true; } /** * Send a message to the player. * * @param player the recipient of the message * @param caption the message to send * * @return boolean success */ public static boolean sendMessage(PlotPlayer player, C caption, String... args) { return sendMessage(player, caption, (Object[]) args); } /** * Send a message to the player * * @param player the recipient of the message * @param caption the message to send * * @return boolean success */ public static boolean sendMessage(final PlotPlayer player, final C caption, final Object... args) { if (caption.s().isEmpty()) { return true; } TaskManager.runTaskAsync(new Runnable() { @Override public void run() { String m = C.format(caption, args); if (player == null) { PS.log(m); } else { player.sendMessage(m); } } }); return true; } /** * If rating categories are enabled, get the average rating by category.<br> * - The index corresponds to the index of the category in the config * @param plot * @return */ public static double[] getAverageRatings(Plot plot) { HashMap<UUID, Integer> rating; if (plot.getSettings().ratings != null) { rating = plot.getSettings().ratings; } else if (Settings.Enabled_Components.RATING_CACHE) { rating = new HashMap<>(); } else { rating = DBFunc.getRatings(plot); } int size = 1; if (!Settings.Ratings.CATEGORIES.isEmpty()) { size = Math.max(1, Settings.Ratings.CATEGORIES.size()); } double[] ratings = new double[size]; if (rating == null || rating.isEmpty()) { return ratings; } for (Entry<UUID, Integer> entry : rating.entrySet()) { int current = entry.getValue(); if (Settings.Ratings.CATEGORIES.isEmpty()) { ratings[0] += current; } else { for (int i = 0; i < Settings.Ratings.CATEGORIES.size(); i++) { ratings[i] += current % 10 - 1; current /= 10; } } } for (int i = 0; i < size; i++) { ratings[i] /= rating.size(); } return ratings; } public static Set<UUID> getUUIDsFromString(String list) { String[] split = list.split(","); HashSet<UUID> result = new HashSet<>(); for (String name : split) { if (name.isEmpty()) { // Invalid return Collections.emptySet(); } if ("*".equals(name)) { result.add(DBFunc.everyone); continue; } if (name.length() > 16) { try { result.add(UUID.fromString(name)); continue; } catch (IllegalArgumentException ignored) { return Collections.emptySet(); } } UUID uuid = UUIDHandler.getUUID(name, null); if (uuid == null) { return Collections.emptySet(); } result.add(uuid); } return result; } /** * Format a string with plot information. * @param info * @param plot * @param player * @param full * @param whenDone */ public static void format(String info, final Plot plot, PlotPlayer player, final boolean full, final RunnableVal<String> whenDone) { int num = plot.getConnectedPlots().size(); String alias = !plot.getAlias().isEmpty() ? plot.getAlias() : C.NONE.s(); Location bot = plot.getCorners()[0]; String biome = WorldUtil.IMP.getBiome(plot.getWorldName(), bot.getX(), bot.getZ()); String trusted = getPlayerList(plot.getTrusted()); String members = getPlayerList(plot.getMembers()); String denied = getPlayerList(plot.getDenied()); String seen; if (Settings.Enabled_Components.PLOT_EXPIRY && ExpireManager.IMP != null) { if (plot.isOnline()) { seen = C.NOW.s(); } else { int time = (int) (ExpireManager.IMP.getAge(plot) / 1000); if (time != 0) { seen = MainUtil.secToTime(time); } else { seen = C.UNKNOWN.s(); } } } else { seen = C.NEVER.s(); } Optional<String> descriptionFlag = plot.getFlag(Flags.DESCRIPTION); String description = !descriptionFlag.isPresent() ? C.NONE.s() : Flags.DESCRIPTION.valueToString(descriptionFlag.get()); StringBuilder flags = new StringBuilder(); HashMap<Flag<?>, Object> flagMap = FlagManager.getPlotFlags(plot.getArea(), plot.getSettings(), true); if (flagMap.isEmpty()) { flags.append(C.NONE.s()); } else { String prefix = ""; for (Entry<Flag<?>, Object> entry : flagMap.entrySet()) { flags.append(prefix).append(C.PLOT_FLAG_LIST.f(entry.getKey().getName(), entry.getValue())); prefix = ", "; } } boolean build = plot.isAdded(player.getUUID()); String owner = plot.getOwners().isEmpty() ? "unowned" : getPlayerList(plot.getOwners()); info = info.replace("%id%", plot.getId().toString()); info = info.replace("%alias%", alias); info = info.replace("%num%", String.valueOf(num)); info = info.replace("%desc%", description); info = info.replace("%biome%", biome); info = info.replace("%owner%", owner); info = info.replace("%members%", members); info = info.replace("%player%", player.getName()); info = info.replace("%trusted%", trusted); info = info.replace("%helpers%", members); info = info.replace("%denied%", denied); info = info.replace("%seen%", seen); info = info.replace("%flags%", flags); info = info.replace("%build%", String.valueOf(build)); info = info.replace("%desc%", "No description set."); if (info.contains("%rating%")) { final String newInfo = info; TaskManager.runTaskAsync(new Runnable() { @Override public void run() { int max = 10; if (Settings.Ratings.CATEGORIES != null && !Settings.Ratings.CATEGORIES.isEmpty()) { max = 8; } String info; if (full && Settings.Ratings.CATEGORIES != null && Settings.Ratings.CATEGORIES.size() > 1) { double[] ratings = MainUtil.getAverageRatings(plot); String rating = ""; String prefix = ""; for (int i = 0; i < ratings.length; i++) { rating += prefix + Settings.Ratings.CATEGORIES.get(i) + '=' + String.format("%.1f", ratings[i]); prefix = ","; } info = newInfo.replaceAll("%rating%", rating); } else { info = newInfo.replaceAll("%rating%", String.format("%.1f", plot.getAverageRating()) + '/' + max); } whenDone.run(info); } }); return; } whenDone.run(info); } public static boolean deleteDirectory(File directory) { if (directory.exists()) { File[] files = directory.listFiles(); if (null != files) { for (int i = 0; i < files.length; i++) { File file = files[i]; if (file.isDirectory()) { deleteDirectory(files[i]); } else { PS.debug("Deleting file: " + file + " | " + file.delete()); } } } } return (directory.delete()); } /** * Get a list of names given a list of uuids.<br> * - Uses the format {@link C#PLOT_USER_LIST} for the returned string * @param uuids * @return */ public static String getPlayerList(Collection<UUID> uuids) { ArrayList<UUID> l = new ArrayList<>(uuids); if (l.size() < 1) { return C.NONE.s(); } List<String> users = new ArrayList<>(); for (UUID u : l) { users.add(getName(u)); } Collections.sort(users); String c = C.PLOT_USER_LIST.s(); StringBuilder list = new StringBuilder(); for (int x = 0; x < users.size(); x++) { if (x + 1 == l.size()) { list.append(c.replace("%user%",users.get(x)).replace(",", "")); } else { list.append(c.replace("%user%", users.get(x))); } } return list.toString(); } public static void getPersistentMeta(UUID uuid, final String key, final RunnableVal<byte[]> result) { PlotPlayer player = UUIDHandler.getPlayer(uuid); if (player != null) { result.run(player.getPersistentMeta(key)); } else { DBFunc.getPersistentMeta(uuid, new RunnableVal<Map<String, byte[]>>() { @Override public void run(Map<String, byte[]> value) { result.run(value.get(key)); } }); } } }