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();
+ }
+
+}