diff --git a/src/main/java/ch/njol/skript/aliases/Aliases.java b/src/main/java/ch/njol/skript/aliases/Aliases.java index 56bffc24f9a..a11a6241650 100644 --- a/src/main/java/ch/njol/skript/aliases/Aliases.java +++ b/src/main/java/ch/njol/skript/aliases/Aliases.java @@ -25,7 +25,6 @@ import ch.njol.skript.config.Node; import ch.njol.skript.config.SectionNode; import ch.njol.skript.entity.EntityData; -import org.skriptlang.skript.lang.script.Script; import ch.njol.skript.lang.parser.ParserInstance; import ch.njol.skript.localization.ArgsMessage; import ch.njol.skript.localization.Language; @@ -39,7 +38,9 @@ import org.bukkit.ChatColor; import org.bukkit.Material; import org.bukkit.NamespacedKey; +import org.bukkit.inventory.meta.ItemMeta; import org.eclipse.jdt.annotation.Nullable; +import org.skriptlang.skript.lang.script.Script; import java.io.IOException; import java.io.UncheckedIOException; @@ -50,11 +51,13 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Arrays; import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; public abstract class Aliases { @@ -231,95 +234,132 @@ public static ItemType parseAlias(final String s) { private final static RegexMessage p_every = new RegexMessage("aliases.every", "", " (.+)", Pattern.CASE_INSENSITIVE); private final static RegexMessage p_of_every = new RegexMessage("aliases.of every", "(\\d+) ", " (.+)", Pattern.CASE_INSENSITIVE); private final static RegexMessage p_of = new RegexMessage("aliases.of", "(\\d+) (?:", " )?(.+)", Pattern.CASE_INSENSITIVE); - + private final static RegexMessage p_named = new RegexMessage("aliases.named", "(.+) ", " \"(.+)\"", Pattern.CASE_INSENSITIVE); + private final static RegexMessage p_with_lore = new RegexMessage("aliases.with lore", "(.+) ", " (\".+\")", Pattern.CASE_INSENSITIVE); + + private final static Pattern ENCHANTMENTS_PATTERN = Pattern.compile("\\s*(,|" + Pattern.quote(Language.get("and")) + ")\\s*"); + private final static Pattern MULTIPLE_VALUE_SPLIT_PATTERN = Pattern.compile("(, ?| " + Pattern.quote(Language.get("and")) + " )"); + + private final static Message m_named = new Message("aliases.named"); + private final static Message m_with_lore = new Message("aliases.with lore"); + /** * Parses an ItemType. *

* Prints errors. * - * @param s + * @param value * @return The parsed ItemType or null if the input is invalid. */ @Nullable - public static ItemType parseItemType(String s) { - if (s.isEmpty()) + public static ItemType parseItemType(String value) { + if (value.isEmpty()) return null; - s = "" + s.trim(); - - final ItemType t = new ItemType(); + value = "" + value.trim(); - Matcher m; - if ((m = p_of_every.matcher(s)).matches()) { - t.setAmount(Utils.parseInt("" + m.group(1))); - t.setAll(true); - s = "" + m.group(m.groupCount()); - } else if ((m = p_of.matcher(s)).matches()) { - t.setAmount(Utils.parseInt("" + m.group(1))); - s = "" + m.group(m.groupCount()); - } else if ((m = p_every.matcher(s)).matches()) { - t.setAll(true); - s = "" + m.group(m.groupCount()); + ItemType itemtype = new ItemType(); + + // Amount + Matcher matcher; + if ((matcher = p_of_every.matcher(value)).matches()) { + itemtype.setAmount(Utils.parseInt("" + matcher.group(1))); + itemtype.setAll(true); + value = "" + matcher.group(matcher.groupCount()); + } else if ((matcher = p_of.matcher(value)).matches()) { + itemtype.setAmount(Utils.parseInt("" + matcher.group(1))); + value = "" + matcher.group(matcher.groupCount()); + } else if ((matcher = p_every.matcher(value)).matches()) { + itemtype.setAll(true); + value = "" + matcher.group(matcher.groupCount()); } else { - final int l = s.length(); - s = Noun.stripIndefiniteArticle(s); - if (s.length() != l) // had indefinite article - t.setAmount(1); + int length = value.length(); + value = Noun.stripIndefiniteArticle(value); + if (value.length() != length) // had indefinite article + itemtype.setAmount(1); } - - String lc = s.toLowerCase(Locale.ENGLISH); - String of = Language.getSpaced("enchantments.of").toLowerCase(); - int c = -1; - outer: while ((c = lc.indexOf(of, c + 1)) != -1) { - ItemType t2 = t.clone(); + + String lowercase = value.toLowerCase(Locale.ENGLISH); + String of = Language.getSpaced("enchantments.of").toLowerCase(Locale.ENGLISH); + int character = -1; + + outer: while ((character = lowercase.indexOf(of, character + 1)) != -1) { + ItemType clonedItemtype = itemtype.clone(); try (BlockingLogHandler ignored = new BlockingLogHandler().start()) { - if (parseType("" + s.substring(0, c), t2, false) == null) + if (parseType("" + value.substring(0, character), clonedItemtype, false) == null) continue; } - if (t2.numTypes() == 0) + if (clonedItemtype.numTypes() == 0) continue; - String[] enchs = lc.substring(c + of.length()).split("\\s*(,|" + Pattern.quote(Language.get("and")) + ")\\s*"); - for (final String ench : enchs) { - EnchantmentType e = EnchantmentType.parse("" + ench); - if (e == null) + + boolean hasName = lowercase.contains(" " + m_named + " "); + boolean hasLore = lowercase.contains(" " + m_with_lore + " "); + + // Order of ItemType output matters here + // Order: AMOUNT TYPE ENCHANTMENTS NAME LORE + // e.g. Name is followed my Lore, so we need to check for name from its beginning to the beginning of `with lore` part + // otherwise the regex will include the lore as a name + + // Name + matcher = p_named.matcher(value.substring(character, hasLore ? lowercase.indexOf(" " + m_with_lore + " ") : lowercase.length())); + if (matcher.matches()) { + ItemMeta meta = clonedItemtype.getItemMeta(); + meta.setDisplayName(matcher.group(2)); + clonedItemtype.setItemMeta(meta); + } + + // Lore + matcher = p_with_lore.matcher(value); // since no more values are printed after the lore we don't need to substring it like name + if (matcher.matches()) { + ItemMeta meta = clonedItemtype.getItemMeta(); + meta.setLore(Arrays.stream(MULTIPLE_VALUE_SPLIT_PATTERN.split(matcher.group(2))).map(line -> line.substring(1, line.length() -1)).collect(Collectors.toList())); // strip quotes + clonedItemtype.setItemMeta(meta); + } + + // Substring to avoid including name and lore as enchantments + int endOfSubstring = hasName ? lowercase.indexOf(" " + m_named + " ") : hasLore ? lowercase.indexOf(" " + m_with_lore + " ") : lowercase.length(); + String[] enchantments = ENCHANTMENTS_PATTERN.split(lowercase.substring(character + of.length(), endOfSubstring)); + for (String enchantment : enchantments) { + EnchantmentType enchantmentType = EnchantmentType.parse(enchantment); + if (enchantmentType == null) continue outer; - t2.addEnchantments(e); + clonedItemtype.addEnchantments(enchantmentType); } - return t2; + return clonedItemtype; } - - if (parseType(s, t, false) == null) + + if (parseType(value, itemtype, false) == null) return null; - - if (t.numTypes() == 0) + + if (itemtype.numTypes() == 0) return null; - - return t; + + return itemtype; } /** * Prints errors. * - * @param s The string holding the type, can be either a number or an alias, plus an optional data part. Case does not matter. - * @param t The ItemType to add the parsed ItemData(s) to (i.e. this ItemType will be modified) + * @param value The string holding the type, can be either a number or an alias, plus an optional data part. Case does not matter. + * @param itemtype The ItemType to add the parsed ItemData(s) to (i.e. this ItemType will be modified) * @param isAlias Whether this type is parsed for an alias. * @return The given item type or null if the input couldn't be parsed. */ @Nullable - private static ItemType parseType(final String s, final ItemType t, final boolean isAlias) { - ItemType i; - if (s.isEmpty()) { - t.add(new ItemData(Material.AIR)); - return t; - } else if (s.matches("\\d+")) { + private static ItemType parseType(String value, ItemType itemtype, boolean isAlias) { + ItemType itemtypeCompare; + if (value.isEmpty()) { + itemtype.add(new ItemData(Material.AIR)); + return itemtype; + } else if (value.matches("\\d+")) { return null; - } else if ((i = getAlias(s)) != null) { - for (ItemData d : i) { - t.add(d.clone()); + } else if ((itemtypeCompare = getAlias(value)) != null) { + for (ItemData itemData : itemtypeCompare) { + itemtype.add(itemData.clone()); } - return t; + return itemtype; } if (isAlias) - Skript.error(m_invalid_item_type.toString(s)); + Skript.error(m_invalid_item_type.toString(value)); return null; } diff --git a/src/main/java/ch/njol/skript/aliases/ItemData.java b/src/main/java/ch/njol/skript/aliases/ItemData.java index 32323c57e6f..4d56fbfc906 100644 --- a/src/main/java/ch/njol/skript/aliases/ItemData.java +++ b/src/main/java/ch/njol/skript/aliases/ItemData.java @@ -23,7 +23,11 @@ import ch.njol.skript.bukkitutil.ItemUtils; import ch.njol.skript.bukkitutil.block.BlockCompat; import ch.njol.skript.bukkitutil.block.BlockValues; +import ch.njol.skript.localization.GeneralWords; +import ch.njol.skript.localization.Language; import ch.njol.skript.localization.Message; +import ch.njol.skript.util.EnchantmentType; +import ch.njol.skript.util.SkriptColor; import ch.njol.skript.variables.Variables; import ch.njol.yggdrasil.Fields; import ch.njol.yggdrasil.YggdrasilSerializable.YggdrasilExtendedSerializable; @@ -46,7 +50,9 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; +import java.util.Locale; import java.util.Map; +import java.util.Map.Entry; import java.util.Objects; import java.util.Set; @@ -82,7 +88,8 @@ public static class OldItemData { } private final static Message m_named = new Message("aliases.named"); - + private final static Message m_with_lore = new Message("aliases.with lore"); + /** * Before 1.13, data values ("block states") are applicable to items. * @@ -247,13 +254,55 @@ public String toString() { return toString(false, false); } - public String toString(final boolean debug, final boolean plural) { + public String toString(boolean debug, boolean plural) { StringBuilder builder = new StringBuilder(Aliases.getMaterialName(this, plural)); ItemMeta meta = stack.getItemMeta(); - if (meta != null && meta.hasDisplayName()) { - builder.append(" ").append(m_named).append(" "); - builder.append(meta.getDisplayName()); + + Map enchantments = stack.getEnchantments(); + if (!enchantments.isEmpty()) { + builder.append(Language.getSpaced("enchantments.of").toLowerCase(Locale.ENGLISH)); + int i = 0; + for (Entry entry : enchantments.entrySet()) { + if (i != 0) { + if (i != enchantments.size() - 1) { + builder.append(", "); + } else { + builder.append(" ").append(GeneralWords.and).append(" "); + } + } + Enchantment ench = entry.getKey(); + if (ench == null) + continue; + builder.append(EnchantmentType.toString(ench)); + builder.append(" "); + builder.append(entry.getValue()); + i++; + } } + + if (meta != null) { + if (meta.hasDisplayName()) { + builder.append(" ").append(m_named).append(" "); + builder.append("\"").append(SkriptColor.replaceColorChar(meta.getDisplayName())).append("\""); + } + if (meta.hasLore()) { + builder.append(" ").append(m_with_lore).append(" "); + List lore = meta.getLore(); + int i = 0; + for (String l : lore) { // hasLore handles NPE + if (i != 0) { + if (i != lore.size() - 1) { + builder.append(", "); + } else { + builder.append(" ").append(GeneralWords.and).append(" "); + } + } + builder.append("\"").append(SkriptColor.replaceColorChar(l)).append("\""); + i++; + } + } + } + return builder.toString(); } diff --git a/src/main/java/ch/njol/skript/aliases/ItemType.java b/src/main/java/ch/njol/skript/aliases/ItemType.java index 7341667fa91..09854a3290a 100644 --- a/src/main/java/ch/njol/skript/aliases/ItemType.java +++ b/src/main/java/ch/njol/skript/aliases/ItemType.java @@ -1049,16 +1049,6 @@ public String toString(final int flags, final @Nullable Adjective a) { private String toString(final boolean debug, final int flags, final @Nullable Adjective a) { final StringBuilder b = new StringBuilder(); -// if (types.size() == 1 && !types.get(0).hasDataRange()) { -// if (getAmount() != 1) -// b.append(amount + " "); -// if (isAll()) -// b.append(getAmount() == 1 ? "every " : "of every "); -// } else { -// if (getAmount() != 1) -// b.append(amount + " of "); -// b.append(isAll() ? "every " : "any "); -// } final boolean plural = amount != 1 && amount != -1 || (flags & Language.F_PLURAL) != 0; if (amount != -1 && amount != 1) { b.append(amount + " "); @@ -1076,36 +1066,7 @@ private String toString(final boolean debug, final int flags, final @Nullable Ad } b.append(types.get(i).toString(debug, plural)); } -// final Map enchs = enchantments; -// if (enchs == null) -// return "" + b.toString(); -// b.append(Language.getSpaced("enchantments.of")); -// int i = 0; -// for (final Entry e : enchs.entrySet()) { -// if (i != 0) { -// if (i != enchs.size() - 1) -// b.append(", "); -// else -// b.append(" " + GeneralWords.and + " "); -// } -// final Enchantment ench = e.getKey(); -// if (ench == null) -// continue; -// b.append(EnchantmentType.toString(ench)); -// b.append(" "); -// b.append(e.getValue()); -// i++; -// } -// if (meta != null) { -// final ItemMeta m = (ItemMeta) meta; -// if (m.hasDisplayName()) { -// b.append(" " + m_named.toString() + " "); -// b.append("\"" + m.getDisplayName() + "\""); -// } -// if (debug) -// b.append(" meta=[").append(meta).append("]"); -// } - return "" + b.toString(); + return b.toString(); } public static String toString(final ItemStack i) { diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang index 21bfe5f6092..800d28448a0 100644 --- a/src/main/resources/lang/default.lang +++ b/src/main/resources/lang/default.lang @@ -44,6 +44,7 @@ aliases: of: of(?: any)? named: named + with lore: with lore # -- Enchantments -- enchantments: diff --git a/src/test/skript/tests/regressions/4707-itemtype parsing.sk b/src/test/skript/tests/regressions/4707-itemtype parsing.sk new file mode 100644 index 00000000000..e7f9b08f6bd --- /dev/null +++ b/src/test/skript/tests/regressions/4707-itemtype parsing.sk @@ -0,0 +1,80 @@ +test "itemtype parsing": + + # -- ASSIGNMENTS + + # amount, enchantment, name, multi line lore + set {_i1} to 3 of stone pickaxe of power 1 and unbreaking 2 named "Hello ""World!" with lore "test1", "test2" and "test3%nl%tes""t4" + # amount, enchantment, name, single line lore + set {_i2} to 5 of stone of power 10 and efficiency 5 named "Hello World!" with lore "test1" + # amount, enchantment, name + set {_i3} to dirt of power 4 and unbreaking 5 and efficiency 6 named "&aGreen Color" + # amount, enchantment + set {_i4} to 20 of diamond pickaxe of power 1 and unbreaking 2 + + # name + set {_i5} to dirt named "Hello ""World!" + # multi line lore + set {_i6} to stone pickaxe with lore "test1", "test2" and "test3%nl%tes""t4" + # multi line lore, name (order) + set {_i7} to stone pickaxe of power 1 with lore "test1", "test2" and "test3%nl%tes""t4" named "Hello ""World!" + # plain + set {_i8} to stone pickaxe + set {_i9} to "%{_i1}%" parsed as itemtype + + + # -- CHECKS + + assert name of {_i1} is "Hello ""World!" with "Itemtype test ##1.1 failed" + assert size of lore of {_i1} is 4 with "Itemtype test ##1.2 failed" + assert size of enchantments of {_i1} is 2 with "Itemtype test ##1.3 failed" + assert itemstack size of {_i1} is 3 with "Itemtype test ##1.4 failed" + assert type of {_i1} is stone pickaxe with "Itemtype test ##1.5 failed" + + assert name of {_i2} is "Hello World!" with "Itemtype test ##2.1 failed" + assert lore of {_i2} is "test1" with "Itemtype test ##2.2 failed" + assert level of power of {_i2} is 10 with "Itemtype test ##2.3 failed" + assert itemstack size of {_i2} is 5 with "Itemtype test ##2.4 failed" + assert type of {_i2} is stone with "Itemtype test ##2.5 failed" + + assert name of {_i3} is "&aGreen Color" with "Itemtype test ##3.1 failed" + assert lore of {_i3} is not set with "Itemtype test ##3.2 failed" + assert size of lore of {_i3} is 0 with "Itemtype test ##3.3 failed" + assert enchantments of {_i3} contains power 4 and unbreaking 5 and efficiency 6 with "Itemtype test ##3.4 failed" + assert itemstack size of {_i3} is 1 with "Itemtype test ##3.5 failed" + assert type of {_i3} is dirt with "Itemtype test ##3.6 failed" + + assert name of {_i4} is not set with "Itemtype test ##4.1 failed" + assert lore of {_i4} is not set with "Itemtype test ##4.2 failed" + assert enchantments of {_i4} contains power 1 and unbreaking 2 with "Itemtype test ##4.3 failed" + assert itemstack size of {_i4} is 20 with "Itemtype test ##4.4 failed" + assert type of {_i4} is diamond pickaxe with "Itemtype test ##4.5 failed" + + assert name of {_i5} is "Hello ""World!" with "Itemtype test ##5.1 failed" + assert lore of {_i5} is not set with "Itemtype test ##5.2 failed" + assert enchantments of {_i5} is not set with "Itemtype test ##5.3 failed" + assert itemstack size of {_i5} is 1 with "Itemtype test ##5.4 failed" + assert type of {_i5} is dirt with "Itemtype test ##5.5 failed" + + assert name of {_i6} is not set with "Itemtype test ##6.1 failed" + assert 4th element of lore of {_i6} is "tes""t4" with "Itemtype test ##6.2 failed" + assert enchantments of {_i6} is not set with "Itemtype test ##6.3 failed" + assert itemstack size of {_i6} is 1 with "Itemtype test ##6.4 failed" + assert type of {_i6} is stone pickaxe with "Itemtype test ##6.5 failed" + + assert name of {_i7} is "Hello ""World!" with "Itemtype test ##7.1 failed" + assert size of lore of {_i7} is 4 with "Itemtype test ##7.2 failed" + assert size of enchantments of {_i7} is 1 with "Itemtype test ##7.3 failed" + assert itemstack size of {_i7} is 1 with "Itemtype test ##7.4 failed" + assert type of {_i7} is stone pickaxe with "Itemtype test ##7.5 failed" + + assert name of {_i8} is not set with "Itemtype test ##8.1 failed" + assert lore of {_i8} is not set with "Itemtype test ##8.2 failed" + assert enchantments of {_i8} is not set with "Itemtype test ##8.3 failed" + assert itemstack size of {_i8} is 1 with "Itemtype test ##8.4 failed" + assert type of {_i8} is stone pickaxe with "Itemtype test ##8.5 failed" + + assert name of {_i9} is "Hello ""World!" with "Itemtype test ##9.1 failed" + assert size of lore of {_i9} is 4 with "Itemtype test ##9.2 failed" + assert size of enchantments of {_i9} is 2 with "Itemtype test ##9.3 failed" + assert itemstack size of {_i9} is 3 with "Itemtype test ##9.4 failed" + assert type of {_i9} is stone pickaxe with "Itemtype test ##9.5 failed"