/* * This file is part of Skript. * * Skript is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Skript 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Skript. If not, see <http://www.gnu.org/licenses/>. * * * Copyright 2011-2014 Peter Güttinger * */ package ch.njol.skript.util; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.bukkit.ChatColor; import org.bukkit.entity.Creature; import org.bukkit.entity.Entity; import org.bukkit.entity.LivingEntity; import org.bukkit.inventory.ItemStack; import org.bukkit.util.Vector; import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.aliases.ItemType; import ch.njol.skript.effects.EffTeleport; import ch.njol.skript.entity.EntityData; import ch.njol.skript.localization.Language; import ch.njol.skript.localization.LanguageChangeListener; import ch.njol.skript.registrations.Classes; import ch.njol.util.Callback; import ch.njol.util.NonNullPair; import ch.njol.util.Pair; import ch.njol.util.StringUtils; /** * Utility class. * * @author Peter Güttinger */ public abstract class Utils { private Utils() {} public final static Random random = new Random(); public static String join(final Object[] objects) { assert objects != null; final StringBuilder b = new StringBuilder(); for (int i = 0; i < objects.length; i++) { if (i != 0) b.append(", "); b.append(Classes.toString(objects[i])); } return "" + b.toString(); } public static String join(final Iterable<?> objects) { assert objects != null; final StringBuilder b = new StringBuilder(); boolean first = true; for (final Object o : objects) { if (!first) b.append(", "); else first = false; b.append(Classes.toString(o)); } return "" + b.toString(); } /** * Tests whether two item stacks are of the same type, i.e. it ignores the amounts. * * @param is1 * @param is2 * @return Whether the item stacks are of the same type */ public static boolean itemStacksEqual(final @Nullable ItemStack is1, final @Nullable ItemStack is2) { if (is1 == null || is2 == null) return is1 == is2; return is1.getType() == is2.getType() && is1.getDurability() == is2.getDurability() && (ItemType.itemMetaSupported ? is1.getItemMeta().equals(is2.getItemMeta()) : is1.getEnchantments().equals(is2.getEnchantments())); } /** * Gets an entity's target. * * @param entity The entity to get the target of * @param type Can be null for any entity * @return The entity's target */ @SuppressWarnings("unchecked") @Nullable public static <T extends Entity> T getTarget(final LivingEntity entity, @Nullable final EntityData<T> type) { if (entity instanceof Creature) { return ((Creature) entity).getTarget() == null || type != null && !type.isInstance(((Creature) entity).getTarget()) ? null : (T) ((Creature) entity).getTarget(); } T target = null; double targetDistanceSquared = 0; final double radiusSquared = 1; final Vector l = entity.getEyeLocation().toVector(), n = entity.getLocation().getDirection().normalize(); final double cos45 = Math.cos(Math.PI / 4); for (final T other : type == null ? (List<T>) entity.getWorld().getEntities() : entity.getWorld().getEntitiesByClass(type.getType())) { if (other == null || other == entity || type != null && !type.isInstance(other)) continue; if (target == null || targetDistanceSquared > other.getLocation().distanceSquared(entity.getLocation())) { final Vector t = other.getLocation().add(0, 1, 0).toVector().subtract(l); if (n.clone().crossProduct(t).lengthSquared() < radiusSquared && t.normalize().dot(n) >= cos45) { target = other; targetDistanceSquared = target.getLocation().distanceSquared(entity.getLocation()); } } } return target; } public final static Pair<String, Integer> getAmount(final String s) { if (s.matches("\\d+ of .+")) { return new Pair<String, Integer>(s.split(" ", 3)[2], Utils.parseInt("" + s.split(" ", 2)[0])); } else if (s.matches("\\d+ .+")) { return new Pair<String, Integer>(s.split(" ", 2)[1], Utils.parseInt("" + s.split(" ", 2)[0])); } else if (s.matches("an? .+")) { return new Pair<String, Integer>(s.split(" ", 2)[1], 1); } return new Pair<String, Integer>(s, Integer.valueOf(-1)); } // public final static class AmountResponse { // public final String s; // public final int amount; // public final boolean every; // // public AmountResponse(final String s, final int amount, final boolean every) { // this.s = s; // this.amount = amount; // this.every = every; // } // // public AmountResponse(final String s, final boolean every) { // this.s = s; // amount = -1; // this.every = every; // } // // public AmountResponse(final String s, final int amount) { // this.s = s; // this.amount = amount; // every = false; // } // // public AmountResponse(final String s) { // this.s = s; // amount = -1; // every = false; // } // } // // public final static AmountResponse getAmountWithEvery(final String s) { // if (s.matches("\\d+ of (all|every) .+")) { // return new AmountResponse("" + s.split(" ", 4)[3], Utils.parseInt("" + s.split(" ", 2)[0]), true); // } else if (s.matches("\\d+ of .+")) { // return new AmountResponse("" + s.split(" ", 3)[2], Utils.parseInt("" + s.split(" ", 2)[0])); // } else if (s.matches("\\d+ .+")) { // return new AmountResponse("" + s.split(" ", 2)[1], Utils.parseInt("" + s.split(" ", 2)[0])); // } else if (s.matches("an? .+")) { // return new AmountResponse("" + s.split(" ", 2)[1], 1); // } else if (s.matches("(all|every) .+")) { // return new AmountResponse("" + s.split(" ", 2)[1], true); // } // return new AmountResponse(s); // } private final static String[][] plurals = { {"fe", "ves"},// most -f words' plurals can end in -fs as well as -ves {"axe", "axes"}, {"x", "xes"}, {"ay", "ays"}, {"ey", "eys"}, {"iy", "iys"}, {"oy", "oys"}, {"uy", "uys"}, {"kie", "kies"}, {"zombie", "zombies"}, {"y", "ies"}, {"h", "hes"}, {"man", "men"}, {"us", "i"}, {"hoe", "hoes"}, {"toe", "toes"}, {"o", "oes"}, {"alias", "aliases"}, {"gas", "gases"}, {"child", "children"}, {"sheep", "sheep"}, // general ending {"", "s"}, }; /** * @param s trimmed string * @return Pair of singular string + boolean whether it was plural */ @SuppressWarnings("null") public final static NonNullPair<String, Boolean> getEnglishPlural(final String s) { assert s != null; if (s.isEmpty()) return new NonNullPair<String, Boolean>("", Boolean.FALSE); for (final String[] p : plurals) { if (s.endsWith(p[1])) return new NonNullPair<String, Boolean>(s.substring(0, s.length() - p[1].length()) + p[0], Boolean.TRUE); if (s.endsWith(p[1].toUpperCase())) return new NonNullPair<String, Boolean>(s.substring(0, s.length() - p[1].length()) + p[0].toUpperCase(), Boolean.TRUE); } return new NonNullPair<String, Boolean>(s, Boolean.FALSE); } /** * Gets the english plural of a word. * * @param s * @return The english plural of the given word */ public final static String toEnglishPlural(final String s) { assert s != null && s.length() != 0; for (final String[] p : plurals) { if (s.endsWith(p[0])) return s.substring(0, s.length() - p[0].length()) + p[1]; } assert false; return s + "s"; } /** * Gets the plural of a word (or not if p is false) * * @param s * @param p * @return The english plural of the given word, or the word itself if p is false. */ public final static String toEnglishPlural(final String s, final boolean p) { if (p) return toEnglishPlural(s); return s; } /** * Adds 'a' or 'an' to the given string, depending on the first character of the string. * * @param s The string to add the article to * @return The given string with an appended a/an and a space at the beginning * @see #A(String) * @see #a(String, boolean) */ public final static String a(final String s) { return a(s, false); } /** * Adds 'A' or 'An' to the given string, depending on the first character of the string. * * @param s The string to add the article to * @return The given string with an appended A/An and a space at the beginning * @see #a(String) * @see #a(String, boolean) */ public final static String A(final String s) { return a(s, true); } /** * Adds 'a' or 'an' to the given string, depending on the first character of the string. * * @param s The string to add the article to * @param capA Whether to use a capital a or not * @return The given string with an appended a/an (or A/An if capA is true) and a space at the beginning * @see #a(String) */ public final static String a(final String s, final boolean capA) { assert s != null && s.length() != 0; if ("aeiouAEIOU".indexOf(s.charAt(0)) != -1) { if (capA) return "An " + s; return "an " + s; } else { if (capA) return "A " + s; return "a " + s; } } /** * Gets the collision height of solid or partially-solid blocks at the center of the block. This is mostly for use in the {@link EffTeleport teleport effect}. * <p> * TODO !Update with every version [blocks] * * @param type * @return The block's height at the center */ public static double getBlockHeight(final int type, final byte data) { switch (type) { case 26: // bed return 9. / 16; case 44: // slabs case 126: return (data & 0x8) == 0 ? 0.5 : 1; case 78: // snow layer return data == 0 ? 1 : (data % 8) * 2. / 16; case 85: // fences & gates case 107: case 113: case 139: // cobblestone wall return 1.5; case 88: // soul sand return 14. / 16; case 92: // cake return 7. / 16; case 93: // redstone repeater case 94: case 149: // redstone comparator case 150: return 2. / 16; case 96: // trapdoor return (data & 0x4) == 0 ? ((data & 0x8) == 0 ? 3. / 16 : 1) : 0; case 116: // enchantment table return 12. / 16; case 117: // brewing stand return 14. / 16; case 118: // cauldron return 5. / 16; case 120: // end portal frame return (data & 0x4) == 0 ? 13. / 16 : 1; case 127: // cocoa plant return 12. / 16; case 140: // flower pot return 6. / 16; case 144: // mob head return 0.5; case 151: // daylight sensor return 6. / 16; case 154: // hopper return 10. / 16; default: return 1; } } final static ChatColor[] styles = {ChatColor.BOLD, ChatColor.ITALIC, ChatColor.STRIKETHROUGH, ChatColor.UNDERLINE, ChatColor.MAGIC, ChatColor.RESET}; final static Map<String, String> chat = new HashMap<String, String>(); final static Map<String, String> englishChat = new HashMap<String, String>(); static { Language.addListener(new LanguageChangeListener() { @Override public void onLanguageChange() { final boolean english = englishChat.isEmpty(); chat.clear(); for (final ChatColor style : styles) { for (final String s : Language.getList("chat styles." + style.name())) { chat.put(s.toLowerCase(), style.toString()); if (english) englishChat.put(s.toLowerCase(), style.toString()); } } } }); } @Nullable public final static String getChatStyle(final String s) { final Color c = Color.byName(s); if (c != null) return c.getChat(); return chat.get(s); } @SuppressWarnings("null") private final static Pattern stylePattern = Pattern.compile("<([^<>]+)>"); /** * Replaces <chat styles> in the message * * @param message * @return message with localised chat styles converted to Minecraft's format */ public final static String replaceChatStyles(final String message) { if (message.isEmpty()) return message; String m = StringUtils.replaceAll("" + message.replace("<<none>>", ""), stylePattern, new Callback<String, Matcher>() { @Override public String run(final Matcher m) { final Color c = Color.byName("" + m.group(1)); if (c != null) return c.getChat(); final String f = chat.get(m.group(1).toLowerCase()); if (f != null) return f; return "" + m.group(); } }); assert m != null; m = ChatColor.translateAlternateColorCodes('&', "" + m); return "" + m; } /** * Replaces english <chat styles> in the message. This is used for messages in the language file as the language of colour codes is not well defined while the language is * changing, and for some hardcoded messages. * * @param message * @return message with english chat styles converted to Minecraft's format */ public final static String replaceEnglishChatStyles(final String message) { if (message.isEmpty()) return message; String m = StringUtils.replaceAll(message, stylePattern, new Callback<String, Matcher>() { @Override public String run(final Matcher m) { final Color c = Color.byEnglishName("" + m.group(1)); if (c != null) return c.getChat(); final String f = englishChat.get(m.group(1).toLowerCase()); if (f != null) return f; return "" + m.group(); } }); assert m != null; m = ChatColor.translateAlternateColorCodes('&', "" + m); return "" + m; } /** * Gets a random value between <tt>start</tt> (inclusive) and <tt>end</tt> (exclusive) * * @param start * @param end * @return <tt>start + random.nextInt(end - start)</tt> */ public static int random(final int start, final int end) { if (end <= start) throw new IllegalArgumentException("end (" + end + ") must be > start (" + start + ")"); return start + random.nextInt(end - start); } // TODO improve public final static Class<?> getSuperType(final Class<?>... cs) { assert cs.length > 0; Class<?> r = cs[0]; assert r != null; outer: for (final Class<?> c : cs) { assert c != null && !c.isArray() && !c.isPrimitive() : c; if (c.isAssignableFrom(r)) { r = c; continue; } if (!r.isAssignableFrom(c)) { Class<?> s = c; while ((s = s.getSuperclass()) != null) { if (s != Object.class && s.isAssignableFrom(r)) { r = s; continue outer; } } for (final Class<?> i : c.getInterfaces()) { s = getSuperType(i, r); if (s != Object.class) { r = s; continue outer; } } return Object.class; } } return r; } /** * Parses a number that was validated to be an integer but might still result in a {@link NumberFormatException} when parsed with {@link Integer#parseInt(String)} due to * overflow. * This method will return {@link Integer#MIN_VALUE} or {@link Integer#MAX_VALUE} respectively if that happens. * * @param s * @return The parsed integer, {@link Integer#MIN_VALUE} or {@link Integer#MAX_VALUE} respectively */ public final static int parseInt(final String s) { assert s.matches("-?\\d+"); try { return Integer.parseInt(s); } catch (final NumberFormatException e) { return s.startsWith("-") ? Integer.MIN_VALUE : Integer.MAX_VALUE; } } /** * Parses a number that was validated to be an integer but might still result in a {@link NumberFormatException} when parsed with {@link Long#parseLong(String)} due to * overflow. * This method will return {@link Long#MIN_VALUE} or {@link Long#MAX_VALUE} respectively if that happens. * * @param s * @return The parsed long, {@link Long#MIN_VALUE} or {@link Long#MAX_VALUE} respectively */ public final static long parseLong(final String s) { assert s.matches("-?\\d+"); try { return Long.parseLong(s); } catch (final NumberFormatException e) { return s.startsWith("-") ? Long.MIN_VALUE : Long.MAX_VALUE; } } }