aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJake Potrebic <[email protected]>2024-05-11 19:26:33 -0700
committerJake Potrebic <[email protected]>2024-06-15 06:48:11 -0700
commit4e064745c2c7dd26a0aeab177e7d04f7b5fba3cc (patch)
treea47138f591942b7f23d9aabb174e0f0b60b8b9c5
parente3c7b1949cfc95fc1097951d81ca04b12482eb97 (diff)
downloadPaper-4e064745c2c7dd26a0aeab177e7d04f7b5fba3cc.tar.gz
Paper-4e064745c2c7dd26a0aeab177e7d04f7b5fba3cc.zip
RegistrySet APIfeature/RegistrySet
-rw-r--r--patches/api/0434-Improve-Registry.patch36
-rw-r--r--patches/api/0478-Registry-Modification-API.patch2
-rw-r--r--patches/api/0479-RegistrySet-API.patch486
-rw-r--r--patches/server/1001-Registry-Modification-API.patch (renamed from patches/server/0991-Registry-Modification-API.patch)22
-rw-r--r--patches/server/1002-RegistrySet-API.patch224
5 files changed, 749 insertions, 21 deletions
diff --git a/patches/api/0434-Improve-Registry.patch b/patches/api/0434-Improve-Registry.patch
index 1d2d6fa13c..fd25406955 100644
--- a/patches/api/0434-Improve-Registry.patch
+++ b/patches/api/0434-Improve-Registry.patch
@@ -31,15 +31,45 @@ index 62d2b3f950860dee0898d77b0a29635c3f9a7e23..704dba92f9246ef398ed8d162ebee3cf
@Override
public @NotNull String translationKey() {
diff --git a/src/main/java/org/bukkit/Registry.java b/src/main/java/org/bukkit/Registry.java
-index 5ba5cf06bf12fc2e81500e09209e26047e683fa9..802511eaf697d703cadb4b418fe51ea6d31ff3c8 100644
+index 5ba5cf06bf12fc2e81500e09209e26047e683fa9..b02160f42281bc4e20123752a593c5d655305381 100644
--- a/src/main/java/org/bukkit/Registry.java
+++ b/src/main/java/org/bukkit/Registry.java
-@@ -354,6 +354,49 @@ public interface Registry<T extends Keyed> extends Iterable<T> {
+@@ -354,6 +354,79 @@ public interface Registry<T extends Keyed> extends Iterable<T> {
@Nullable
T get(@NotNull NamespacedKey key);
+ // Paper start - improve Registry
+ /**
++ * Gets the object by its key or throws if it doesn't exist.
++ *
++ * @param key the key to get the object of in this registry
++ * @return the object for the key
++ * @throws java.util.NoSuchElementException if the key doesn't point to an object in the registry
++ */
++ default @NotNull T getOrThrow(final net.kyori.adventure.key.@NotNull Key key) {
++ final T value = this.get(key);
++ if (value == null) {
++ throw new java.util.NoSuchElementException("No value for " + key + " in " + this);
++ }
++ return value;
++ }
++
++ /**
++ * Gets the object by its key or throws if it doesn't exist.
++ *
++ * @param key the key to get the object of in this registry
++ * @return the object for the key
++ * @throws java.util.NoSuchElementException if the key doesn't point to an object in the registry
++ */
++ default @NotNull T getOrThrow(final io.papermc.paper.registry.@NotNull TypedKey<T> key) {
++ final T value = this.get(key);
++ if (value == null) {
++ throw new java.util.NoSuchElementException("No value for " + key + " in " + this);
++ }
++ return value;
++ }
++
++ /**
+ * Gets the key for this object or throws if it doesn't exist.
+ * <p>
+ * Some types can exist without being in a registry
@@ -84,7 +114,7 @@ index 5ba5cf06bf12fc2e81500e09209e26047e683fa9..802511eaf697d703cadb4b418fe51ea6
/**
* Returns a new stream, which contains all registry items, which are registered to the registry.
*
-@@ -428,5 +471,12 @@ public interface Registry<T extends Keyed> extends Iterable<T> {
+@@ -428,5 +501,12 @@ public interface Registry<T extends Keyed> extends Iterable<T> {
public Class<T> getType() {
return this.type;
}
diff --git a/patches/api/0478-Registry-Modification-API.patch b/patches/api/0478-Registry-Modification-API.patch
index 70f095bc1f..b6d067058b 100644
--- a/patches/api/0478-Registry-Modification-API.patch
+++ b/patches/api/0478-Registry-Modification-API.patch
@@ -366,7 +366,7 @@ index 0000000000000000000000000000000000000000..f4d4ebf6cbed1b4a9955ceb2d0586782
+public interface RegistryEntryAddEventType<T, B extends RegistryBuilder<T>> extends LifecycleEventType<BootstrapContext, RegistryEntryAddEvent<T, B>, RegistryEntryAddConfiguration<T>> {
+}
diff --git a/src/main/java/org/bukkit/Registry.java b/src/main/java/org/bukkit/Registry.java
-index 802511eaf697d703cadb4b418fe51ea6d31ff3c8..85c4f231dc343c73f1678819f1ac95d5a7ffd080 100644
+index b02160f42281bc4e20123752a593c5d655305381..240500771bac439156b4c5d93ff047ae45d56154 100644
--- a/src/main/java/org/bukkit/Registry.java
+++ b/src/main/java/org/bukkit/Registry.java
@@ -353,6 +353,27 @@ public interface Registry<T extends Keyed> extends Iterable<T> {
diff --git a/patches/api/0479-RegistrySet-API.patch b/patches/api/0479-RegistrySet-API.patch
new file mode 100644
index 0000000000..7f310e04cb
--- /dev/null
+++ b/patches/api/0479-RegistrySet-API.patch
@@ -0,0 +1,486 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Sat, 11 May 2024 19:25:48 -0700
+Subject: [PATCH] RegistrySet API
+
+This API is supposed to be the API equivalent of nms'
+HolderSet and HolderSet$Named.
+
+diff --git a/src/main/java/io/papermc/paper/registry/set/RegistryKeySet.java b/src/main/java/io/papermc/paper/registry/set/RegistryKeySet.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..2a36c5ec0da05c7ff19023c542e4b6b04326b8ee
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/set/RegistryKeySet.java
+@@ -0,0 +1,40 @@
++package io.papermc.paper.registry.set;
++
++import io.papermc.paper.registry.TypedKey;
++import io.papermc.paper.registry.tag.Tag;
++import java.util.Collection;
++import org.bukkit.Keyed;
++import org.bukkit.Registry;
++import org.checkerframework.checker.nullness.qual.NonNull;
++import org.jetbrains.annotations.ApiStatus;
++import org.jetbrains.annotations.Unmodifiable;
++
++public non-sealed interface RegistryKeySet<T extends Keyed> extends RegistrySet<T> { // TODO remove Keyed
++
++ @Override
++ default int size() {
++ return this.values().size();
++ }
++
++ /**
++ * Get the keys for the values in this set.
++ *
++ * @return the keys
++ */
++ @NonNull @Unmodifiable Collection<TypedKey<T>> values();
++
++ /**
++ * Resolve this set into a collection of values. Depending on the use
++ * case, it may be better to specifically check if this set is a
++ * {@link RegistryKeySet} or a {@link RegistryValueSet} and access
++ * the keys or values, respectively, directly. May
++ * fail if called too early or a registered value is missing.
++ *
++ * @return the resolved values
++ * @see RegistryKeySet#values()
++ * @see RegistryValueSet#values()
++ */
++ @NonNull @Unmodifiable Collection<T> resolve(final @NonNull Registry<T> registry);
++}
+diff --git a/src/main/java/io/papermc/paper/registry/set/RegistryKeySetImpl.java b/src/main/java/io/papermc/paper/registry/set/RegistryKeySetImpl.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..c712181ad4c6a9d00bc04f8a48515a388c692f48
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/set/RegistryKeySetImpl.java
+@@ -0,0 +1,53 @@
++package io.papermc.paper.registry.set;
++
++import com.google.common.base.Preconditions;
++import io.papermc.paper.registry.RegistryAccess;
++import io.papermc.paper.registry.RegistryKey;
++import io.papermc.paper.registry.TypedKey;
++import java.util.ArrayList;
++import java.util.Collection;
++import java.util.Collections;
++import java.util.List;
++import org.bukkit.Keyed;
++import org.bukkit.NamespacedKey;
++import org.bukkit.Registry;
++import org.checkerframework.checker.nullness.qual.NonNull;
++import org.checkerframework.framework.qual.DefaultQualifier;
++import org.jetbrains.annotations.ApiStatus;
++import org.jetbrains.annotations.Nullable;
++
++@DefaultQualifier(NonNull.class)
++record RegistryKeySetImpl<T extends Keyed>(RegistryKey<T> registryKey, List<TypedKey<T>> values) implements RegistryKeySet<T> { // TODO remove Keyed
++
++ static <T extends Keyed> RegistryKeySet<T> create(final RegistryKey<T> registryKey, final Iterable<? extends T> values) { // TODO remove Keyed
++ final Registry<T> registry = RegistryAccess.registryAccess().getRegistry(registryKey);
++ final ArrayList<TypedKey<T>> keys = new ArrayList<>();
++ for (final T value : values) {
++ final @Nullable NamespacedKey key = registry.getKey(value);
++ Preconditions.checkArgument(key != null, value + " does not have a key in " + registryKey);
++ keys.add(TypedKey.create(registryKey, key));
++ }
++ return new RegistryKeySetImpl<>(registryKey, keys);
++ }
++
++ RegistryKeySetImpl {
++ values = List.copyOf(values);
++ }
++
++ @Override
++ public boolean contains(final TypedKey<T> valueKey) {
++ return this.values.contains(valueKey);
++ }
++
++ @Override
++ public Collection<T> resolve(final Registry<T> registry) {
++ final List<T> values = new ArrayList<>(this.values.size());
++ for (final TypedKey<T> key : this.values) {
++ final @Nullable T value = registry.get(key.key());
++ Preconditions.checkState(value != null, "Trying to access unbound TypedKey: " + key);
++ values.add(value);
++ }
++ return Collections.unmodifiableList(values);
++ }
++}
+diff --git a/src/main/java/io/papermc/paper/registry/set/RegistrySet.java b/src/main/java/io/papermc/paper/registry/set/RegistrySet.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..f414df1a114f9d727c27fecc55712b6f5ff415fb
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/set/RegistrySet.java
+@@ -0,0 +1,101 @@
++package io.papermc.paper.registry.set;
++
++import com.google.common.collect.Lists;
++import io.papermc.paper.registry.RegistryKey;
++import io.papermc.paper.registry.TypedKey;
++import io.papermc.paper.registry.tag.Tag;
++import java.util.Collection;
++import java.util.Iterator;
++import org.bukkit.Keyed;
++import org.checkerframework.checker.nullness.qual.NonNull;
++import org.jetbrains.annotations.ApiStatus;
++import org.jetbrains.annotations.Contract;
++import org.jetbrains.annotations.Unmodifiable;
++
++/**
++ * Represents a collection tied to a registry.
++ * <p>
++ * There are 2<!--3--> types of registry sets:
++ * <ul>
++ * <li>{@link Tag} which is a tag from vanilla or a datapack.
++ * These are obtained via {@link org.bukkit.Registry#getTag(io.papermc.paper.registry.tag.TagKey)}.</li>
++ * <li>{@link RegistryKeySet} which is a set of of values that are present in the registry. These are
++ * created via {@link #keySet(RegistryKey, Iterable)} or {@link #keySetFromValues(RegistryKey, Iterable)}.</li>
++ * <!-- <li>{@link RegistryValueSet} which is a set of values which are anonymous (don't have keys in the registry). These are
++ * created via {@link #valueSet(RegistryKey, Iterable)}.</li>-->
++ * </ul>
++ *
++ * @param <T> registry value type
++ */
++public sealed interface RegistrySet<T> permits RegistryKeySet, RegistryValueSet {
++
++ // TODO uncomment when direct holder sets need to be exposed to the API
++ // /**
++ // * Creates a {@link RegistryValueSet} from anonymous values.
++ // * <p>All values provided <b>must not</b> have keys in the given registry.</p>
++ // *
++ // * @param registryKey the registry key for the type of these values
++ // * @param values the values
++ // * @return a new registry set
++ // * @param <T> the type of the values
++ // */
++ // @Contract(value = "_, _ -> new", pure = true)
++ // static <T> @NonNull RegistryValueSet<T> valueSet(final @NonNull RegistryKey<T> registryKey, final @NonNull Iterable<? extends T> values) {
++ // return RegistryValueSetImpl.create(registryKey, values);
++ // }
++
++ /**
++ * Creates a {@link RegistryKeySet} from registry-backed values.
++ * <p>All values provided <b>must</b> have keys in the given registry.
++ * <!--For anonymous values, use {@link #valueSet(RegistryKey, Iterable)}--></p>
++ * <p>If references to actual objects are not available yet, use {@link #keySet(RegistryKey, Iterable)} to
++ * create an equivalent {@link RegistryKeySet} using just {@link TypedKey TypedKeys}.</p>
++ *
++ * @param registryKey the registry key for the owner of these values
++ * @param values the values
++ * @return a new registry set
++ * @param <T> the type of the values
++ * @throws IllegalArgumentException if the registry isn't available yet or if any value doesn't have a key in that registry
++ */
++ @Contract(value = "_, _ -> new", pure = true)
++ static <T extends Keyed> @NonNull RegistryKeySet<T> keySetFromValues(final @NonNull RegistryKey<T> registryKey, final @NonNull Iterable<? extends T> values) { // TODO remove Keyed
++ return RegistryKeySetImpl.create(registryKey, values);
++ }
++
++ /**
++ * Creates a direct {@link RegistrySet} from {@link TypedKey TypedKeys}.
++ *
++ * @param registryKey the registry key for the owner of these keys
++ * @param keys the keys for the values
++ * @return a new registry set
++ * @param <T> the type of the values
++ */
++ @SuppressWarnings("BoundedWildcard")
++ @Contract(value = "_, _ -> new", pure = true)
++ static <T extends Keyed> @NonNull RegistryKeySet<T> keySet(final @NonNull RegistryKey<T> registryKey, final @NonNull Iterable<TypedKey<T>> keys) { // TODO remove Keyed
++ return new RegistryKeySetImpl<>(registryKey, Lists.newArrayList(keys));
++ }
++
++ /**
++ * Get the registry key for this set.
++ *
++ * @return the registry key
++ */
++ @NonNull RegistryKey<T> registryKey();
++
++ /**
++ * Get the size of this set.
++ *
++ * @return the size
++ */
++ int size();
++
++ /**
++ * Checks if this set contains the value with the given key.
++ *
++ * @param valueKey the key to check
++ * @return true if the value is in this set
++ */
++ boolean contains(@NonNull TypedKey<T> valueKey);
++}
+diff --git a/src/main/java/io/papermc/paper/registry/set/RegistryValueSet.java b/src/main/java/io/papermc/paper/registry/set/RegistryValueSet.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..9cbbb507738a4cdcd9f48a36ac00d2d5628a7f94
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/set/RegistryValueSet.java
+@@ -0,0 +1,45 @@
++package io.papermc.paper.registry.set;
++
++import io.papermc.paper.registry.TypedKey;
++import java.util.Collection;
++import org.checkerframework.checker.nullness.qual.NonNull;
++import org.jetbrains.annotations.ApiStatus;
++import org.jetbrains.annotations.Contract;
++import org.jetbrains.annotations.Unmodifiable;
++
++/**
++ * A collection of anonymous values relating to a registry. These
++ * are values of the same type as the registry, but will not be found
++ * in the registry, hence, anonymous.
++ * @param <T> registry value type
++ */
++public sealed interface RegistryValueSet<T> extends RegistrySet<T> permits RegistryValueSetImpl {
++
++ @Override
++ default int size() {
++ return this.values().size();
++ }
++
++ /**
++ * {@inheritDoc}
++ * <p>Will always return false because these are anonymous values</p>
++ */
++ @Contract("_ -> false")
++ @Override
++ default boolean contains(final @NonNull TypedKey<T> valueKey) {
++ return false;
++ }
++
++ @Override
++ default @NonNull @Unmodifiable Collection<T> resolve() {
++ return this.values();
++ }
++
++ /**
++ * Get the collection of values in this direct set.
++ *
++ * @return the values
++ */
++ @NonNull @Unmodifiable Collection<T> values();
++}
+diff --git a/src/main/java/io/papermc/paper/registry/set/RegistryValueSetImpl.java b/src/main/java/io/papermc/paper/registry/set/RegistryValueSetImpl.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..4ce5b26a1fcaae7b28ac8ed3c25014b66c266318
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/set/RegistryValueSetImpl.java
+@@ -0,0 +1,18 @@
++package io.papermc.paper.registry.set;
++
++import com.google.common.collect.Lists;
++import io.papermc.paper.registry.RegistryKey;
++import java.util.List;
++import org.jetbrains.annotations.ApiStatus;
++
++record RegistryValueSetImpl<T>(RegistryKey<T> registryKey, List<T> values) implements RegistryValueSet<T> {
++
++ RegistryValueSetImpl {
++ values = List.copyOf(values);
++ }
++
++ static <T> RegistryValueSet<T> create(final RegistryKey<T> registryKey, final Iterable<? extends T> values) {
++ return new RegistryValueSetImpl<>(registryKey, Lists.newArrayList(values));
++ }
++}
+diff --git a/src/main/java/io/papermc/paper/registry/tag/Tag.java b/src/main/java/io/papermc/paper/registry/tag/Tag.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..5fce47a5a8a46f9f144d9adb4257a00a4ea84315
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/tag/Tag.java
+@@ -0,0 +1,24 @@
++package io.papermc.paper.registry.tag;
++
++import io.papermc.paper.registry.set.RegistryKeySet;
++import org.checkerframework.checker.nullness.qual.NonNull;
++import org.jetbrains.annotations.ApiStatus;
++
++/**
++ * A named {@link RegistryKeySet} which are created
++ * via the datapack tag system.
++ *
++ * @param <T>
++ * @see org.bukkit.Tag
++ * @see org.bukkit.Registry#getTag(TagKey)
++ */
++public interface Tag<T> extends RegistryKeySet<T> {
++
++ /**
++ * Get the identifier for this named set.
++ *
++ * @return the tag key identifier
++ */
++ @NonNull TagKey<T> tagKey();
++}
+diff --git a/src/main/java/io/papermc/paper/registry/tag/TagKey.java b/src/main/java/io/papermc/paper/registry/tag/TagKey.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..a49d328e95f7fda6567ee6c4f5f1878a2c187277
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/tag/TagKey.java
+@@ -0,0 +1,32 @@
++package io.papermc.paper.registry.tag;
++
++import io.papermc.paper.registry.RegistryKey;
++import net.kyori.adventure.key.Key;
++import net.kyori.adventure.key.Keyed;
++import org.checkerframework.checker.nullness.qual.NonNull;
++import org.jetbrains.annotations.ApiStatus;
++import org.jetbrains.annotations.Contract;
++
++public sealed interface TagKey<T> extends Keyed permits TagKeyImpl {
++
++ /**
++ * Creates a new tag key for a registry.
++ *
++ * @param registryKey the registry for the tag
++ * @param key the specific key for the tag
++ * @return a new tag key
++ * @param <T> the registry value type
++ */
++ @Contract(value = "_, _ -> new", pure = true)
++ static <T> @NonNull TagKey<T> create(final @NonNull RegistryKey<T> registryKey, final @NonNull Key key) {
++ return new TagKeyImpl<>(registryKey, key);
++ }
++
++ /**
++ * Get the registry key for this tag key.
++ *
++ * @return the registry key
++ */
++ @NonNull RegistryKey<T> registryKey();
++}
+diff --git a/src/main/java/io/papermc/paper/registry/tag/TagKeyImpl.java b/src/main/java/io/papermc/paper/registry/tag/TagKeyImpl.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..11d19e339c7c62f2eb4467277552c27e4e83069c
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/tag/TagKeyImpl.java
+@@ -0,0 +1,12 @@
++package io.papermc.paper.registry.tag;
++
++import io.papermc.paper.registry.RegistryKey;
++import net.kyori.adventure.key.Key;
++import org.checkerframework.checker.nullness.qual.NonNull;
++import org.checkerframework.framework.qual.DefaultQualifier;
++import org.jetbrains.annotations.ApiStatus;
++
++@DefaultQualifier(NonNull.class)
++record TagKeyImpl<T>(RegistryKey<T> registryKey, Key key) implements TagKey<T> {
++}
+diff --git a/src/main/java/org/bukkit/Registry.java b/src/main/java/org/bukkit/Registry.java
+index 240500771bac439156b4c5d93ff047ae45d56154..dd937b18f93987f4c481d154bb263ef194bd4dc4 100644
+--- a/src/main/java/org/bukkit/Registry.java
++++ b/src/main/java/org/bukkit/Registry.java
+@@ -146,7 +146,23 @@ public interface Registry<T extends Keyed> extends Iterable<T> {
+ *
+ * @see EntityType
+ */
+- Registry<EntityType> ENTITY_TYPE = new SimpleRegistry<>(EntityType.class, (entity) -> entity != EntityType.UNKNOWN);
++ // Paper start - support tags here
++ Registry<EntityType> ENTITY_TYPE = new SimpleRegistry<>(EntityType.class, (entity) -> entity != EntityType.UNKNOWN) {
++ @Override
++ public boolean hasTag(final io.papermc.paper.registry.tag.@NotNull TagKey<EntityType> key) {
++ return Bukkit.getUnsafe().getTag(key) != null;
++ }
++
++ @Override
++ public io.papermc.paper.registry.tag.@NotNull Tag<EntityType> getTag(final io.papermc.paper.registry.tag.@NotNull TagKey<EntityType> key) {
++ final io.papermc.paper.registry.tag.Tag<EntityType> tag = Bukkit.getUnsafe().getTag(key);
++ if (tag == null) {
++ throw new java.util.NoSuchElementException("No tag " + key + " found");
++ }
++ return tag;
++ }
++ };
++ // Paper end - support tags here
+ /**
+ * Server instruments.
+ *
+@@ -290,7 +306,23 @@ public interface Registry<T extends Keyed> extends Iterable<T> {
+ *
+ * @see Fluid
+ */
+- Registry<Fluid> FLUID = new SimpleRegistry<>(Fluid.class);
++ // Paper start - support tags here
++ Registry<Fluid> FLUID = new SimpleRegistry<>(Fluid.class) {
++ @Override
++ public boolean hasTag(final io.papermc.paper.registry.tag.@NotNull TagKey<Fluid> key) {
++ return Bukkit.getUnsafe().getTag(key) != null;
++ }
++
++ @Override
++ public io.papermc.paper.registry.tag.@NotNull Tag<Fluid> getTag(final io.papermc.paper.registry.tag.@NotNull TagKey<Fluid> key) {
++ final io.papermc.paper.registry.tag.Tag<Fluid> tag = Bukkit.getUnsafe().getTag(key);
++ if (tag == null) {
++ throw new java.util.NoSuchElementException("No tag " + key + " found");
++ }
++ return tag;
++ }
++ };
++ // Paper end - support tags here
+ /**
+ * Frog variants.
+ *
+@@ -448,6 +480,30 @@ public interface Registry<T extends Keyed> extends Iterable<T> {
+ }
+ // Paper end - improve Registry
+
++ // Paper start - RegistrySet API
++ /**
++ * Checks if this registry has a tag with the given key.
++ *
++ * @param key the key to check for
++ * @return true if this registry has a tag with the given key, false otherwise
++ */
++ @ApiStatus.Experimental
++ default boolean hasTag(final io.papermc.paper.registry.tag.@NotNull TagKey<T> key) {
++ throw new UnsupportedOperationException(this + " doesn't have tags");
++ }
++
++ /**
++ * Gets the named registry set (tag) for the given key.
++ *
++ * @param key the key to get the tag for
++ * @return the tag for the key
++ */
++ @ApiStatus.Experimental
++ default @NotNull io.papermc.paper.registry.tag.Tag<T> getTag(final io.papermc.paper.registry.tag.@NotNull TagKey<T> key) {
++ throw new UnsupportedOperationException(this + " doesn't have tags");
++ }
++ // Paper end - RegistrySet API
++
+ /**
+ * Returns a new stream, which contains all registry items, which are registered to the registry.
+ *
+@@ -477,7 +533,7 @@ public interface Registry<T extends Keyed> extends Iterable<T> {
+ return (namespacedKey != null) ? get(namespacedKey) : null;
+ }
+
+- static final class SimpleRegistry<T extends Enum<T> & Keyed> implements Registry<T> {
++ static class SimpleRegistry<T extends Enum<T> & Keyed> implements Registry<T> { // Paper - not final
+
+ private final Class<T> type;
+ private final Map<NamespacedKey, T> map;
+diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java
+index 0e9ccfee7a03d341e7c4d271f53b4ed168b404ef..7332034bb1753f48f7904dafab1ef4b3ee117ea3 100644
+--- a/src/main/java/org/bukkit/UnsafeValues.java
++++ b/src/main/java/org/bukkit/UnsafeValues.java
+@@ -275,4 +275,6 @@ public interface UnsafeValues {
+ // Paper end - lifecycle event API
+
+ @NotNull java.util.List<net.kyori.adventure.text.Component> computeTooltipLines(@NotNull ItemStack itemStack, @NotNull io.papermc.paper.inventory.tooltip.TooltipContext tooltipContext, @Nullable org.bukkit.entity.Player player); // Paper - expose itemstack tooltip lines
++
++ <A extends Keyed, M> io.papermc.paper.registry.tag.@Nullable Tag<A> getTag(io.papermc.paper.registry.tag.@NotNull TagKey<A> tagKey); // Paper - hack to get tags for non-server backed registries
+ }
diff --git a/patches/server/0991-Registry-Modification-API.patch b/patches/server/1001-Registry-Modification-API.patch
index c770d38c3a..e139aba408 100644
--- a/patches/server/0991-Registry-Modification-API.patch
+++ b/patches/server/1001-Registry-Modification-API.patch
@@ -4,6 +4,7 @@ Date: Mon, 27 Feb 2023 18:28:39 -0800
Subject: [PATCH] Registry Modification API
== AT ==
+public net.minecraft.server.RegistryLayer STATIC_ACCESS
public net.minecraft.core.MappedRegistry validateWrite(Lnet/minecraft/resources/ResourceKey;)V
diff --git a/src/main/java/io/papermc/paper/registry/PaperRegistries.java b/src/main/java/io/papermc/paper/registry/PaperRegistries.java
@@ -69,7 +70,7 @@ index 0000000000000000000000000000000000000000..4cf32102a134ebef67d3893cfd24bf0a
+}
diff --git a/src/main/java/io/papermc/paper/registry/PaperRegistryListenerManager.java b/src/main/java/io/papermc/paper/registry/PaperRegistryListenerManager.java
new file mode 100644
-index 0000000000000000000000000000000000000000..e2605a9324b9dc91cd910e4bb974d5a911b58b1f
+index 0000000000000000000000000000000000000000..6b3ba059b4f530b9e3d1c3d6bbc033e96c6a9bc2
--- /dev/null
+++ b/src/main/java/io/papermc/paper/registry/PaperRegistryListenerManager.java
@@ -0,0 +1,167 @@
@@ -116,7 +117,7 @@ index 0000000000000000000000000000000000000000..e2605a9324b9dc91cd910e4bb974d5a9
+ * For {@link Registry#register(Registry, String, Object)}
+ */
+ public <M> M registerWithListeners(final Registry<M> registry, final String id, final M nms) {
-+ return this.registerWithListeners(registry, ResourceLocation.withDefaultNamespace(id), nms);
++ return this.registerWithListeners(registry, new ResourceLocation(id), nms);
+ }
+
+ /**
@@ -804,7 +805,7 @@ index 44b7927081b476813505cab6b3a2da2ec2942c54..c96bcd815ec9bf76625b5bedef461da4
}
}
diff --git a/src/main/java/net/minecraft/resources/RegistryDataLoader.java b/src/main/java/net/minecraft/resources/RegistryDataLoader.java
-index abadf4abe08dc3bb6612b42cbb3f7df3ffa28ce9..56d8dcf2f3fd042ed1e7ed01a0ce5d90c89ee502 100644
+index abadf4abe08dc3bb6612b42cbb3f7df3ffa28ce9..ce65f2ea462b920e5e14b8cb687ef371c311d242 100644
--- a/src/main/java/net/minecraft/resources/RegistryDataLoader.java
+++ b/src/main/java/net/minecraft/resources/RegistryDataLoader.java
@@ -136,6 +136,7 @@ public class RegistryDataLoader {
@@ -820,23 +821,10 @@ index abadf4abe08dc3bb6612b42cbb3f7df3ffa28ce9..56d8dcf2f3fd042ed1e7ed01a0ce5d90
DataResult<E> dataResult = decoder.parse(ops, jsonElement);
E object = dataResult.getOrThrow();
- registry.register(key, object, entryInfo);
-+ io.papermc.paper.registry.PaperRegistryListenerManager.INSTANCE.registerWithListeners(registry, key, object, entryInfo); // Paper - register with listeners
++ io.papermc.paper.registry.PaperRegistryListenerManager.INSTANCE.registerWithListeners(registry, key, object, entryInfo); // Paper
}
}
-diff --git a/src/main/java/net/minecraft/server/ReloadableServerRegistries.java b/src/main/java/net/minecraft/server/ReloadableServerRegistries.java
-index 397bdacab9517354875ebc0bc68d35059b3c318b..705fe42b52414fbac14ec107fda5ad0989a5a115 100644
---- a/src/main/java/net/minecraft/server/ReloadableServerRegistries.java
-+++ b/src/main/java/net/minecraft/server/ReloadableServerRegistries.java
-@@ -66,7 +66,7 @@ public class ReloadableServerRegistries {
- SimpleJsonResourceReloadListener.scanDirectory(resourceManager, string, GSON, map);
- map.forEach(
- (id, json) -> type.deserialize(id, ops, json)
-- .ifPresent(value -> writableRegistry.register(ResourceKey.create(type.registryKey(), id), (T)value, DEFAULT_REGISTRATION_INFO))
-+ .ifPresent(value -> io.papermc.paper.registry.PaperRegistryListenerManager.INSTANCE.registerWithListeners(writableRegistry, ResourceKey.create(type.registryKey(), id), value, DEFAULT_REGISTRATION_INFO)) // Paper - register with listeners
- );
- return writableRegistry;
- },
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java b/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java
index d21b7e39d71c785f47f790e1ad4be33a8e8e6e51..43d686a9958cff96f5b15d93e920c8f2313aa65b 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java
diff --git a/patches/server/1002-RegistrySet-API.patch b/patches/server/1002-RegistrySet-API.patch
new file mode 100644
index 0000000000..a90bdc3168
--- /dev/null
+++ b/patches/server/1002-RegistrySet-API.patch
@@ -0,0 +1,224 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Sat, 11 May 2024 19:26:18 -0700
+Subject: [PATCH] RegistrySet API
+
+
+diff --git a/src/main/java/io/papermc/paper/registry/legacy/DelayedRegistry.java b/src/main/java/io/papermc/paper/registry/legacy/DelayedRegistry.java
+index 5562e8da5ebaef2a3add46e88d64358b7737b59e..e5880f76cdb8ebf01fcefdf77ba9b95674b997a8 100644
+--- a/src/main/java/io/papermc/paper/registry/legacy/DelayedRegistry.java
++++ b/src/main/java/io/papermc/paper/registry/legacy/DelayedRegistry.java
+@@ -1,12 +1,13 @@
+ package io.papermc.paper.registry.legacy;
+
++import io.papermc.paper.registry.tag.Tag;
++import io.papermc.paper.registry.tag.TagKey;
+ import java.util.Iterator;
+ import java.util.function.Supplier;
+ import java.util.stream.Stream;
+ import org.bukkit.Keyed;
+ import org.bukkit.NamespacedKey;
+ import org.bukkit.Registry;
+-import org.bukkit.craftbukkit.CraftRegistry;
+ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
+ import org.checkerframework.checker.nullness.qual.Nullable;
+ import org.jetbrains.annotations.NotNull;
+@@ -52,4 +53,14 @@ public final class DelayedRegistry<T extends Keyed, R extends Registry<T>> imple
+ public NamespacedKey getKey(final T value) {
+ return this.delegate().getKey(value);
+ }
++
++ @Override
++ public boolean hasTag(final TagKey<T> key) {
++ return this.delegate().hasTag(key);
++ }
++
++ @Override
++ public @NotNull Tag<T> getTag(final TagKey<T> key) {
++ return this.delegate().getTag(key);
++ }
+ }
+diff --git a/src/main/java/io/papermc/paper/registry/set/NamedRegistryKeySetImpl.java b/src/main/java/io/papermc/paper/registry/set/NamedRegistryKeySetImpl.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..918d80542a1185988fcda3d7642548c7935f73af
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/set/NamedRegistryKeySetImpl.java
+@@ -0,0 +1,75 @@
++package io.papermc.paper.registry.set;
++
++import com.google.common.collect.ImmutableList;
++import io.papermc.paper.adventure.PaperAdventure;
++import io.papermc.paper.registry.RegistryAccess;
++import io.papermc.paper.registry.RegistryKey;
++import io.papermc.paper.registry.TypedKey;
++import io.papermc.paper.registry.tag.Tag;
++import io.papermc.paper.registry.tag.TagKey;
++import java.util.Collection;
++import java.util.Set;
++import net.kyori.adventure.key.Key;
++import net.minecraft.core.Holder;
++import net.minecraft.core.HolderSet;
++import org.bukkit.Keyed;
++import org.bukkit.NamespacedKey;
++import org.bukkit.Registry;
++import org.bukkit.craftbukkit.util.CraftNamespacedKey;
++import org.checkerframework.checker.nullness.qual.NonNull;
++import org.checkerframework.framework.qual.DefaultQualifier;
++import org.jetbrains.annotations.NotNull;
++import org.jetbrains.annotations.Unmodifiable;
++
++@DefaultQualifier(NonNull.class)
++public record NamedRegistryKeySetImpl<T extends Keyed, M>( // TODO remove Keyed
++ TagKey<T> tagKey,
++ HolderSet.Named<M> namedSet
++) implements Tag<T>, org.bukkit.Tag<T> {
++
++ @Override
++ public @Unmodifiable Collection<TypedKey<T>> values() {
++ final ImmutableList.Builder<TypedKey<T>> builder = ImmutableList.builder();
++ for (final Holder<M> holder : this.namedSet) {
++ builder.add(TypedKey.create(this.tagKey.registryKey(), CraftNamespacedKey.fromMinecraft(((Holder.Reference<?>) holder).key().location())));
++ }
++ return builder.build();
++ }
++
++ @Override
++ public RegistryKey<T> registryKey() {
++ return this.tagKey.registryKey();
++ }
++
++ @Override
++ public boolean contains(final TypedKey<T> valueKey) {
++ return this.namedSet.stream().anyMatch(h -> {
++ return ((Holder.Reference<?>) h).key().location().equals(PaperAdventure.asVanilla(valueKey.key()));
++ });
++ }
++
++ @Override
++ public @Unmodifiable Collection<T> resolve(final Registry<T> registry) {
++ final ImmutableList.Builder<T> builder = ImmutableList.builder();
++ for (final Holder<M> holder : this.namedSet) {
++ builder.add(registry.getOrThrow(CraftNamespacedKey.fromMinecraft(((Holder.Reference<?>) holder).key().location())));
++ }
++ return builder.build();
++ }
++
++ @Override
++ public boolean isTagged(final T item) {
++ return this.getValues().contains(item);
++ }
++
++ @Override
++ public Set<T> getValues() {
++ return Set.copyOf(this.resolve(RegistryAccess.registryAccess().getRegistry(this.registryKey())));
++ }
++
++ @Override
++ public @NotNull NamespacedKey getKey() {
++ final Key key = this.tagKey().key();
++ return new NamespacedKey(key.namespace(), key.value());
++ }
++}
+diff --git a/src/main/java/io/papermc/paper/registry/set/PaperRegistrySets.java b/src/main/java/io/papermc/paper/registry/set/PaperRegistrySets.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..c3d5545824189f83308f5a62e3a237efaea2bfe1
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/set/PaperRegistrySets.java
+@@ -0,0 +1,52 @@
++package io.papermc.paper.registry.set;
++
++import io.papermc.paper.adventure.PaperAdventure;
++import io.papermc.paper.registry.PaperRegistries;
++import io.papermc.paper.registry.RegistryKey;
++import io.papermc.paper.registry.TypedKey;
++import io.papermc.paper.registry.tag.TagKey;
++import java.util.ArrayList;
++import java.util.List;
++import net.minecraft.core.Holder;
++import net.minecraft.core.HolderSet;
++import net.minecraft.core.Registry;
++import net.minecraft.resources.ResourceKey;
++import org.bukkit.Keyed;
++import org.bukkit.craftbukkit.CraftRegistry;
++import org.bukkit.craftbukkit.util.CraftNamespacedKey;
++import org.checkerframework.checker.nullness.qual.NonNull;
++import org.checkerframework.framework.qual.DefaultQualifier;
++
++@DefaultQualifier(NonNull.class)
++public final class PaperRegistrySets {
++
++ public static <A extends Keyed, M> HolderSet<M> convertToNms(final ResourceKey<? extends Registry<M>> resourceKey, final RegistryKeySet<A> registryKeySet) { // TODO remove Keyed
++ if (registryKeySet instanceof NamedRegistryKeySetImpl<A, ?>) {
++ return ((NamedRegistryKeySetImpl<A, M>) registryKeySet).namedSet();
++ } else {
++ final Registry<M> registry = CraftRegistry.getMinecraftRegistry().registryOrThrow(resourceKey);
++ return HolderSet.direct(key -> {
++ return registry.getHolderOrThrow(ResourceKey.create(resourceKey, PaperAdventure.asVanilla(key.key())));
++ }, registryKeySet.values());
++ }
++ }
++
++ public static <A extends Keyed, M> RegistryKeySet<A> convertToApi(final RegistryKey<A> registryKey, final HolderSet<M> holders) { // TODO remove Keyed
++ if (holders instanceof final HolderSet.Named<M> named) {
++ final RegistryKey<A> apiRegistryKey = PaperRegistries.fromNms(named.key().registry());
++ return new NamedRegistryKeySetImpl<>(TagKey.create(apiRegistryKey, CraftNamespacedKey.fromMinecraft(named.key().location())), named);
++ } else {
++ final List<TypedKey<A>> keys = new ArrayList<>();
++ for (final Holder<M> holder : holders) {
++ if (!(holder instanceof final Holder.Reference<M> reference)) {
++ throw new UnsupportedOperationException("Cannot convert a holder set containing direct holders");
++ }
++ keys.add(TypedKey.create(registryKey, CraftNamespacedKey.fromMinecraft(reference.key().location())));
++ }
++ return RegistrySet.keySet(registryKey, keys);
++ }
++ }
++
++ private PaperRegistrySets() {
++ }
++}
+diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java b/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java
+index 43d686a9958cff96f5b15d93e920c8f2313aa65b..f0248e3d3782b1f6b4ff209502f626d66c05647b 100644
+--- a/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java
++++ b/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java
+@@ -233,4 +233,17 @@ public class CraftRegistry<B extends Keyed, M> implements Registry<B> {
+ return this.byValue.get(value);
+ }
+ // Paper end - improve Registry
++
++ // Paper start - RegistrySet API
++ @Override
++ public boolean hasTag(final io.papermc.paper.registry.tag.TagKey<B> key) {
++ return this.minecraftRegistry.getTag(net.minecraft.tags.TagKey.create(this.minecraftRegistry.key(), io.papermc.paper.adventure.PaperAdventure.asVanilla(key.key()))).isPresent();
++ }
++
++ @Override
++ public io.papermc.paper.registry.tag.Tag<B> getTag(final io.papermc.paper.registry.tag.TagKey<B> key) {
++ final net.minecraft.core.HolderSet.Named<M> namedHolderSet = this.minecraftRegistry.getTag(net.minecraft.tags.TagKey.create(this.minecraftRegistry.key(), io.papermc.paper.adventure.PaperAdventure.asVanilla(key.key()))).orElseThrow();
++ return new io.papermc.paper.registry.set.NamedRegistryKeySetImpl<>(key, namedHolderSet);
++ }
++ // Paper end - RegistrySet API
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
+index 5a04134973dd1db7f778a57ec5f185feec370990..3885dcf255bf8eea19d311da15d4525234983f17 100644
+--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
+@@ -685,6 +685,18 @@ public final class CraftMagicNumbers implements UnsafeValues {
+ }
+ // Paper end - lifecycle event API
+
++ // Paper start - hack to get tags for non server-backed registries
++ @Override
++ public <A extends Keyed, M> io.papermc.paper.registry.tag.Tag<A> getTag(final io.papermc.paper.registry.tag.TagKey<A> tagKey) { // TODO remove Keyed
++ final net.minecraft.resources.ResourceKey<? extends net.minecraft.core.Registry<M>> nmsKey = io.papermc.paper.registry.PaperRegistries.toNms(tagKey.registryKey());
++ final net.minecraft.core.Registry<M> nmsRegistry = org.bukkit.craftbukkit.CraftRegistry.getMinecraftRegistry().registryOrThrow(nmsKey);
++ return nmsRegistry
++ .getTag(net.minecraft.tags.TagKey.create(nmsKey, io.papermc.paper.adventure.PaperAdventure.asVanilla(tagKey.key())))
++ .map(named -> new io.papermc.paper.registry.set.NamedRegistryKeySetImpl<>(tagKey, named))
++ .orElse(null);
++ }
++ // Paper end - hack to get tags for non server-backed registries
++
+ /**
+ * This helper class represents the different NBT Tags.
+ * <p>