diff --git a/src/main/java/ch/njol/skript/aliases/Aliases.java b/src/main/java/ch/njol/skript/aliases/Aliases.java index 3e67971698a..b92a4196f84 100644 --- a/src/main/java/ch/njol/skript/aliases/Aliases.java +++ b/src/main/java/ch/njol/skript/aliases/Aliases.java @@ -1,21 +1,3 @@ -/** - * 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 . - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ package ch.njol.skript.aliases; import ch.njol.skript.Skript; @@ -25,8 +7,6 @@ import ch.njol.skript.config.Node; import ch.njol.skript.config.SectionNode; import ch.njol.skript.entity.EntityData; -import org.bukkit.entity.EntityType; -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; @@ -37,11 +17,6 @@ import ch.njol.skript.util.EnchantmentType; import ch.njol.skript.util.Utils; import ch.njol.skript.util.Version; -import org.bukkit.ChatColor; -import org.bukkit.Material; -import org.bukkit.NamespacedKey; -import org.jetbrains.annotations.Nullable; - import java.io.IOException; import java.io.UncheckedIOException; import java.net.URI; @@ -56,14 +31,53 @@ import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.lang.script.Script; public abstract class Aliases { - static final boolean USING_ITEM_COMPONENTS = Skript.isRunningMinecraft(1, 20, 5); private static final AliasesProvider provider = createProvider(10000, null); private static final AliasesParser parser = createParser(provider); - + // this is not an alias! + private final static ItemType everything = new ItemType(); + private final static Message m_empty_string = new Message("aliases.empty string"); + private final static ArgsMessage m_invalid_item_type = new ArgsMessage("aliases.invalid item type"); + private final static Message m_outside_section = new Message("aliases.outside section"); + private final static RegexMessage p_any = new RegexMessage("aliases.any", "", " (.+)", Pattern.CASE_INSENSITIVE); + 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); + + /** + * Go through these whenever aliases are reloaded, and update them. + */ + private static final Map trackedTypes = new HashMap<>(); + + /** + * If user had an obscure config option set, don't crash due to missing + * Java item types. + */ + private static final boolean noHardExceptions = SkriptConfig.apiSoftExceptions.value(); + static String itemSingular = "item"; + static String itemPlural = "items"; + @Nullable + static String itemGender = null; + static String blockSingular = "block"; + static String blockPlural = "blocks"; + @Nullable + static String blockGender = null; + + static { + everything.setAll(true); + ItemData all = new ItemData(Material.AIR); + all.isAnything = true; + everything.add(all); + } + @Nullable private static ItemType getAlias_i(final String s) { // Check script aliases first @@ -71,10 +85,10 @@ private static ItemType getAlias_i(final String s) { if (aliases != null) { return aliases.provider.getAlias(s); // Delegates to global provider if needed } - + return provider.getAlias(s); } - + /** * Creates an aliases provider with Skript's default configuration. * @param expectedCount Expected alias count. @@ -84,14 +98,14 @@ private static ItemType getAlias_i(final String s) { private static AliasesProvider createProvider(int expectedCount, @Nullable AliasesProvider parent) { return new AliasesProvider(expectedCount, parent); } - + /** * Creates an aliases parser with Skript's default configuration. * @return Aliases parser. */ private static AliasesParser createParser(AliasesProvider provider) { AliasesParser parser = new AliasesParser(provider); - + // Register standard conditions parser.registerCondition("minecraft version", (str) -> { int orNewer = str.indexOf("or newer"); // For example: 1.12 or newer @@ -100,14 +114,14 @@ private static AliasesParser createParser(AliasesProvider provider) { Version ver = new Version(str.substring(0, orNewer - 1)); return Skript.getMinecraftVersion().compareTo(ver) >= 0; } - + int orOlder = str.indexOf("or older"); // For example: 1.11 or older if (orOlder != -1) { @SuppressWarnings("null") Version ver = new Version(str.substring(0, orOlder - 1)); return Skript.getMinecraftVersion().compareTo(ver) <= 0; } - + int to = str.indexOf("to"); // For example: 1.11 to 1.12 if (to != -1) { @SuppressWarnings("null") @@ -117,39 +131,17 @@ private static AliasesParser createParser(AliasesProvider provider) { Version current = Skript.getMinecraftVersion(); return current.compareTo(first) >= 0 && current.compareTo(second) <= 0; } - + return Skript.getMinecraftVersion().equals(new Version(str)); }); - + return parser; } - static String itemSingular = "item"; - static String itemPlural = "items"; - @Nullable - static String itemGender = null; - static String blockSingular = "block"; - static String blockPlural = "blocks"; - @Nullable - static String blockGender = null; - - // this is not an alias! - private final static ItemType everything = new ItemType(); - static { - everything.setAll(true); - ItemData all = new ItemData(Material.AIR); - all.isAnything = true; - everything.add(all); - } - - private final static Message m_empty_string = new Message("aliases.empty string"); - private final static ArgsMessage m_invalid_item_type = new ArgsMessage("aliases.invalid item type"); - private final static Message m_outside_section = new Message("aliases.outside section"); - /** * Concatenates parts of an alias's name. This currently 'lowercases' the first character of any part if there's no space in front of it. It also replaces double spaces with a * single one and trims the resulting string. - * + * * @param parts */ static String concatenate(final String... parts) { @@ -171,7 +163,7 @@ static String concatenate(final String... parts) { } return "" + b.toString().replace(" ", " ").trim(); } - + @Nullable private static MaterialName getMaterialNameData(ItemData type) { // Check script aliases first @@ -179,11 +171,11 @@ private static MaterialName getMaterialNameData(ItemData type) { if (aliases != null) { return aliases.provider.getMaterialName(type); } - + // Then global aliases return provider.getMaterialName(type); } - + public static String getMaterialName(ItemData type, boolean plural) { MaterialName name = getMaterialNameData(type); if (name == null) { @@ -191,7 +183,7 @@ public static String getMaterialName(ItemData type, boolean plural) { } return name.toString(plural); } - + /** * @return The item's gender or -1 if no name is found */ @@ -201,10 +193,10 @@ public static int getGender(ItemData item) { return n.gender; return -1; } - + /** * Parses an ItemType to be used as an alias, i.e. it doesn't parse 'all'/'every' and the amount. - * + * * @param s mixed case string * @return A new ItemType representing the given value */ @@ -216,28 +208,23 @@ public static ItemType parseAlias(final String s) { } if (s.equals("*")) return everything; - + final ItemType t = new ItemType(); - + final String[] types = s.split("\\s*,\\s*"); for (final String type : types) { if (type == null || parseType(type, t, true) == null) return null; } - + return t; } - - private final static RegexMessage p_any = new RegexMessage("aliases.any", "", " (.+)", Pattern.CASE_INSENSITIVE); - 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); - + /** * Parses an ItemType. *

* Prints errors. - * + * * @param s * @return The parsed ItemType or null if the input is invalid. */ @@ -245,10 +232,10 @@ public static ItemType parseAlias(final String s) { public static ItemType parseItemType(String s) { if (s.isEmpty()) return null; - s = "" + s.trim(); - + s = s.trim(); + final ItemType t = new ItemType(); - + Matcher m; if ((m = p_of_every.matcher(s)).matches()) { t.setAmount(Utils.parseInt("" + m.group(1))); @@ -266,11 +253,12 @@ public static ItemType parseItemType(String s) { if (s.length() != l) // had indefinite article t.setAmount(1); } - + String lc = s.toLowerCase(Locale.ENGLISH); String of = Language.getSpaced("of").toLowerCase(); int c = -1; - outer: while ((c = lc.indexOf(of, c + 1)) != -1) { + outer: + while ((c = lc.indexOf(of, c + 1)) != -1) { ItemType t2 = t.clone(); try (BlockingLogHandler ignored = new BlockingLogHandler().start()) { if (parseType("" + s.substring(0, c), t2, false) == null) @@ -287,19 +275,19 @@ public static ItemType parseItemType(String s) { } return t2; } - + if (parseType(s, t, false) == null) return null; - + if (t.numTypes() == 0) return null; - + return t; } - + /** * 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 isAlias Whether this type is parsed for an alias. @@ -323,10 +311,10 @@ private static ItemType parseType(final String s, final ItemType t, final boolea Skript.error(m_invalid_item_type.toString(s)); return null; } - + /** * Gets an alias from the aliases defined in the config. - * + * * @param s The alias to get, case does not matter * @return A copy of the ItemType represented by the given alias or null if no such alias exists. */ @@ -372,14 +360,14 @@ private static ItemType getAlias(final String s) { } return null; } - + /** * Clears aliases. Make sure to load them after this! */ public static void clear() { provider.clearAliases(); } - + /** * Loads aliases from Skript's standard locations. * Exceptions will be logged, but not thrown. @@ -400,19 +388,41 @@ public static void load() { private static void loadMissingAliases() { if (!Skript.methodExists(Material.class, "getKey")) return; + boolean modItemRegistered = false; for (Material material : Material.values()) { if (!material.isLegacy() && !provider.hasAliasForMaterial(material)) { NamespacedKey key = material.getKey(); - String name = key.getKey().replace("_", " "); - parser.loadAlias(name + "¦s", key.toString()); + // mod:an_item -> (mod's an item) | (an item from mod) + // minecraft:dirt -> dirt + if (NamespacedKey.MINECRAFT.equals(key.getNamespace())) { + parser.loadAlias(key.getKey().replace("_", " ") + "¦s", key.toString()); + } else { + if (!modItemRegistered) modItemRegistered = true; + parser.loadAlias((key.getNamespace() + "'s " + key.getKey() + "¦s").replace("_", " "), key.toString()); + parser.loadAlias((key.getKey() + "¦s from " + key.getNamespace()).replace("_", " "), key.toString()); + } Skript.debug(ChatColor.YELLOW + "Creating temporary alias for: " + key); } } + + if (modItemRegistered) { + Skript.warning("=============================================================="); + Skript.warning("Some materials were found that seem to be modded."); + Skript.warning("An item that has the id 'mod:item' can be used as 'mod's item' or 'item from mod'."); + Skript.warning("WARNING: Skript does not officially support any modded servers."); + Skript.warning("Any issues you encounter related to modded items will be your responsibility to fix."); + Skript.warning("The server will keep loading after 5 seconds."); + Skript.warning("=============================================================="); + try { + Thread.sleep(5000L); + } catch (InterruptedException ignored) { + } + } } - + private static void loadInternal() throws IOException { Path dataFolder = Skript.getInstance().getDataFolder().toPath(); - + // Load aliases.zip OR aliases from jar (never both) Path zipPath = dataFolder.resolve("aliases-english.zip"); if (!SkriptConfig.loadDefaultAliases.value()) { @@ -436,9 +446,9 @@ private static void loadInternal() throws IOException { } catch (URISyntaxException e) { assert false; } - + } - + // Load everything from aliases folder (user aliases) Path aliasesFolder = dataFolder.resolve("aliases"); if (Files.exists(aliasesFolder)) { @@ -448,19 +458,19 @@ private static void loadInternal() throws IOException { // generate aliases from item names for any missing items loadMissingAliases(); - + // Update tracked item types for (Map.Entry entry : trackedTypes.entrySet()) { @SuppressWarnings("null") // No null keys in this map ItemType type = parseItemType(entry.getKey()); if (type == null) Skript.warning("Alias '" + entry.getKey() + "' is required by Skript, but does not exist anymore. " - + "Make sure to fix this before restarting the server."); + + "Make sure to fix this before restarting the server."); else entry.getValue().setTo(type); } } - + /** * Loads aliases from given directory. * @param dir Directory of aliases. @@ -484,7 +494,7 @@ else if (name.endsWith(".sk")) throw e.getCause(); } } - + /** * Loads aliases from given path. * @param f Path of alias file. @@ -494,7 +504,7 @@ public static void load(Path f) throws IOException { Config config = new Config(f, false, false, "="); load(config); } - + /** * Loads aliases from configuration. * @param config Configuration containing the aliases. @@ -505,7 +515,7 @@ public static void load(Config config) { Skript.error(m_outside_section.toString()); continue; } - + parser.load((SectionNode) n); } } @@ -523,7 +533,7 @@ public static String getMinecraftId(ItemData data) { } return provider.getMinecraftId(data); } - + /** * Gets an entity type related to given item. For example, an armor stand * item is related with armor stand entity. @@ -538,22 +548,11 @@ public static EntityData getRelatedEntity(ItemData data) { } return provider.getRelatedEntity(data); } - - /** - * Go through these whenever aliases are reloaded, and update them. - */ - private static final Map trackedTypes = new HashMap<>(); - - /** - * If user had an obscure config option set, don't crash due to missing - * Java item types. - */ - private static final boolean noHardExceptions = SkriptConfig.apiSoftExceptions.value(); - + /** * Gets an item type that matches the given name. * If it doesn't exist, an exception is thrown instead. - * + * *

Item types provided by this method are updated when aliases are * reloaded. However, this also means they are tracked by aliases system * and NOT necessarily garbage-collected. @@ -580,7 +579,7 @@ public static ItemType javaItemType(String name) { trackedTypes.put(name, type); return type; } - + /** * Creates an aliases provider to be used by given addon. It can be used to * register aliases and variations to be used in scripts. @@ -591,11 +590,11 @@ public static AliasesProvider getAddonProvider(@Nullable SkriptAddon addon) { if (addon == null) { throw new IllegalArgumentException("addon needed"); } - + // TODO in future, maybe record and allow unloading addon-provided aliases? return provider; // For now, just allow loading aliases easily } - + /** * Creates script aliases for the provided Script. * @return Script aliases that are ready to be added to. diff --git a/src/main/java/ch/njol/skript/aliases/AliasesProvider.java b/src/main/java/ch/njol/skript/aliases/AliasesProvider.java index 06ae5317f78..1cbaa0c9cfd 100644 --- a/src/main/java/ch/njol/skript/aliases/AliasesProvider.java +++ b/src/main/java/ch/njol/skript/aliases/AliasesProvider.java @@ -1,45 +1,24 @@ -/** - * 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 . - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ package ch.njol.skript.aliases; +import ch.njol.skript.Skript; +import ch.njol.skript.bukkitutil.BukkitUnsafe; +import ch.njol.skript.bukkitutil.ItemUtils; +import ch.njol.skript.bukkitutil.block.BlockCompat; +import ch.njol.skript.bukkitutil.block.BlockValues; +import ch.njol.skript.entity.EntityData; +import com.google.gson.Gson; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; -import java.util.Set; import java.util.List; import java.util.Map; - -import ch.njol.skript.Skript; -import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import java.util.Set; import org.bukkit.Material; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.Nullable; -import com.google.gson.Gson; - -import ch.njol.skript.bukkitutil.BukkitUnsafe; -import ch.njol.skript.bukkitutil.ItemUtils; -import ch.njol.skript.bukkitutil.block.BlockCompat; -import ch.njol.skript.bukkitutil.block.BlockValues; -import ch.njol.skript.entity.EntityData; - /** * Provides aliases on Bukkit/Spigot platform. */ @@ -47,14 +26,14 @@ public class AliasesProvider { // not supported on Spigot versions older than 1.18 private static final boolean FASTER_SET_SUPPORTED = Skript.classExists("it.unimi.dsi.fastutil.objects.ObjectOpenHashSet"); - + /** * When an alias is not found, it will requested from this provider. * Null when this is global aliases provider. */ @Nullable private final AliasesProvider parent; - + /** * All aliases that are currently loaded by this provider. */ @@ -64,112 +43,21 @@ public class AliasesProvider { * All materials that are currently loaded by this provider. */ private final Set materials; - + /** * Tags are in JSON format. We may need GSON when merging tags * (which might be done if variations are used). */ private final Gson gson; - - /** - * Represents a variation of material. It could, for example, define one - * more tag or change base id, but keep tag intact. - */ - public static class Variation { - - @Nullable - private final String id; - private final int insertPoint; - - private final Map tags; - private final Map states; - - public Variation(@Nullable String id, int insertPoint, Map tags, Map states) { - this.id = id; - this.insertPoint = insertPoint; - this.tags = tags; - this.states = states; - } - - @Nullable - public String getId() { - return id; - } - - public int getInsertPoint() { - return insertPoint; - } - - @Nullable - public String insertId(@Nullable String inserted) { - if (inserted == null) // Nothing to insert - return id; - inserted = inserted.substring(0, inserted.length() - 1); // Strip out - - if (id == null) // Inserting to nothing - return inserted; - - String id = this.id; - assert id != null; - if (insertPoint == -1) // No place where to insert - return inserted; - - // Insert given string to in middle of our id - String before = id.substring(0, insertPoint); - String after = id.substring(insertPoint + 1); - return before + inserted + after; - } - - public Map getTags() { - return tags; - } - - - public Map getBlockStates() { - return states; - } - - - public Variation merge(Variation other) { - // Merge tags and block states - Map mergedTags = new HashMap<>(other.tags); - mergedTags.putAll(tags); - Map mergedStates = new HashMap<>(other.states); - mergedStates.putAll(states); - - // Potentially merge ids - String id = insertId(other.id); - - return new Variation(id, -1, mergedTags, mergedStates); - } - } - - public static class VariationGroup { - - public final List keys; - - public final List values; - - public VariationGroup() { - this.keys = new ArrayList<>(); - this.values = new ArrayList<>(); - } - - public void put(String key, Variation value) { - keys.add(key); - values.add(value); - } - } - /** * Contains all variations. */ private final Map variations; - /** * Allows looking up aliases based on item datas created runtime. */ private final AliasesMap aliasesMap; - + /** * Constructs a new aliases provider with no data. */ @@ -184,20 +72,20 @@ public AliasesProvider(int expectedCount, @Nullable AliasesProvider parent) { } else { this.materials = new HashSet<>(); } - + this.gson = new Gson(); } - + /** * Uses GSON to parse Mojang's JSON format to a map. * @param raw Raw JSON. - * @return String,Object map. + * @return String, Object map. */ @SuppressWarnings({"null", "unchecked"}) public Map parseMojangson(String raw) { return (Map) gson.fromJson(raw, Object.class); } - + /** * Applies given tags to an item stack. * @param stack Item stack. @@ -213,10 +101,10 @@ public int applyTags(ItemStack stack, Map tags) { tags.remove("Damage"); flags |= ItemFlags.CHANGED_DURABILITY; } - + if (tags.isEmpty()) // No real tags to apply return flags; - + // Apply random tags using JSON if (Aliases.USING_ITEM_COMPONENTS) { String components = (String) tags.get("components"); // only components are supported for modifying a stack @@ -231,40 +119,10 @@ public int applyTags(ItemStack stack, Map tags) { BukkitUnsafe.modifyItemStack(stack, json); } flags |= ItemFlags.CHANGED_TAGS; - + return flags; } - - /** - * Name of an alias used by {@link #addAlias(AliasName, String, Map, Map)} - * for registration. - */ - public static class AliasName { - - /** - * Singular for of alias name. - */ - public final String singular; - - /** - * Plural form of alias name. - */ - public final String plural; - - /** - * Gender of alias name. - */ - public final int gender; - public AliasName(String singular, String plural, int gender) { - super(); - this.singular = singular; - this.plural = plural; - this.gender = gender; - } - - } - /** * Adds an alias to this provider. * @param name Name of alias without any patterns or variation blocks. @@ -290,19 +148,19 @@ public void addAlias(AliasName name, String id, @Nullable Map ta datas = typeOfId.getTypes(); } else { // ... but quite often, we just got Vanilla id // Prepare and modify ItemStack (using somewhat Unsafe methods) - Material material = BukkitUnsafe.getMaterialFromMinecraftId(id); + Material material = BukkitUnsafe.getMaterialFromNamespacedId(id); if (material == null) { // If server doesn't recognize id, do not proceed throw new InvalidMinecraftIdException(id); } materials.add(material); - + // Hacky: get related entity from block states String entityName = blockStates.remove("relatedEntity"); if (entityName != null) { related = EntityData.parse(entityName); } - + // Apply (NBT) tags to item stack ItemStack stack = null; int itemFlags = 0; @@ -312,14 +170,14 @@ public void addAlias(AliasName name, String id, @Nullable Map ta itemFlags = applyTags(stack, new HashMap<>(tags)); } } - + // Parse block state to block values BlockValues blockValues = BlockCompat.INSTANCE.createBlockValues(material, blockStates, stack, itemFlags); - + ItemData data = stack != null ? new ItemData(stack, blockValues) : new ItemData(material, blockValues); data.isAlias = true; data.itemFlags = itemFlags; - + // Deduplicate item data if this has been loaded before if (deduplicate) { AliasesMap.Match canonical = aliasesMap.exactMatch(data); @@ -329,10 +187,10 @@ public void addAlias(AliasName name, String id, @Nullable Map ta data = aliasData.getItem(); } } - + datas = Collections.singletonList(data); } - + // If this is first time we're defining an item, store additional data about it if (typeOfId == null) { ItemData data = datas.get(0); @@ -351,7 +209,8 @@ public void addAlias(AliasName name, String id, @Nullable Map ta aliases.put(name.plural, type); // Plural form type.addAll(datas); } else { // There is already an item type with this name, we need to *only* add new data - newDataLoop: for (ItemData newData : datas) { + newDataLoop: + for (ItemData newData : datas) { for (ItemData existingData : type.getTypes()) { if (newData == existingData || newData.matchAlias(existingData).isAtLeast(MatchQuality.EXACT)) // Don't add this data, the item type already contains it! continue newDataLoop; @@ -360,11 +219,11 @@ public void addAlias(AliasName name, String id, @Nullable Map ta } } } - + public void addVariationGroup(String name, VariationGroup group) { variations.put(name, group); } - + @Nullable public VariationGroup getVariationGroup(String name) { return variations.get(name); @@ -378,7 +237,7 @@ public ItemType getAlias(String alias) { } return item; } - + public AliasesMap.@Nullable AliasData getAliasData(ItemData item) { AliasesMap.AliasData data = aliasesMap.matchAlias(item).getData(); if (data == null && parent != null) { @@ -395,7 +254,7 @@ public String getMinecraftId(ItemData item) { } return null; } - + @Nullable public MaterialName getMaterialName(ItemData item) { AliasesMap.AliasData data = getAliasData(item); @@ -404,7 +263,7 @@ public MaterialName getMaterialName(ItemData item) { } return null; } - + @Nullable public EntityData getRelatedEntity(ItemData item) { AliasesMap.AliasData data = getAliasData(item); @@ -434,4 +293,123 @@ public boolean hasAliasForMaterial(Material material) { return materials.contains(material); } + /** + * Represents a variation of material. It could, for example, define one + * more tag or change base id, but keep tag intact. + */ + public static class Variation { + + @Nullable + private final String id; + private final int insertPoint; + + private final Map tags; + private final Map states; + + public Variation(@Nullable String id, int insertPoint, Map tags, Map states) { + this.id = id; + this.insertPoint = insertPoint; + this.tags = tags; + this.states = states; + } + + @Nullable + public String getId() { + return id; + } + + public int getInsertPoint() { + return insertPoint; + } + + @Nullable + public String insertId(@Nullable String inserted) { + if (inserted == null) // Nothing to insert + return id; + inserted = inserted.substring(0, inserted.length() - 1); // Strip out - + if (id == null) // Inserting to nothing + return inserted; + + String id = this.id; + assert id != null; + if (insertPoint == -1) // No place where to insert + return inserted; + + // Insert given string to in middle of our id + String before = id.substring(0, insertPoint); + String after = id.substring(insertPoint + 1); + return before + inserted + after; + } + + public Map getTags() { + return tags; + } + + + public Map getBlockStates() { + return states; + } + + + public Variation merge(Variation other) { + // Merge tags and block states + Map mergedTags = new HashMap<>(other.tags); + mergedTags.putAll(tags); + Map mergedStates = new HashMap<>(other.states); + mergedStates.putAll(states); + + // Potentially merge ids + String id = insertId(other.id); + + return new Variation(id, -1, mergedTags, mergedStates); + } + } + + public static class VariationGroup { + + public final List keys; + + public final List values; + + public VariationGroup() { + this.keys = new ArrayList<>(); + this.values = new ArrayList<>(); + } + + public void put(String key, Variation value) { + keys.add(key); + values.add(value); + } + } + + /** + * Name of an alias used by {@link #addAlias(AliasName, String, Map, Map)} + * for registration. + */ + public static class AliasName { + + /** + * Singular for of alias name. + */ + public final String singular; + + /** + * Plural form of alias name. + */ + public final String plural; + + /** + * Gender of alias name. + */ + public final int gender; + + public AliasName(String singular, String plural, int gender) { + super(); + this.singular = singular; + this.plural = plural; + this.gender = gender; + } + + } + } diff --git a/src/main/java/ch/njol/skript/bukkitutil/BukkitUnsafe.java b/src/main/java/ch/njol/skript/bukkitutil/BukkitUnsafe.java index f47a3f5d8d7..e7e255f1e72 100644 --- a/src/main/java/ch/njol/skript/bukkitutil/BukkitUnsafe.java +++ b/src/main/java/ch/njol/skript/bukkitutil/BukkitUnsafe.java @@ -1,76 +1,75 @@ -/** - * 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 . - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ package ch.njol.skript.bukkitutil; +import ch.njol.skript.Skript; +import ch.njol.util.EnumTypeAdapter; +import com.google.common.io.ByteStreams; +import com.google.gson.GsonBuilder; +import com.google.gson.reflect.TypeToken; import java.io.IOException; import java.io.InputStream; -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; import java.lang.reflect.Type; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; - import org.bukkit.Bukkit; import org.bukkit.Material; +import org.bukkit.NamespacedKey; import org.bukkit.UnsafeValues; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.Nullable; -import com.google.common.io.ByteStreams; -import com.google.gson.GsonBuilder; -import com.google.gson.reflect.TypeToken; -import ch.njol.util.EnumTypeAdapter; -import ch.njol.skript.Skript; -import ch.njol.skript.util.Version; - /** * Contains helpers for Bukkit's not so safe stuff. */ @SuppressWarnings("deprecation") public class BukkitUnsafe { - + /** * Bukkit's UnsafeValues allows us to do stuff that would otherwise * require NMS. It has existed for a long time, too, so 1.9 support is * not particularly hard to achieve. - * + * * UnsafeValues' existence and behavior is not guaranteed across future versions. */ @Nullable private static final UnsafeValues unsafe = Bukkit.getUnsafe(); + /** + * Maps pre 1.12 ids to materials for variable conversions. + */ + private static @Nullable Map idMappings; + static { if (unsafe == null) throw new Error("UnsafeValues are not available."); } /** - * Maps pre 1.12 ids to materials for variable conversions. + * Get a material from a minecraft id. + * + * @param id Namespaced ID with or without a namespace. IDs without a namespace will be treated + * as minecraft namespaced IDs. ('minecraft:dirt' and 'dirt' are equivalent.) + * @return The Material which the id represents, or null if no material can be matched. + * @deprecated Prefer {@link BukkitUnsafe#getMaterialFromNamespacedId(String)} for including modded item support */ - @Nullable - private static Map idMappings; + @Deprecated + public static @Nullable Material getMaterialFromMinecraftId(String id) { + return getMaterialFromNamespacedId(id); + } - @Nullable - public static Material getMaterialFromMinecraftId(String id) { - return Material.matchMaterial(id); + /** + * Get a material from a namespaced ID. + * For example, 'minecraft:iron_ingot' -> Material.IRON_INGOT; 'mod:an_item' -> Material.MOD_AN_ITEM + * + * @param id Namespaced ID with or without a namespace. IDs without a namespace will be treated + * as minecraft namespaced IDs. ('minecraft:dirt' and 'dirt' are equivalent.) + * @return The Material which the id represents, or null if no material can be matched. + */ + public static @Nullable Material getMaterialFromNamespacedId(String id) { + return Material.matchMaterial(id.toLowerCase().startsWith(NamespacedKey.MINECRAFT + ":") + ? id + : id.replace(":", "_") //For Hybrid Server + ); } public static void modifyItemStack(ItemStack stack, String arguments) { @@ -78,19 +77,20 @@ public static void modifyItemStack(ItemStack stack, String arguments) { throw new IllegalStateException("modifyItemStack could not be performed as UnsafeValues are not available."); unsafe.modifyItemStack(stack, arguments); } - + private static void initIdMappings() { try (InputStream is = Skript.getInstance().getResource("materials/ids.json")) { if (is == null) { throw new AssertionError("missing id mappings"); } String data = new String(ByteStreams.toByteArray(is), StandardCharsets.UTF_8); - - Type type = new TypeToken>(){}.getType(); + + Type type = new TypeToken>() { + }.getType(); Map rawMappings = new GsonBuilder(). - registerTypeAdapterFactory(EnumTypeAdapter.factory) - .create().fromJson(data, type); - + registerTypeAdapterFactory(EnumTypeAdapter.factory) + .create().fromJson(data, type); + // Process raw mappings Map parsed = new HashMap<>(rawMappings.size()); // Legacy material conversion API @@ -102,7 +102,7 @@ private static void initIdMappings() { throw new AssertionError(e); } } - + @Nullable public static Material getMaterialFromId(int id) { if (idMappings == null) {