Paper/patches/api/0475-Registry-Modification-API.patch
Nassim Jahnke dd11ef8441
Updated Upstream (Bukkit/CraftBukkit/Spigot) (#11102)
Upstream has released updates that appear to apply and compile correctly.
This update has not been tested by PaperMC and as with ANY update, please do your own testing

Bukkit Changes:
3a3bea52 SPIGOT-7829: Increase maximum outgoing plugin message size to match Vanilla intention
5cd1c8cb SPIGOT-7831: Add CreatureSpawnEvent.SpawnReason#POTION_EFFECT
a8e278f0 SPIGOT-7827: Sync EntityPortalEvent with PlayerPortalEvent since non-players can now create portals
53729d12 Remove spurious ApiStatus.Internal annotation
b9f57486 SPIGOT-7799, PR-1039: Expose explosion world interaction in EntityExplodeEvent and BlockExplodeEvent
7983b966 PR-1029: Trial changing a small number of inner enums to classes/interfaces to better support custom values

CraftBukkit Changes:
403accd56 SPIGOT-7831: Add CreatureSpawnEvent.SpawnReason#POTION_EFFECT
812761660 Increase outdated build delay
bed1e3ff6 SPIGOT-7827: Sync EntityPortalEvent with PlayerPortalEvent since non-players can now create portals
2444c8b23 SPIGOT-7823: Suspicious sand and gravel material are not marked as having gravity correctly
aceddcd0b SPIGOT-7820: Enum changes - duplicate method name
a0d2d6a84 SPIGOT-7813: Material#isInteractable() always returns false
8fd64b091 SPIGOT-7806: Handle both loot and inventory item drop behaviour in PlayerDeathEvent
a4ee40b74 SPIGOT-7799, PR-1436: Expose explosion world interaction in EntityExplodeEvent and BlockExplodeEvent
082aa51c5 PR-1424: Trial changing a small number of inner enums to classes/interfaces to better support custom values
66e78a96b SPIGOT-7815: Consider EntityDamageEvent status for Wolf armor damage

Spigot Changes:
5bbef5ad SPIGOT-7834: Modify max value for generic.max_absorption
2024-07-18 10:13:20 +02:00

894 lines
35 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Wed, 2 Mar 2022 13:36:21 -0800
Subject: [PATCH] Registry Modification API
diff --git a/src/main/java/io/papermc/paper/registry/RegistryBuilder.java b/src/main/java/io/papermc/paper/registry/RegistryBuilder.java
new file mode 100644
index 0000000000000000000000000000000000000000..6edf300c1d81c1001756141c9efd022ba0e372cd
--- /dev/null
+++ b/src/main/java/io/papermc/paper/registry/RegistryBuilder.java
@@ -0,0 +1,13 @@
+package io.papermc.paper.registry;
+
+import org.jetbrains.annotations.ApiStatus;
+
+/**
+ * To be implemented by any type used for modifying registries.
+ *
+ * @param <T> registry value type
+ */
+@ApiStatus.NonExtendable
+@ApiStatus.Experimental
+public interface RegistryBuilder<T> {
+}
diff --git a/src/main/java/io/papermc/paper/registry/event/RegistryEntryAddEvent.java b/src/main/java/io/papermc/paper/registry/event/RegistryEntryAddEvent.java
new file mode 100644
index 0000000000000000000000000000000000000000..a5d7385eae9dfb88b52aed0e42c09a10ef807385
--- /dev/null
+++ b/src/main/java/io/papermc/paper/registry/event/RegistryEntryAddEvent.java
@@ -0,0 +1,47 @@
+package io.papermc.paper.registry.event;
+
+import io.papermc.paper.registry.RegistryBuilder;
+import io.papermc.paper.registry.TypedKey;
+import io.papermc.paper.registry.tag.Tag;
+import io.papermc.paper.registry.tag.TagKey;
+import org.bukkit.Keyed;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.jetbrains.annotations.ApiStatus;
+
+/**
+ * Event object for {@link RegistryEventProvider#entryAdd()}. This
+ * event is fired right before a specific entry is registered in/added to registry.
+ * It provides a way for plugins to modify parts of this entry.
+ *
+ * @param <T> registry entry type
+ * @param <B> registry entry builder type
+ */
+@ApiStatus.Experimental
+@ApiStatus.NonExtendable
+public interface RegistryEntryAddEvent<T, B extends RegistryBuilder<T>> extends RegistryEvent<T> {
+
+ /**
+ * Gets the builder for the entry being added to the registry.
+ *
+ * @return the object builder
+ */
+ @NonNull B builder();
+
+ /**
+ * Gets the key for this entry in the registry.
+ *
+ * @return the key
+ */
+ @NonNull TypedKey<T> key();
+
+ /**
+ * Gets or creates a tag for the given tag key. This tag
+ * is then required to be filled either from the built-in or
+ * custom datapack.
+ *
+ * @param tagKey the tag key
+ * @return the tag
+ * @param <V> the tag value type
+ */
+ @NonNull <V extends Keyed> Tag<V> getOrCreateTag(@NonNull TagKey<V> tagKey);
+}
diff --git a/src/main/java/io/papermc/paper/registry/event/RegistryEvent.java b/src/main/java/io/papermc/paper/registry/event/RegistryEvent.java
new file mode 100644
index 0000000000000000000000000000000000000000..498c5920926158e86c2aec2bd2129d5e8b8613a3
--- /dev/null
+++ b/src/main/java/io/papermc/paper/registry/event/RegistryEvent.java
@@ -0,0 +1,23 @@
+package io.papermc.paper.registry.event;
+
+import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent;
+import io.papermc.paper.registry.RegistryKey;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.jetbrains.annotations.ApiStatus;
+
+/**
+ * Base type for all registry events.
+ *
+ * @param <T> registry entry type
+ */
+@ApiStatus.Experimental
+@ApiStatus.NonExtendable
+public interface RegistryEvent<T> extends LifecycleEvent {
+
+ /**
+ * Get the key for the registry this event pertains to.
+ *
+ * @return the registry key
+ */
+ @NonNull RegistryKey<T> registryKey();
+}
diff --git a/src/main/java/io/papermc/paper/registry/event/RegistryEventProvider.java b/src/main/java/io/papermc/paper/registry/event/RegistryEventProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..477ed0fd5acc923d429980529876f0dd7dd3a52a
--- /dev/null
+++ b/src/main/java/io/papermc/paper/registry/event/RegistryEventProvider.java
@@ -0,0 +1,58 @@
+package io.papermc.paper.registry.event;
+
+import io.papermc.paper.plugin.bootstrap.BootstrapContext;
+import io.papermc.paper.plugin.lifecycle.event.handler.LifecycleEventHandler;
+import io.papermc.paper.plugin.lifecycle.event.handler.configuration.LifecycleEventHandlerConfiguration;
+import io.papermc.paper.plugin.lifecycle.event.handler.configuration.PrioritizedLifecycleEventHandlerConfiguration;
+import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEventType;
+import io.papermc.paper.registry.RegistryBuilder;
+import io.papermc.paper.registry.RegistryKey;
+import io.papermc.paper.registry.event.type.RegistryEntryAddEventType;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.jetbrains.annotations.ApiStatus;
+
+/**
+ * Provider for registry events for a specific registry.
+ * <p>
+ * Supported events are:
+ * <ul>
+ * <li>{@link RegistryEntryAddEvent} (via {@link #entryAdd()})</li>
+ * <li>{@link RegistryFreezeEvent} (via {@link #freeze()})</li>
+ * </ul>
+ *
+ * @param <T> registry entry type
+ * @param <B> registry entry builder type
+ */
+@ApiStatus.Experimental
+@ApiStatus.NonExtendable
+public interface RegistryEventProvider<T, B extends RegistryBuilder<T>> {
+
+ /**
+ * Gets the event type for {@link RegistryEntryAddEvent} which is fired just before
+ * an object is added to a registry.
+ * <p>
+ * Can be used in {@link io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager#registerEventHandler(LifecycleEventType, LifecycleEventHandler)}
+ * to register a handler for {@link RegistryEntryAddEvent}.
+ *
+ * @return the registry entry add event type
+ */
+ @NonNull RegistryEntryAddEventType<T, B> entryAdd();
+
+ /**
+ * Gets the event type for {@link RegistryFreezeEvent} which is fired just before
+ * a registry is frozen. It allows for the registration of new objects.
+ * <p>
+ * Can be used in {@link io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager#registerEventHandler(LifecycleEventType, LifecycleEventHandler)}
+ * to register a handler for {@link RegistryFreezeEvent}.
+ *
+ * @return the registry freeze event type
+ */
+ LifecycleEventType.@NonNull Prioritizable<BootstrapContext, RegistryFreezeEvent<T, B>> freeze();
+
+ /**
+ * Gets the registry key associated with this event type provider.
+ *
+ * @return the registry key
+ */
+ @NonNull RegistryKey<T> registryKey();
+}
diff --git a/src/main/java/io/papermc/paper/registry/event/RegistryEventProviderImpl.java b/src/main/java/io/papermc/paper/registry/event/RegistryEventProviderImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..cfe47c8bd0888db6d00867acfefc8db42ef314aa
--- /dev/null
+++ b/src/main/java/io/papermc/paper/registry/event/RegistryEventProviderImpl.java
@@ -0,0 +1,30 @@
+package io.papermc.paper.registry.event;
+
+import io.papermc.paper.plugin.bootstrap.BootstrapContext;
+import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEventType;
+import io.papermc.paper.registry.RegistryBuilder;
+import io.papermc.paper.registry.RegistryKey;
+import io.papermc.paper.registry.event.type.RegistryEntryAddEventType;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.framework.qual.DefaultQualifier;
+import org.jetbrains.annotations.ApiStatus;
+
+@ApiStatus.Internal
+@DefaultQualifier(NonNull.class)
+record RegistryEventProviderImpl<T, B extends RegistryBuilder<T>>(RegistryKey<T> registryKey) implements RegistryEventProvider<T, B> {
+
+ static <T, B extends RegistryBuilder<T>> RegistryEventProvider<T, B> create(final RegistryKey<T> registryKey) {
+ return new RegistryEventProviderImpl<>(registryKey);
+ }
+
+ @Override
+ public RegistryEntryAddEventType<T, B> entryAdd() {
+ return RegistryEventTypeProvider.provider().registryEntryAdd(this);
+ }
+
+ @Override
+ public LifecycleEventType.Prioritizable<BootstrapContext, RegistryFreezeEvent<T, B>> freeze() {
+ return RegistryEventTypeProvider.provider().registryFreeze(this);
+ }
+
+}
diff --git a/src/main/java/io/papermc/paper/registry/event/RegistryEventTypeProvider.java b/src/main/java/io/papermc/paper/registry/event/RegistryEventTypeProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..d807bd2f42c98e37a96cf110ad77820dfffc8398
--- /dev/null
+++ b/src/main/java/io/papermc/paper/registry/event/RegistryEventTypeProvider.java
@@ -0,0 +1,24 @@
+package io.papermc.paper.registry.event;
+
+import io.papermc.paper.plugin.bootstrap.BootstrapContext;
+import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEventType;
+import io.papermc.paper.registry.RegistryBuilder;
+import io.papermc.paper.registry.event.type.RegistryEntryAddEventType;
+import java.util.Optional;
+import java.util.ServiceLoader;
+import org.jetbrains.annotations.ApiStatus;
+
+@ApiStatus.Internal
+interface RegistryEventTypeProvider {
+
+ Optional<RegistryEventTypeProvider> PROVIDER = ServiceLoader.load(RegistryEventTypeProvider.class)
+ .findFirst();
+
+ static RegistryEventTypeProvider provider() {
+ return PROVIDER.orElseThrow(() -> new IllegalStateException("Could not find a %s service implementation".formatted(RegistryEventTypeProvider.class.getSimpleName())));
+ }
+
+ <T, B extends RegistryBuilder<T>> RegistryEntryAddEventType<T, B> registryEntryAdd(RegistryEventProvider<T, B> type);
+
+ <T, B extends RegistryBuilder<T>> LifecycleEventType.Prioritizable<BootstrapContext, RegistryFreezeEvent<T, B>> registryFreeze(RegistryEventProvider<T, B> type);
+}
diff --git a/src/main/java/io/papermc/paper/registry/event/RegistryEvents.java b/src/main/java/io/papermc/paper/registry/event/RegistryEvents.java
new file mode 100644
index 0000000000000000000000000000000000000000..1f89945be2ed68f52a544f41f7a151b8fdfe113e
--- /dev/null
+++ b/src/main/java/io/papermc/paper/registry/event/RegistryEvents.java
@@ -0,0 +1,14 @@
+package io.papermc.paper.registry.event;
+
+import org.jetbrains.annotations.ApiStatus;
+
+/**
+ * Holds providers for {@link RegistryEntryAddEvent} and {@link RegistryFreezeEvent}
+ * handlers for each applicable registry.
+ */
+@ApiStatus.Experimental
+public final class RegistryEvents {
+
+ private RegistryEvents() {
+ }
+}
diff --git a/src/main/java/io/papermc/paper/registry/event/RegistryFreezeEvent.java b/src/main/java/io/papermc/paper/registry/event/RegistryFreezeEvent.java
new file mode 100644
index 0000000000000000000000000000000000000000..12ec7e794a5047a30354a485acd40fa0f3438eea
--- /dev/null
+++ b/src/main/java/io/papermc/paper/registry/event/RegistryFreezeEvent.java
@@ -0,0 +1,39 @@
+package io.papermc.paper.registry.event;
+
+import io.papermc.paper.registry.RegistryBuilder;
+import io.papermc.paper.registry.tag.Tag;
+import io.papermc.paper.registry.tag.TagKey;
+import org.bukkit.Keyed;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.jetbrains.annotations.ApiStatus;
+
+/**
+ * Event object for {@link RegistryEventProvider#freeze()}. This
+ * event is fired right before a registry is frozen disallowing further changes.
+ * It provides a way for plugins to add new objects to the registry.
+ *
+ * @param <T> registry entry type
+ * @param <B> registry entry builder type
+ */
+@ApiStatus.Experimental
+@ApiStatus.NonExtendable
+public interface RegistryFreezeEvent<T, B extends RegistryBuilder<T>> extends RegistryEvent<T> {
+
+ /**
+ * Get the writable registry.
+ *
+ * @return a writable registry
+ */
+ @NonNull WritableRegistry<T, B> registry();
+
+ /**
+ * Gets or creates a tag for the given tag key. This tag
+ * is then required to be filled either from the built-in or
+ * custom datapack.
+ *
+ * @param tagKey the tag key
+ * @return the tag
+ * @param <V> the tag value type
+ */
+ @NonNull <V extends Keyed> Tag<V> getOrCreateTag(@NonNull TagKey<V> tagKey);
+}
diff --git a/src/main/java/io/papermc/paper/registry/event/WritableRegistry.java b/src/main/java/io/papermc/paper/registry/event/WritableRegistry.java
new file mode 100644
index 0000000000000000000000000000000000000000..6de377275097f065c38dd59c6db9704018ac81fc
--- /dev/null
+++ b/src/main/java/io/papermc/paper/registry/event/WritableRegistry.java
@@ -0,0 +1,27 @@
+package io.papermc.paper.registry.event;
+
+import io.papermc.paper.registry.RegistryBuilder;
+import io.papermc.paper.registry.TypedKey;
+import java.util.function.Consumer;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.jetbrains.annotations.ApiStatus;
+
+/**
+ * A registry which supports registering new objects.
+ *
+ * @param <T> registry entry type
+ * @param <B> registry entry builder type
+ */
+@ApiStatus.NonExtendable
+@ApiStatus.Experimental
+public interface WritableRegistry<T, B extends RegistryBuilder<T>> {
+
+ /**
+ * Register a new value with the specified key. This will
+ * fire a {@link RegistryEntryAddEvent} for the new entry.
+ *
+ * @param key the entry's key (must be unique from others)
+ * @param value a consumer for the entry's builder
+ */
+ void register(@NonNull TypedKey<T> key, @NonNull Consumer<? super B> value);
+}
diff --git a/src/main/java/io/papermc/paper/registry/event/type/RegistryEntryAddConfiguration.java b/src/main/java/io/papermc/paper/registry/event/type/RegistryEntryAddConfiguration.java
new file mode 100644
index 0000000000000000000000000000000000000000..c5b484ee5f4a0f283a8d46f5490afac4b58b06be
--- /dev/null
+++ b/src/main/java/io/papermc/paper/registry/event/type/RegistryEntryAddConfiguration.java
@@ -0,0 +1,42 @@
+package io.papermc.paper.registry.event.type;
+
+import io.papermc.paper.plugin.bootstrap.BootstrapContext;
+import io.papermc.paper.plugin.lifecycle.event.handler.configuration.PrioritizedLifecycleEventHandlerConfiguration;
+import io.papermc.paper.registry.TypedKey;
+import java.util.function.Predicate;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.jetbrains.annotations.Contract;
+
+/**
+ * Specific configuration for {@link io.papermc.paper.registry.event.RegistryEntryAddEvent}s.
+ *
+ * @param <T> registry entry type
+ */
+public interface RegistryEntryAddConfiguration<T> extends PrioritizedLifecycleEventHandlerConfiguration<BootstrapContext> {
+
+ /**
+ * Only call the handler if the value being added matches the specified key.
+ *
+ * @param key the key to match
+ * @return this configuration
+ */
+ @Contract(value = "_ -> this", mutates = "this")
+ default @NonNull RegistryEntryAddConfiguration<T> filter(final @NonNull TypedKey<T> key) {
+ return this.filter(key::equals);
+ }
+
+ /**
+ * Only call the handler if the value being added passes the provided filter.
+ *
+ * @param filter the predicate to match the key against
+ * @return this configuration
+ */
+ @Contract(value = "_ -> this", mutates = "this")
+ @NonNull RegistryEntryAddConfiguration<T> filter(@NonNull Predicate<TypedKey<T>> filter);
+
+ @Override
+ @NonNull RegistryEntryAddConfiguration<T> priority(int priority);
+
+ @Override
+ @NonNull RegistryEntryAddConfiguration<T> monitor();
+}
diff --git a/src/main/java/io/papermc/paper/registry/event/type/RegistryEntryAddEventType.java b/src/main/java/io/papermc/paper/registry/event/type/RegistryEntryAddEventType.java
new file mode 100644
index 0000000000000000000000000000000000000000..f4d4ebf6cbed1b4a9955ceb2d0586782181d97e5
--- /dev/null
+++ b/src/main/java/io/papermc/paper/registry/event/type/RegistryEntryAddEventType.java
@@ -0,0 +1,18 @@
+package io.papermc.paper.registry.event.type;
+
+import io.papermc.paper.plugin.bootstrap.BootstrapContext;
+import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEventType;
+import io.papermc.paper.registry.RegistryBuilder;
+import io.papermc.paper.registry.event.RegistryEntryAddEvent;
+import org.jetbrains.annotations.ApiStatus;
+
+/**
+ * Lifecycle event type for {@link RegistryEntryAddEvent}s.
+ *
+ * @param <T> registry entry type
+ * @param <B> registry entry builder type
+ */
+@ApiStatus.Experimental
+@ApiStatus.NonExtendable
+public interface RegistryEntryAddEventType<T, B extends RegistryBuilder<T>> extends LifecycleEventType<BootstrapContext, RegistryEntryAddEvent<T, B>, RegistryEntryAddConfiguration<T>> {
+}
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..b891101b43148f63c96b7dd611914c85d7b29dbf
--- /dev/null
+++ b/src/main/java/io/papermc/paper/registry/set/RegistryKeySet.java
@@ -0,0 +1,50 @@
+package io.papermc.paper.registry.set;
+
+import io.papermc.paper.registry.TypedKey;
+import java.util.Collection;
+import java.util.Iterator;
+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;
+
+@ApiStatus.Experimental
+@ApiStatus.NonExtendable
+public non-sealed interface RegistryKeySet<T extends Keyed> extends Iterable<TypedKey<T>>, 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. Prefer using
+ * {@link #values()}.
+ *
+ * @param registry the registry to resolve the values from (must match {@link #registryKey()})
+ * @return the resolved values
+ * @see RegistryKeySet#values()
+ */
+ @NonNull @Unmodifiable Collection<T> resolve(final @NonNull Registry<T> registry);
+
+ /**
+ * 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);
+
+ @Override
+ default @NonNull Iterator<TypedKey<T>> iterator() {
+ return this.values().iterator();
+ }
+}
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;
+
+@ApiStatus.Internal
+@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..108df480e16131781a52c7bbba2ca6581e4c1ca1
--- /dev/null
+++ b/src/main/java/io/papermc/paper/registry/set/RegistrySet.java
@@ -0,0 +1,112 @@
+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 org.bukkit.Keyed;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.Contract;
+
+/**
+ * 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 keys linked to 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
+ */
+@ApiStatus.Experimental
+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
+ */
+ @SafeVarargs
+ static <T extends Keyed> RegistryKeySet<T> keySet(final @NonNull RegistryKey<T> registryKey, final @NonNull TypedKey<T> @NonNull... keys) { // TODO remove Keyed
+ return keySet(registryKey, Lists.newArrayList(keys));
+ }
+
+ /**
+ * 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 the registry set is empty.
+ *
+ * @return true, if empty
+ */
+ default boolean isEmpty() {
+ return this.size() == 0;
+ }
+}
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..58e2e42737d48b243854466eb7f7d3a844a86b6e
--- /dev/null
+++ b/src/main/java/io/papermc/paper/registry/set/RegistryValueSet.java
@@ -0,0 +1,34 @@
+package io.papermc.paper.registry.set;
+
+import java.util.Collection;
+import java.util.Iterator;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.jetbrains.annotations.ApiStatus;
+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
+ */
+@ApiStatus.Experimental
+public sealed interface RegistryValueSet<T> extends Iterable<T>, RegistrySet<T> permits RegistryValueSetImpl {
+
+ @Override
+ default int size() {
+ return this.values().size();
+ }
+
+ /**
+ * Get the collection of values in this direct set.
+ *
+ * @return the values
+ */
+ @NonNull @Unmodifiable Collection<T> values();
+
+ @Override
+ default @NonNull Iterator<T> iterator() {
+ return this.values().iterator();
+ }
+}
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;
+
+@ApiStatus.Internal
+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..ae374f68ef9baa16ed90c371f1622de0c0159873
--- /dev/null
+++ b/src/main/java/io/papermc/paper/registry/tag/Tag.java
@@ -0,0 +1,25 @@
+package io.papermc.paper.registry.tag;
+
+import io.papermc.paper.registry.set.RegistryKeySet;
+import org.bukkit.Keyed;
+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)
+ */
+@ApiStatus.Experimental
+public interface Tag<T extends Keyed> extends RegistryKeySet<T> { // TODO remove Keyed
+
+ /**
+ * 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;
+
+@ApiStatus.Experimental
+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;
+
+@ApiStatus.Internal
+@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 36a8f6082f111a1cbb25e0ff3c968a89f02611a0..ff4997c6c5cecf7caf957e1aedaafb22df647e7d 100644
--- a/src/main/java/org/bukkit/Registry.java
+++ b/src/main/java/org/bukkit/Registry.java
@@ -356,6 +356,27 @@ public interface Registry<T extends Keyed> extends Iterable<T> {
*/
@Nullable
T get(@NotNull NamespacedKey key);
+ // Paper start
+ /**
+ * Get the object by its key.
+ *
+ * @param key non-null key
+ * @return item or null if it does not exist
+ */
+ default @Nullable T get(final net.kyori.adventure.key.@NotNull Key key) {
+ return key instanceof final NamespacedKey nsKey ? this.get(nsKey) : this.get(new NamespacedKey(key.namespace(), key.value()));
+ }
+
+ /**
+ * Get the object by its typed key.
+ *
+ * @param typedKey non-null typed key
+ * @return item or null if it does not exist
+ */
+ default @Nullable T get(final io.papermc.paper.registry.@NotNull TypedKey<T> typedKey) {
+ return this.get(typedKey.key());
+ }
+ // Paper end
// Paper start - improve Registry
/**
@@ -430,6 +451,34 @@ 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
+ * @see #getTag(io.papermc.paper.registry.tag.TagKey)
+ */
+ @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
+ * @throws java.util.NoSuchElementException if no tag with the given key is found
+ * @throws UnsupportedOperationException if this registry doesn't have or support tags
+ * @see #hasTag(io.papermc.paper.registry.tag.TagKey)
+ */
+ @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.
*
@@ -511,5 +560,23 @@ public interface Registry<T extends Keyed> extends Iterable<T> {
return value.getKey();
}
// Paper end - improve Registry
+
+ // Paper start - RegistrySet API
+ @SuppressWarnings("deprecation")
+ @Override
+ public boolean hasTag(final io.papermc.paper.registry.tag.@NotNull TagKey<T> key) {
+ return Bukkit.getUnsafe().getTag(key) != null;
+ }
+
+ @SuppressWarnings("deprecation")
+ @Override
+ public io.papermc.paper.registry.tag.@NotNull Tag<T> getTag(final io.papermc.paper.registry.tag.@NotNull TagKey<T> key) {
+ final io.papermc.paper.registry.tag.Tag<T> tag = Bukkit.getUnsafe().getTag(key);
+ if (tag == null) {
+ throw new java.util.NoSuchElementException("No tag " + key + " found");
+ }
+ return tag;
+ }
+ // Paper end - RegistrySet API
}
}
diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java
index 8534478dba8b9bdcb9cd1535fb0be18f8638f8b1..732f4f2eafd5c8bf676c963c689cfd0f7e1c1502 100644
--- a/src/main/java/org/bukkit/UnsafeValues.java
+++ b/src/main/java/org/bukkit/UnsafeValues.java
@@ -279,4 +279,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
}