package com.earth2me.essentials; import com.earth2me.essentials.textreader.BookInput; import com.earth2me.essentials.textreader.BookPager; import com.earth2me.essentials.textreader.IText; import com.earth2me.essentials.utils.EnumUtil; import com.earth2me.essentials.utils.FormatUtil; import com.earth2me.essentials.utils.MaterialUtil; import com.earth2me.essentials.utils.NumberUtil; import com.google.common.base.Joiner; import net.ess3.api.IEssentials; import net.ess3.nms.refl.ReflUtil; import org.bukkit.Color; import org.bukkit.DyeColor; import org.bukkit.FireworkEffect; import org.bukkit.Material; import org.bukkit.block.Banner; import org.bukkit.block.banner.PatternType; import org.bukkit.enchantments.Enchantment; import org.bukkit.inventory.ItemFlag; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.BannerMeta; import org.bukkit.inventory.meta.BlockStateMeta; import org.bukkit.inventory.meta.BookMeta; import org.bukkit.inventory.meta.EnchantmentStorageMeta; import org.bukkit.inventory.meta.FireworkMeta; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.LeatherArmorMeta; import org.bukkit.inventory.meta.PotionMeta; import org.bukkit.inventory.meta.SkullMeta; import org.bukkit.potion.Potion; import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.logging.Level; import java.util.regex.Pattern; import static com.earth2me.essentials.I18n.tl; public class MetaItemStack { private static final Map colorMap = new HashMap<>(); private static final Map fireworkShape = new HashMap<>(); private static int bukkitUnbreakableSupport = -1; private static Method spigotMethod; private static Method setUnbreakableMethod; private static boolean useNewSkullMethod = true; static { for (final DyeColor color : DyeColor.values()) { colorMap.put(color.name(), color); } for (final FireworkEffect.Type type : FireworkEffect.Type.values()) { fireworkShape.put(type.name(), type); } } private final transient Pattern splitPattern = Pattern.compile("[:+',;.]"); private ItemStack stack; private FireworkEffect.Builder builder = FireworkEffect.builder(); private PotionEffectType pEffectType; private PotionEffect pEffect; private boolean validFirework = false; private boolean validPotionEffect = false; private boolean validPotionDuration = false; private boolean validPotionPower = false; private boolean isSplashPotion = false; private boolean completePotion = false; private int power = 1; private int duration = 120; public MetaItemStack(final ItemStack stack) { this.stack = stack.clone(); } private static void setSkullOwner(final IEssentials ess, final ItemStack stack, final String owner) { if (!(stack.getItemMeta() instanceof SkullMeta)) return; final SkullMeta meta = (SkullMeta) stack.getItemMeta(); if (useNewSkullMethod) { try { meta.setOwningPlayer(ess.getServer().getOfflinePlayer(owner)); stack.setItemMeta(meta); return; } catch (final NoSuchMethodError e) { useNewSkullMethod = false; } } meta.setOwner(owner); stack.setItemMeta(meta); } public ItemStack getItemStack() { return stack; } public boolean isValidFirework() { return validFirework; } public boolean isValidPotion() { return validPotionEffect && validPotionDuration && validPotionPower; } public FireworkEffect.Builder getFireworkBuilder() { return builder; } public PotionEffect getPotionEffect() { return pEffect; } public boolean completePotion() { return completePotion; } private void resetPotionMeta() { pEffect = null; pEffectType = null; validPotionEffect = false; validPotionDuration = false; validPotionPower = false; isSplashPotion = false; completePotion = true; } public boolean canSpawn(final IEssentials ess) { try { ess.getServer().getUnsafe().modifyItemStack(stack.clone(), "{}"); return true; } catch (final NoSuchMethodError nsme) { return true; } catch (final Throwable npe) { if (ess.getSettings().isDebug()) { ess.getLogger().log(Level.INFO, "Itemstack is invalid", npe); } return false; } } public void parseStringMeta(final CommandSource sender, final boolean allowUnsafe, final String[] string, final int fromArg, final IEssentials ess) throws Exception { if (string[fromArg].startsWith("{") && hasMetaPermission(sender, "vanilla", false, true, ess)) { try { stack = ess.getServer().getUnsafe().modifyItemStack(stack, Joiner.on(' ').join(Arrays.asList(string).subList(fromArg, string.length))); } catch (final NullPointerException npe) { if (ess.getSettings().isDebug()) { ess.getLogger().log(Level.INFO, "Itemstack is invalid", npe); } } catch (final NoSuchMethodError nsme) { throw new Exception(tl("noMetaJson"), nsme); } catch (final Throwable throwable) { throw new Exception(throwable.getMessage(), throwable); } } else { for (int i = fromArg; i < string.length; i++) { addStringMeta(sender, allowUnsafe, string[i], ess); } if (validFirework) { if (!hasMetaPermission(sender, "firework", true, true, ess)) { throw new Exception(tl("noMetaFirework")); } final FireworkEffect effect = builder.build(); final FireworkMeta fmeta = (FireworkMeta) stack.getItemMeta(); fmeta.addEffect(effect); if (fmeta.getEffects().size() > 1 && !hasMetaPermission(sender, "firework-multiple", true, true, ess)) { throw new Exception(tl("multipleCharges")); } stack.setItemMeta(fmeta); } } } public void addStringMeta(final CommandSource sender, final boolean allowUnsafe, final String string, final IEssentials ess) throws Exception { final String[] split = splitPattern.split(string, 2); if (split.length < 1) { return; } final Material WRITTEN_BOOK = EnumUtil.getMaterial("WRITTEN_BOOK"); if (split.length > 1 && split[0].equalsIgnoreCase("name") && hasMetaPermission(sender, "name", false, true, ess)) { final String displayName = FormatUtil.replaceFormat(split[1].replace('_', ' ')); final ItemMeta meta = stack.getItemMeta(); meta.setDisplayName(displayName); stack.setItemMeta(meta); } else if (split.length > 1 && (split[0].equalsIgnoreCase("lore") || split[0].equalsIgnoreCase("desc")) && hasMetaPermission(sender, "lore", false, true, ess)) { final List lore = new ArrayList<>(); for (final String line : split[1].split("(? 1 && (split[0].equalsIgnoreCase("player") || split[0].equalsIgnoreCase("owner")) && hasMetaPermission(sender, "head", false, true, ess)) { if (MaterialUtil.isPlayerHead(stack.getType(), stack.getDurability())) { final String owner = split[1]; setSkullOwner(ess, stack, owner); } else { throw new Exception(tl("onlyPlayerSkulls")); } } else if (split.length > 1 && split[0].equalsIgnoreCase("book") && stack.getType() == WRITTEN_BOOK && (hasMetaPermission(sender, "book", true, true, ess) || hasMetaPermission(sender, "chapter-" + split[1].toLowerCase(Locale.ENGLISH), true, true, ess))) { final BookMeta meta = (BookMeta) stack.getItemMeta(); final IText input = new BookInput("book", true, ess); final BookPager pager = new BookPager(input); final List pages = pager.getPages(split[1]); meta.setPages(pages); stack.setItemMeta(meta); } else if (split.length > 1 && split[0].equalsIgnoreCase("author") && stack.getType() == WRITTEN_BOOK && hasMetaPermission(sender, "author", false, true, ess)) { final String author = FormatUtil.replaceFormat(split[1]); final BookMeta meta = (BookMeta) stack.getItemMeta(); meta.setAuthor(author); stack.setItemMeta(meta); } else if (split.length > 1 && split[0].equalsIgnoreCase("title") && stack.getType() == WRITTEN_BOOK && hasMetaPermission(sender, "title", false, true, ess)) { final String title = FormatUtil.replaceFormat(split[1].replace('_', ' ')); final BookMeta meta = (BookMeta) stack.getItemMeta(); meta.setTitle(title); stack.setItemMeta(meta); } else if (split.length > 1 && split[0].equalsIgnoreCase("power") && MaterialUtil.isFirework(stack.getType()) && hasMetaPermission(sender, "firework-power", false, true, ess)) { final int power = NumberUtil.isInt(split[1]) ? Integer.parseInt(split[1]) : 0; final FireworkMeta meta = (FireworkMeta) stack.getItemMeta(); meta.setPower(power > 3 ? 4 : power); stack.setItemMeta(meta); } else if (split.length > 1 && split[0].equalsIgnoreCase("itemflags") && hasMetaPermission(sender, "itemflags", false, true, ess)) { addItemFlags(string); } else if (MaterialUtil.isFirework(stack.getType())) { //WARNING - Meta for fireworks will be ignored after this point. addFireworkMeta(sender, false, string, ess); } else if (MaterialUtil.isPotion(stack.getType())) { //WARNING - Meta for potions will be ignored after this point. addPotionMeta(sender, false, string, ess); } else if (MaterialUtil.isBanner(stack.getType())) { if (stack.getType().toString().equals("SHIELD") && Enchantments.getByName(split[0]) != null) { parseEnchantmentStrings(sender, allowUnsafe, split, ess); } else { //WARNING - Meta for banners will be ignored after this point. addBannerMeta(sender, false, string, ess); } } else if (split.length > 1 && (split[0].equalsIgnoreCase("color") || split[0].equalsIgnoreCase("colour")) && MaterialUtil.isLeatherArmor(stack.getType())) { final String[] color = split[1].split("(\\||,)"); if (color.length == 1 && (NumberUtil.isInt(color[0]) || color[0].startsWith("#"))) { // Either integer or hexadecimal final LeatherArmorMeta meta = (LeatherArmorMeta) stack.getItemMeta(); final String input = color[0]; if (input.startsWith("#")) { // Hex meta.setColor(Color.fromRGB( Integer.valueOf(input.substring(1, 3), 16), Integer.valueOf(input.substring(3, 5), 16), Integer.valueOf(input.substring(5, 7), 16))); } else { // Int meta.setColor(Color.fromRGB(Integer.parseInt(input))); } stack.setItemMeta(meta); } else if (color.length == 3) { // r,g,b final int red = NumberUtil.isInt(color[0]) ? Integer.parseInt(color[0]) : 0; final int green = NumberUtil.isInt(color[1]) ? Integer.parseInt(color[1]) : 0; final int blue = NumberUtil.isInt(color[2]) ? Integer.parseInt(color[2]) : 0; final LeatherArmorMeta meta = (LeatherArmorMeta) stack.getItemMeta(); meta.setColor(Color.fromRGB(red, green, blue)); stack.setItemMeta(meta); } else { throw new Exception(tl("leatherSyntax")); } } else { parseEnchantmentStrings(sender, allowUnsafe, split, ess); } } public void addItemFlags(final String string) throws Exception { final String[] separate = splitPattern.split(string, 2); if (separate.length != 2) { throw new Exception(tl("invalidItemFlagMeta", string)); } final String[] split = separate[1].split(","); final ItemMeta meta = stack.getItemMeta(); for (final String s : split) { for (final ItemFlag flag : ItemFlag.values()) { if (s.equalsIgnoreCase(flag.name())) { meta.addItemFlags(flag); } } } if (meta.getItemFlags().isEmpty()) { throw new Exception(tl("invalidItemFlagMeta", string)); } stack.setItemMeta(meta); } public void addFireworkMeta(final CommandSource sender, final boolean allowShortName, final String string, final IEssentials ess) throws Exception { if (MaterialUtil.isFirework(stack.getType())) { final String[] split = splitPattern.split(string, 2); if (split.length < 2) { return; } if (split[0].equalsIgnoreCase("color") || split[0].equalsIgnoreCase("colour") || (allowShortName && split[0].equalsIgnoreCase("c"))) { if (validFirework) { if (!hasMetaPermission(sender, "firework", true, true, ess)) { throw new Exception(tl("noMetaFirework")); } final FireworkEffect effect = builder.build(); final FireworkMeta fmeta = (FireworkMeta) stack.getItemMeta(); fmeta.addEffect(effect); if (fmeta.getEffects().size() > 1 && !hasMetaPermission(sender, "firework-multiple", true, true, ess)) { throw new Exception(tl("multipleCharges")); } stack.setItemMeta(fmeta); builder = FireworkEffect.builder(); } final List primaryColors = new ArrayList<>(); final String[] colors = split[1].split(","); for (final String color : colors) { if (colorMap.containsKey(color.toUpperCase())) { validFirework = true; primaryColors.add(colorMap.get(color.toUpperCase()).getFireworkColor()); } else { throw new Exception(tl("invalidFireworkFormat", split[1], split[0])); } } builder.withColor(primaryColors); } else if (split[0].equalsIgnoreCase("shape") || split[0].equalsIgnoreCase("type") || (allowShortName && (split[0].equalsIgnoreCase("s") || split[0].equalsIgnoreCase("t")))) { FireworkEffect.Type finalEffect = null; split[1] = split[1].equalsIgnoreCase("large") ? "BALL_LARGE" : split[1]; if (fireworkShape.containsKey(split[1].toUpperCase())) { finalEffect = fireworkShape.get(split[1].toUpperCase()); } else { throw new Exception(tl("invalidFireworkFormat", split[1], split[0])); } if (finalEffect != null) { builder.with(finalEffect); } } else if (split[0].equalsIgnoreCase("fade") || (allowShortName && split[0].equalsIgnoreCase("f"))) { final List fadeColors = new ArrayList<>(); final String[] colors = split[1].split(","); for (final String color : colors) { if (colorMap.containsKey(color.toUpperCase())) { fadeColors.add(colorMap.get(color.toUpperCase()).getFireworkColor()); } else { throw new Exception(tl("invalidFireworkFormat", split[1], split[0])); } } if (!fadeColors.isEmpty()) { builder.withFade(fadeColors); } } else if (split[0].equalsIgnoreCase("effect") || (allowShortName && split[0].equalsIgnoreCase("e"))) { final String[] effects = split[1].split(","); for (final String effect : effects) { if (effect.equalsIgnoreCase("twinkle")) { builder.flicker(true); } else if (effect.equalsIgnoreCase("trail")) { builder.trail(true); } else { throw new Exception(tl("invalidFireworkFormat", split[1], split[0])); } } } } } public void addPotionMeta(final CommandSource sender, final boolean allowShortName, final String string, final IEssentials ess) throws Exception { if (MaterialUtil.isPotion(stack.getType())) { final String[] split = splitPattern.split(string, 2); if (split.length < 2) { return; } if (split[0].equalsIgnoreCase("effect") || (allowShortName && split[0].equalsIgnoreCase("e"))) { pEffectType = Potions.getByName(split[1]); if (pEffectType != null && pEffectType.getName() != null) { if (hasMetaPermission(sender, "potions." + pEffectType.getName().toLowerCase(Locale.ENGLISH), true, false, ess)) { validPotionEffect = true; } else { throw new Exception(tl("noPotionEffectPerm", pEffectType.getName().toLowerCase(Locale.ENGLISH))); } } else { throw new Exception(tl("invalidPotionMeta", split[1])); } } else if (split[0].equalsIgnoreCase("power") || (allowShortName && split[0].equalsIgnoreCase("p"))) { if (NumberUtil.isInt(split[1])) { validPotionPower = true; power = Integer.parseInt(split[1]); if (power > 0 && power < 4) { power -= 1; } } else { throw new Exception(tl("invalidPotionMeta", split[1])); } } else if (split[0].equalsIgnoreCase("amplifier") || (allowShortName && split[0].equalsIgnoreCase("a"))) { if (NumberUtil.isInt(split[1])) { validPotionPower = true; power = Integer.parseInt(split[1]); } else { throw new Exception(tl("invalidPotionMeta", split[1])); } } else if (split[0].equalsIgnoreCase("duration") || (allowShortName && split[0].equalsIgnoreCase("d"))) { if (NumberUtil.isInt(split[1])) { validPotionDuration = true; duration = Integer.parseInt(split[1]) * 20; //Duration is in ticks by default, converted to seconds } else { throw new Exception(tl("invalidPotionMeta", split[1])); } } else if (split[0].equalsIgnoreCase("splash") || (allowShortName && split[0].equalsIgnoreCase("s"))) { isSplashPotion = Boolean.parseBoolean(split[1]); } if (isValidPotion()) { final PotionMeta pmeta = (PotionMeta) stack.getItemMeta(); pEffect = pEffectType.createEffect(duration, power); if (pmeta.getCustomEffects().size() > 1 && !hasMetaPermission(sender, "potions.multiple", true, false, ess)) { throw new Exception(tl("multiplePotionEffects")); } pmeta.addCustomEffect(pEffect, true); stack.setItemMeta(pmeta); if (ReflUtil.getNmsVersionObject().isHigherThanOrEqualTo(ReflUtil.V1_9_R1)) { if (isSplashPotion && stack.getType() != Material.SPLASH_POTION) { stack.setType(Material.SPLASH_POTION); } else if (!isSplashPotion && stack.getType() != Material.POTION) { stack.setType(Material.POTION); } } else { final Potion potion = Potion.fromItemStack(stack); potion.setSplash(isSplashPotion); potion.apply(stack); } resetPotionMeta(); } } } private void parseEnchantmentStrings(final CommandSource sender, final boolean allowUnsafe, final String[] split, final IEssentials ess) throws Exception { final Enchantment enchantment = Enchantments.getByName(split[0]); if (enchantment == null || !hasMetaPermission(sender, "enchantments." + enchantment.getName().toLowerCase(Locale.ENGLISH), false, false, ess)) { return; } int level = -1; if (split.length > 1) { try { level = Integer.parseInt(split[1]); } catch (final NumberFormatException ex) { level = -1; } } if (level < 0 || (!allowUnsafe && level > enchantment.getMaxLevel())) { level = enchantment.getMaxLevel(); } addEnchantment(sender, allowUnsafe, enchantment, level); } public void addEnchantment(final CommandSource sender, final boolean allowUnsafe, final Enchantment enchantment, final int level) throws Exception { if (enchantment == null) { throw new Exception(tl("enchantmentNotFound")); } try { if (stack.getType().equals(Material.ENCHANTED_BOOK)) { final EnchantmentStorageMeta meta = (EnchantmentStorageMeta) stack.getItemMeta(); if (level == 0) { meta.removeStoredEnchant(enchantment); } else { meta.addStoredEnchant(enchantment, level, allowUnsafe); } stack.setItemMeta(meta); } else { // all other material types besides ENCHANTED_BOOK if (level == 0) { stack.removeEnchantment(enchantment); } else { if (allowUnsafe) { stack.addUnsafeEnchantment(enchantment, level); } else { stack.addEnchantment(enchantment, level); } } } } catch (final Exception ex) { throw new Exception("Enchantment " + enchantment.getName() + ": " + ex.getMessage(), ex); } } public Enchantment getEnchantment(final User user, final String name) throws Exception { final Enchantment enchantment = Enchantments.getByName(name); if (enchantment == null) { return null; } final String enchantmentName = enchantment.getName().toLowerCase(Locale.ENGLISH); if (!hasMetaPermission(user, "enchantments." + enchantmentName, true, false)) { throw new Exception(tl("enchantmentPerm", enchantmentName)); } return enchantment; } public void addBannerMeta(final CommandSource sender, final boolean allowShortName, final String string, final IEssentials ess) throws Exception { if (MaterialUtil.isBanner(stack.getType()) && !stack.getType().toString().equals("SHIELD") && string != null) { final String[] split = splitPattern.split(string, 2); if (split.length < 2) { throw new Exception(tl("invalidBanner", split[1])); } PatternType patternType = null; try { patternType = PatternType.valueOf(split[0]); } catch (final Exception ignored) { } final BannerMeta meta = (BannerMeta) stack.getItemMeta(); if (split[0].equalsIgnoreCase("basecolor")) { final Color color = Color.fromRGB(Integer.parseInt(split[1])); meta.setBaseColor(DyeColor.getByColor(color)); } else if (patternType != null) { final PatternType type = PatternType.valueOf(split[0]); final DyeColor color = DyeColor.getByColor(Color.fromRGB(Integer.parseInt(split[1]))); final org.bukkit.block.banner.Pattern pattern = new org.bukkit.block.banner.Pattern(color, type); meta.addPattern(pattern); } stack.setItemMeta(meta); } else if (stack.getType().toString().equals("SHIELD") && string != null) { final String[] split = splitPattern.split(string, 2); if (split.length < 2) { throw new Exception(tl("invalidBanner", split[1])); } PatternType patternType = null; try { patternType = PatternType.valueOf(split[0]); } catch (final Exception ignored) { } // Hacky fix for accessing Shield meta - https://github.com/drtshock/Essentials/pull/745#issuecomment-234843795 final BlockStateMeta meta = (BlockStateMeta) stack.getItemMeta(); final Banner banner = (Banner) meta.getBlockState(); if (split[0].equalsIgnoreCase("basecolor")) { final Color color = Color.fromRGB(Integer.parseInt(split[1])); banner.setBaseColor(DyeColor.getByColor(color)); } else if (patternType != null) { final PatternType type = PatternType.valueOf(split[0]); final DyeColor color = DyeColor.getByColor(Color.fromRGB(Integer.parseInt(split[1]))); final org.bukkit.block.banner.Pattern pattern = new org.bukkit.block.banner.Pattern(color, type); banner.addPattern(pattern); } banner.update(); meta.setBlockState(banner); stack.setItemMeta(meta); } } private boolean hasMetaPermission(final CommandSource sender, final String metaPerm, final boolean graceful, final boolean includeBase, final IEssentials ess) throws Exception { final User user = sender != null && sender.isPlayer() ? ess.getUser(sender.getPlayer()) : null; return hasMetaPermission(user, metaPerm, graceful, includeBase); } private boolean hasMetaPermission(final User user, final String metaPerm, final boolean graceful, final boolean includeBase) throws Exception { final String permBase = includeBase ? "essentials.itemspawn.meta-" : "essentials."; if (user == null || user.isAuthorized(permBase + metaPerm)) { return true; } if (graceful) { return false; } else { throw new Exception(tl("noMetaPerm", metaPerm)); } } private void setUnbreakable(final ItemStack is, final boolean unbreakable) { final ItemMeta meta = is.getItemMeta(); try { if (bukkitUnbreakableSupport == -1) { try { ItemMeta.class.getDeclaredMethod("setUnbreakable", boolean.class); bukkitUnbreakableSupport = 1; } catch (final NoSuchMethodException | SecurityException ex) { bukkitUnbreakableSupport = 0; } } if (bukkitUnbreakableSupport == 1) { meta.setUnbreakable(unbreakable); } else { if (spigotMethod == null) { spigotMethod = meta.getClass().getDeclaredMethod("spigot"); spigotMethod.setAccessible(true); } final Object itemStackSpigot = spigotMethod.invoke(meta); if (setUnbreakableMethod == null) { setUnbreakableMethod = itemStackSpigot.getClass().getDeclaredMethod("setUnbreakable", Boolean.TYPE); setUnbreakableMethod.setAccessible(true); } setUnbreakableMethod.invoke(itemStackSpigot, unbreakable); } is.setItemMeta(meta); } catch (final Throwable t) { t.printStackTrace(); } } }