diff --git a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java index 25000a2390d..ba1a419aad0 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java @@ -38,6 +38,7 @@ import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.OfflinePlayer; +import org.bukkit.Registry; import org.bukkit.SoundCategory; import org.bukkit.World; import org.bukkit.World.Environment; @@ -77,7 +78,6 @@ import org.bukkit.potion.PotionEffectType; import org.bukkit.util.CachedServerIcon; import org.bukkit.util.Vector; -import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.Skript; import ch.njol.skript.SkriptConfig; @@ -90,6 +90,7 @@ import ch.njol.skript.classes.EnumClassInfo; import ch.njol.skript.classes.Parser; import ch.njol.skript.classes.Serializer; +import ch.njol.skript.classes.registry.RegistryClassInfo; import ch.njol.skript.entity.EntityData; import ch.njol.skript.expressions.ExprDamageCause; import ch.njol.skript.expressions.base.EventValueExpression; @@ -104,6 +105,7 @@ import ch.njol.util.StringUtils; import ch.njol.yggdrasil.Fields; import io.papermc.paper.world.MoonPhase; +import org.jetbrains.annotations.Nullable; /** * @author Peter Güttinger @@ -976,8 +978,14 @@ public String toVariableNameString(final ItemStack i) { .name(ClassInfo.NO_DOC) .since("2.0") .changer(DefaultChangers.itemChanger)); - - Classes.registerClass(new EnumClassInfo<>(Biome.class, "biome", "biomes") + + ClassInfo biomeClassInfo; + if (Skript.classExists("org.bukkit.Registry") && Skript.fieldExists(Registry.class, "BIOME")) { + biomeClassInfo = new RegistryClassInfo<>(Biome.class, Registry.BIOME, "biome", "biomes"); + } else { + biomeClassInfo = new EnumClassInfo<>(Biome.class, "biome", "biomes"); + } + Classes.registerClass(biomeClassInfo .user("biomes?") .name("Biome") .description("All possible biomes Minecraft uses to generate a world.") diff --git a/src/main/java/ch/njol/skript/classes/registry/RegistryClassInfo.java b/src/main/java/ch/njol/skript/classes/registry/RegistryClassInfo.java new file mode 100644 index 00000000000..ff82f966af8 --- /dev/null +++ b/src/main/java/ch/njol/skript/classes/registry/RegistryClassInfo.java @@ -0,0 +1,53 @@ +/** + * 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.classes.registry; + +import ch.njol.skript.classes.ClassInfo; +import ch.njol.skript.expressions.base.EventValueExpression; +import ch.njol.skript.lang.DefaultExpression; +import org.bukkit.Keyed; +import org.bukkit.Registry; + +/** + * This class can be used for easily creating ClassInfos for {@link Registry}s. + * It registers a language node with usage, a serializer, default expression, and a parser. + * + * @param The Registry class. + */ +public class RegistryClassInfo extends ClassInfo { + + public RegistryClassInfo(Class registryClass, Registry registry, String codeName, String languageNode) { + this(registryClass, registry, codeName, languageNode, new EventValueExpression<>(registryClass)); + } + + /** + * @param registry The registry + * @param codeName The name used in patterns + */ + public RegistryClassInfo(Class registryClass, Registry registry, String codeName, String languageNode, DefaultExpression defaultExpression) { + super(registryClass, codeName); + RegistryParser registryParser = new RegistryParser<>(registry, languageNode); + usage(registryParser.getAllNames()) + .supplier(registry::iterator) + .serializer(new RegistrySerializer(registry)) + .defaultExpression(defaultExpression) + .parser(registryParser); + } + +} diff --git a/src/main/java/ch/njol/skript/classes/registry/RegistryParser.java b/src/main/java/ch/njol/skript/classes/registry/RegistryParser.java new file mode 100644 index 00000000000..51cb4637434 --- /dev/null +++ b/src/main/java/ch/njol/skript/classes/registry/RegistryParser.java @@ -0,0 +1,155 @@ +/** + * 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.classes.registry; + +import ch.njol.skript.classes.Parser; +import ch.njol.skript.lang.ParseContext; +import ch.njol.skript.localization.Language; +import ch.njol.skript.localization.Noun; +import ch.njol.util.NonNullPair; +import ch.njol.util.StringUtils; +import org.bukkit.Keyed; +import org.bukkit.NamespacedKey; +import org.bukkit.Registry; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +/** + * A parser based on a {@link Registry} used to parse data from a string or turn data into a string. + * + * @param Registry class + */ +public class RegistryParser extends Parser { + + private final Registry registry; + private final String languageNode; + + private final Map names = new HashMap<>(); + private final Map parseMap = new HashMap<>(); + + public RegistryParser(Registry registry, String languageNode) { + assert !languageNode.isEmpty() && !languageNode.endsWith(".") : languageNode; + this.registry = registry; + this.languageNode = languageNode; + refresh(); + Language.addListener(this::refresh); + } + + private void refresh() { + names.clear(); + parseMap.clear(); + for (R registryObject : registry) { + NamespacedKey namespacedKey = registryObject.getKey(); + String namespace = namespacedKey.getNamespace(); + String key = namespacedKey.getKey(); + String keyWithSpaces = key.replace("_", " "); + String languageKey = languageNode + "." + key; + + // Put the full namespaced key as a pattern + parseMap.put(namespacedKey.toString(), registryObject); + + // If the object is a vanilla Minecraft object, we'll add the key with spaces as a pattern + if (namespace.equalsIgnoreCase(NamespacedKey.MINECRAFT)) { + parseMap.put(keyWithSpaces, registryObject); + } + + String[] options = Language.getList(languageKey); + // Missing/Custom registry objects + if (options.length == 1 && options[0].equals(languageKey.toLowerCase(Locale.ENGLISH))) { + if (namespace.equalsIgnoreCase(NamespacedKey.MINECRAFT)) { + // If the object is a vanilla Minecraft object, we'll use the key with spaces as a name + names.put(registryObject, keyWithSpaces); + } else { + // If the object is a custom object, we'll use the full namespaced key as a name + names.put(registryObject, namespacedKey.toString()); + } + } else { + for (String option : options) { + option = option.toLowerCase(Locale.ENGLISH); + + // Isolate the gender if one is present + NonNullPair strippedOption = Noun.stripGender(option, languageKey); + String first = strippedOption.getFirst(); + Integer second = strippedOption.getSecond(); + + // Add to name map if needed + names.putIfAbsent(registryObject, first); + + parseMap.put(first, registryObject); + if (second != -1) { // There is a gender present + parseMap.put(Noun.getArticleWithSpace(second, Language.F_INDEFINITE_ARTICLE) + first, registryObject); + } + } + } + } + } + + /** + * This method attempts to match the string input against one of the string representations of the registry. + * + * @param input a string to attempt to match against one in the registry. + * @param context of parsing, may not be null + * @return The registry object matching the input, or null if no match could be made. + */ + @Override + public @Nullable R parse(String input, @NotNull ParseContext context) { + return parseMap.get(input.toLowerCase(Locale.ENGLISH)); + } + + /** + * This method returns the string representation of a registry. + * + * @param object The object to represent as a string. + * @param flags not currently used + * @return A string representation of the registry object. + */ + @Override + public @NotNull String toString(R object, int flags) { + return names.get(object); + } + + /** + * Returns a registry object's string representation in a variable name. + * + * @param object Object to represent in a variable name. + * @return The given object's representation in a variable name. + */ + @Override + public @NotNull String toVariableNameString(R object) { + return toString(object, 0); + } + + /** + * @return A comma-separated string containing a list of all names representing the registry. + * Note that some entries may represent the same registry object. + */ + public String getAllNames() { + List strings = new ArrayList<>(parseMap.keySet()); + Collections.sort(strings); + return StringUtils.join(strings, ", "); + } + +} diff --git a/src/main/java/ch/njol/skript/classes/registry/RegistrySerializer.java b/src/main/java/ch/njol/skript/classes/registry/RegistrySerializer.java new file mode 100644 index 00000000000..ddec6c3225f --- /dev/null +++ b/src/main/java/ch/njol/skript/classes/registry/RegistrySerializer.java @@ -0,0 +1,83 @@ +/** + * 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.classes.registry; + +import ch.njol.skript.classes.Serializer; +import ch.njol.yggdrasil.Fields; +import org.bukkit.Keyed; +import org.bukkit.NamespacedKey; +import org.bukkit.Registry; + +import java.io.StreamCorruptedException; + +/** + * Serializer for {@link RegistryClassInfo} + * + * @param Registry class + */ +public class RegistrySerializer extends Serializer { + + private final Registry registry; + + public RegistrySerializer(Registry registry) { + this.registry = registry; + } + + @Override + public Fields serialize(R o) { + Fields fields = new Fields(); + fields.putPrimitive("name", o.getKey().toString()); + return null; + } + + @Override + protected R deserialize(Fields fields) { + try { + String name = fields.getAndRemovePrimitive("name", String.class); + NamespacedKey namespacedKey; + if (!name.contains(":")) { + // Old variables + namespacedKey = NamespacedKey.minecraft(name); + } else { + namespacedKey = NamespacedKey.fromString(name); + } + if (namespacedKey == null) + return null; + return registry.get(namespacedKey); + } catch (StreamCorruptedException e) { + return null; + } + } + + @Override + public boolean mustSyncDeserialization() { + return false; + } + + @Override + protected boolean canBeInstantiated() { + return false; + } + + @Override + public void deserialize(R o, Fields f) { + throw new UnsupportedOperationException(); + } + +}