From d9111ccec278132052cebdebca9f1c6dcaba9512 Mon Sep 17 00:00:00 2001 From: Jake Potrebic Date: Sun, 16 Jun 2024 12:39:59 -0700 Subject: [PATCH] Registry Modification API (#10893) * Registry Modification API * some fixes * even more fixes --- .editorconfig | 2 + patches/api/0003-Test-changes.patch | 24 +- patches/api/0073-AsyncTabCompleteEvent.patch | 2 +- patches/api/0433-Improve-Registry.patch | 36 +- .../api/0476-Registry-Modification-API.patch | 893 +++++++++++ patches/api/0476-WIP-Tag-API.patch | 62 - patches/server/0010-Adventure.patch | 26 +- ...oleAppender-for-console-improvements.patch | 6 +- patches/server/0019-Paper-Plugins.patch | 13 +- .../server/0237-Optimize-MappedRegistry.patch | 2 +- ...gistryAccess-for-managing-Registries.patch | 97 +- .../0934-Add-Lifecycle-Event-system.patch | 156 +- .../0975-Brigadier-based-command-API.patch | 8 +- .../1021-Registry-Modification-API.patch | 1427 +++++++++++++++++ 14 files changed, 2547 insertions(+), 207 deletions(-) create mode 100644 patches/api/0476-Registry-Modification-API.patch delete mode 100644 patches/api/0476-WIP-Tag-API.patch create mode 100644 patches/server/1021-Registry-Modification-API.patch diff --git a/.editorconfig b/.editorconfig index 2874476cf4..198db6e8a1 100644 --- a/.editorconfig +++ b/.editorconfig @@ -29,6 +29,8 @@ ij_java_names_count_to_use_import_on_demand = 999999 ij_java_imports_layout = *,|,$* ij_java_generate_final_locals = true ij_java_generate_final_parameters = true +ij_java_method_parameters_new_line_after_left_paren = true +ij_java_method_parameters_right_paren_on_new_line = true [test-plugin/**/*.java] ij_java_use_fq_class_names = false diff --git a/patches/api/0003-Test-changes.patch b/patches/api/0003-Test-changes.patch index a9fa8a4131..a1db2f75de 100644 --- a/patches/api/0003-Test-changes.patch +++ b/patches/api/0003-Test-changes.patch @@ -66,7 +66,7 @@ index 0000000000000000000000000000000000000000..77154095cfb8b259bdb318e8ff40cb6f + } +} diff --git a/src/test/java/org/bukkit/AnnotationTest.java b/src/test/java/org/bukkit/AnnotationTest.java -index 64e7aef6220097edefdff3b98a771b988365930d..a899f63eb2ce58b3cf708e91819cbbdeffda5d9f 100644 +index 64e7aef6220097edefdff3b98a771b988365930d..d9091ba1e5a55e03adca98305233cce9d6888609 100644 --- a/src/test/java/org/bukkit/AnnotationTest.java +++ b/src/test/java/org/bukkit/AnnotationTest.java @@ -29,7 +29,13 @@ public class AnnotationTest { @@ -103,7 +103,7 @@ index 64e7aef6220097edefdff3b98a771b988365930d..a899f63eb2ce58b3cf708e91819cbbde }; @Test -@@ -67,14 +83,40 @@ public class AnnotationTest { +@@ -67,14 +83,48 @@ public class AnnotationTest { } if (mustBeAnnotated(Type.getReturnType(method.desc)) && !isWellAnnotated(method.invisibleAnnotations)) { @@ -132,8 +132,16 @@ index 64e7aef6220097edefdff3b98a771b988365930d..a899f63eb2ce58b3cf708e91819cbbde for (int i = 0; i < paramTypes.length; i++) { if (mustBeAnnotated(paramTypes[i]) ^ isWellAnnotated(method.invisibleParameterAnnotations == null ? null : method.invisibleParameterAnnotations[i])) { + // Paper start -+ if (method.invisibleTypeAnnotations != null || method.visibleTypeAnnotations != null) { -+ for (final org.objectweb.asm.tree.TypeAnnotationNode invisibleTypeAnnotation : java.util.Objects.requireNonNullElse(method.invisibleTypeAnnotations, method.visibleTypeAnnotations)) { ++ if (method.invisibleTypeAnnotations != null) { ++ for (final org.objectweb.asm.tree.TypeAnnotationNode invisibleTypeAnnotation : method.invisibleTypeAnnotations) { ++ final org.objectweb.asm.TypeReference ref = new org.objectweb.asm.TypeReference(invisibleTypeAnnotation.typeRef); ++ if (ref.getSort() == org.objectweb.asm.TypeReference.METHOD_FORMAL_PARAMETER && ref.getTypeParameterIndex() == i && java.util.Arrays.asList(ACCEPTED_ANNOTATIONS).contains(invisibleTypeAnnotation.desc)) { ++ continue dancing; ++ } ++ } ++ } ++ if (method.visibleTypeAnnotations != null) { ++ for (final org.objectweb.asm.tree.TypeAnnotationNode invisibleTypeAnnotation : method.visibleTypeAnnotations) { + final org.objectweb.asm.TypeReference ref = new org.objectweb.asm.TypeReference(invisibleTypeAnnotation.typeRef); + if (ref.getSort() == org.objectweb.asm.TypeReference.METHOD_FORMAL_PARAMETER && ref.getTypeParameterIndex() == i && java.util.Arrays.asList(ACCEPTED_ANNOTATIONS).contains(invisibleTypeAnnotation.desc)) { + continue dancing; @@ -144,7 +152,7 @@ index 64e7aef6220097edefdff3b98a771b988365930d..a899f63eb2ce58b3cf708e91819cbbde ParameterNode paramNode = parameters == null ? null : parameters.get(i); String paramName = paramNode == null ? null : paramNode.name; -@@ -91,13 +133,18 @@ public class AnnotationTest { +@@ -91,13 +141,18 @@ public class AnnotationTest { Collections.sort(errors); @@ -167,7 +175,7 @@ index 64e7aef6220097edefdff3b98a771b988365930d..a899f63eb2ce58b3cf708e91819cbbde } private static void collectClasses(@NotNull File from, @NotNull Map to) throws IOException { -@@ -140,6 +187,11 @@ public class AnnotationTest { +@@ -140,6 +195,11 @@ public class AnnotationTest { // Exceptions are excluded return false; } @@ -179,7 +187,7 @@ index 64e7aef6220097edefdff3b98a771b988365930d..a899f63eb2ce58b3cf708e91819cbbde for (String excludedClass : EXCLUDED_CLASSES) { if (excludedClass.equals(clazz.name)) { -@@ -152,7 +204,7 @@ public class AnnotationTest { +@@ -152,7 +212,7 @@ public class AnnotationTest { private static boolean isMethodIncluded(@NotNull ClassNode clazz, @NotNull MethodNode method, @NotNull Map allClasses) { // Exclude private, synthetic and deprecated methods @@ -188,7 +196,7 @@ index 64e7aef6220097edefdff3b98a771b988365930d..a899f63eb2ce58b3cf708e91819cbbde return false; } -@@ -170,11 +222,30 @@ public class AnnotationTest { +@@ -170,11 +230,30 @@ public class AnnotationTest { if ("".equals(method.name) && isAnonymous(clazz)) { return false; } diff --git a/patches/api/0073-AsyncTabCompleteEvent.patch b/patches/api/0073-AsyncTabCompleteEvent.patch index b88930e4dc..3908b1a718 100644 --- a/patches/api/0073-AsyncTabCompleteEvent.patch +++ b/patches/api/0073-AsyncTabCompleteEvent.patch @@ -589,7 +589,7 @@ index 270e6d8ad4358baa256cee5f16cff281f063ce3b..b43c3cb5c88eada186d6f81712c244aa @Override diff --git a/src/test/java/org/bukkit/AnnotationTest.java b/src/test/java/org/bukkit/AnnotationTest.java -index a899f63eb2ce58b3cf708e91819cbbdeffda5d9f..057dc3ebea3516863dda24252fe05d344c16fab3 100644 +index d9091ba1e5a55e03adca98305233cce9d6888609..b82f07a2879412f6b30643ca93a97439aa49a98a 100644 --- a/src/test/java/org/bukkit/AnnotationTest.java +++ b/src/test/java/org/bukkit/AnnotationTest.java @@ -48,6 +48,8 @@ public class AnnotationTest { diff --git a/patches/api/0433-Improve-Registry.patch b/patches/api/0433-Improve-Registry.patch index 8a49d39633..d5cb134c45 100644 --- a/patches/api/0433-Improve-Registry.patch +++ b/patches/api/0433-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 17714f04fdd87ed4332ea62bcfab7063560bf1be..bc7144f02ac6857dbeec13c3995b71f6920e022a 100644 +index 17714f04fdd87ed4332ea62bcfab7063560bf1be..27b987db385a594fede4e884b6437dc363f6e817 100644 --- a/src/main/java/org/bukkit/Registry.java +++ b/src/main/java/org/bukkit/Registry.java -@@ -358,6 +358,49 @@ public interface Registry extends Iterable { +@@ -358,6 +358,79 @@ public interface Registry extends Iterable { @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 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. + *

+ * Some types can exist without being in a registry @@ -84,7 +114,7 @@ index 17714f04fdd87ed4332ea62bcfab7063560bf1be..bc7144f02ac6857dbeec13c3995b71f6 /** * Returns a new stream, which contains all registry items, which are registered to the registry. * -@@ -432,5 +475,12 @@ public interface Registry extends Iterable { +@@ -432,5 +505,12 @@ public interface Registry extends Iterable { public Class getType() { return this.type; } diff --git a/patches/api/0476-Registry-Modification-API.patch b/patches/api/0476-Registry-Modification-API.patch new file mode 100644 index 0000000000..f566c2d39d --- /dev/null +++ b/patches/api/0476-Registry-Modification-API.patch @@ -0,0 +1,893 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +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 registry value type ++ */ ++@ApiStatus.NonExtendable ++@ApiStatus.Experimental ++public interface RegistryBuilder { ++} +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 registry entry type ++ * @param registry entry builder type ++ */ ++@ApiStatus.Experimental ++@ApiStatus.NonExtendable ++public interface RegistryEntryAddEvent> extends RegistryEvent { ++ ++ /** ++ * 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 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 the tag value type ++ */ ++ @NonNull Tag getOrCreateTag(@NonNull TagKey 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 registry entry type ++ */ ++@ApiStatus.Experimental ++@ApiStatus.NonExtendable ++public interface RegistryEvent extends LifecycleEvent { ++ ++ /** ++ * Get the key for the registry this event pertains to. ++ * ++ * @return the registry key ++ */ ++ @NonNull RegistryKey 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. ++ *

++ * Supported events are: ++ *

    ++ *
  • {@link RegistryEntryAddEvent} (via {@link #entryAdd()})
  • ++ *
  • {@link RegistryFreezeEvent} (via {@link #freeze()})
  • ++ *
++ * ++ * @param registry entry type ++ * @param registry entry builder type ++ */ ++@ApiStatus.Experimental ++@ApiStatus.NonExtendable ++public interface RegistryEventProvider> { ++ ++ /** ++ * Gets the event type for {@link RegistryEntryAddEvent} which is fired just before ++ * an object is added to a registry. ++ *

++ * 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 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. ++ *

++ * 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> freeze(); ++ ++ /** ++ * Gets the registry key associated with this event type provider. ++ * ++ * @return the registry key ++ */ ++ @NonNull RegistryKey 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>(RegistryKey registryKey) implements RegistryEventProvider { ++ ++ static > RegistryEventProvider create(final RegistryKey registryKey) { ++ return new RegistryEventProviderImpl<>(registryKey); ++ } ++ ++ @Override ++ public RegistryEntryAddEventType entryAdd() { ++ return RegistryEventTypeProvider.provider().registryEntryAdd(this); ++ } ++ ++ @Override ++ public LifecycleEventType.Prioritizable> 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 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()))); ++ } ++ ++ > RegistryEntryAddEventType registryEntryAdd(RegistryEventProvider type); ++ ++ > LifecycleEventType.Prioritizable> registryFreeze(RegistryEventProvider 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 registry entry type ++ * @param registry entry builder type ++ */ ++@ApiStatus.Experimental ++@ApiStatus.NonExtendable ++public interface RegistryFreezeEvent> extends RegistryEvent { ++ ++ /** ++ * Get the writable registry. ++ * ++ * @return a writable registry ++ */ ++ @NonNull WritableRegistry 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 the tag value type ++ */ ++ @NonNull Tag getOrCreateTag(@NonNull TagKey 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 registry entry type ++ * @param registry entry builder type ++ */ ++@ApiStatus.NonExtendable ++@ApiStatus.Experimental ++public interface WritableRegistry> { ++ ++ /** ++ * 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 key, @NonNull Consumer 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 registry entry type ++ */ ++public interface RegistryEntryAddConfiguration extends PrioritizedLifecycleEventHandlerConfiguration { ++ ++ /** ++ * 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 filter(final @NonNull TypedKey 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 filter(@NonNull Predicate> filter); ++ ++ @Override ++ @NonNull RegistryEntryAddConfiguration priority(int priority); ++ ++ @Override ++ @NonNull RegistryEntryAddConfiguration 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 registry entry type ++ * @param registry entry builder type ++ */ ++@ApiStatus.Experimental ++@ApiStatus.NonExtendable ++public interface RegistryEntryAddEventType> extends LifecycleEventType, RegistryEntryAddConfiguration> { ++} +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 extends Iterable>, RegistrySet { // 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> 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 resolve(final @NonNull Registry 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 valueKey); ++ ++ @Override ++ default @NonNull Iterator> 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(RegistryKey registryKey, List> values) implements RegistryKeySet { // TODO remove Keyed ++ ++ static RegistryKeySet create(final RegistryKey registryKey, final Iterable values) { // TODO remove Keyed ++ final Registry registry = RegistryAccess.registryAccess().getRegistry(registryKey); ++ final ArrayList> 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 valueKey) { ++ return this.values.contains(valueKey); ++ } ++ ++ @Override ++ public Collection resolve(final Registry registry) { ++ final List values = new ArrayList<>(this.values.size()); ++ for (final TypedKey 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. ++ *

++ * There are 2 types of registry sets: ++ *

    ++ *
  • {@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)}.
  • ++ *
  • {@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)}.
  • ++ * ++ *
++ * ++ * @param registry value type ++ */ ++@ApiStatus.Experimental ++public sealed interface RegistrySet permits RegistryKeySet, RegistryValueSet { ++ ++ // TODO uncomment when direct holder sets need to be exposed to the API ++ // /** ++ // * Creates a {@link RegistryValueSet} from anonymous values. ++ // *

All values provided must not have keys in the given registry.

++ // * ++ // * @param registryKey the registry key for the type of these values ++ // * @param values the values ++ // * @return a new registry set ++ // * @param the type of the values ++ // */ ++ // @Contract(value = "_, _ -> new", pure = true) ++ // static @NonNull RegistryValueSet valueSet(final @NonNull RegistryKey registryKey, final @NonNull Iterable values) { ++ // return RegistryValueSetImpl.create(registryKey, values); ++ // } ++ ++ /** ++ * Creates a {@link RegistryKeySet} from registry-backed values. ++ *

All values provided must have keys in the given registry. ++ *

++ *

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}.

++ * ++ * @param registryKey the registry key for the owner of these values ++ * @param values the values ++ * @return a new registry set ++ * @param 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 @NonNull RegistryKeySet keySetFromValues(final @NonNull RegistryKey registryKey, final @NonNull Iterable 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 the type of the values ++ */ ++ @SafeVarargs ++ static RegistryKeySet keySet(final @NonNull RegistryKey registryKey, final @NonNull TypedKey @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 the type of the values ++ */ ++ @SuppressWarnings("BoundedWildcard") ++ @Contract(value = "_, _ -> new", pure = true) ++ static @NonNull RegistryKeySet keySet(final @NonNull RegistryKey registryKey, final @NonNull Iterable> keys) { // TODO remove Keyed ++ return new RegistryKeySetImpl<>(registryKey, Lists.newArrayList(keys)); ++ } ++ ++ /** ++ * Get the registry key for this set. ++ * ++ * @return the registry key ++ */ ++ @NonNull RegistryKey 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 registry value type ++ */ ++@ApiStatus.Experimental ++public sealed interface RegistryValueSet extends Iterable, RegistrySet 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 values(); ++ ++ @Override ++ default @NonNull Iterator 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(RegistryKey registryKey, List values) implements RegistryValueSet { ++ ++ RegistryValueSetImpl { ++ values = List.copyOf(values); ++ } ++ ++ static RegistryValueSet create(final RegistryKey registryKey, final Iterable 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 ++ * @see org.bukkit.Tag ++ * @see org.bukkit.Registry#getTag(TagKey) ++ */ ++@ApiStatus.Experimental ++public interface Tag extends RegistryKeySet { // TODO remove Keyed ++ ++ /** ++ * Get the identifier for this named set. ++ * ++ * @return the tag key identifier ++ */ ++ @NonNull TagKey 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 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 the registry value type ++ */ ++ @Contract(value = "_, _ -> new", pure = true) ++ static @NonNull TagKey create(final @NonNull RegistryKey registryKey, final @NonNull Key key) { ++ return new TagKeyImpl<>(registryKey, key); ++ } ++ ++ /** ++ * Get the registry key for this tag key. ++ * ++ * @return the registry key ++ */ ++ @NonNull RegistryKey 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(RegistryKey registryKey, Key key) implements TagKey { ++} +diff --git a/src/main/java/org/bukkit/Registry.java b/src/main/java/org/bukkit/Registry.java +index 27b987db385a594fede4e884b6437dc363f6e817..9725580b6458e5d37fbc6059869604f9883bd6d1 100644 +--- a/src/main/java/org/bukkit/Registry.java ++++ b/src/main/java/org/bukkit/Registry.java +@@ -357,6 +357,27 @@ public interface Registry extends Iterable { + */ + @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 typedKey) { ++ return this.get(typedKey.key()); ++ } ++ // Paper end + + // Paper start - improve Registry + /** +@@ -431,6 +452,34 @@ public interface Registry extends Iterable { + } + // 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 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 getTag(final io.papermc.paper.registry.tag.@NotNull TagKey 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. + * +@@ -512,5 +561,23 @@ public interface Registry extends Iterable { + 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 key) { ++ return Bukkit.getUnsafe().getTag(key) != null; ++ } ++ ++ @SuppressWarnings("deprecation") ++ @Override ++ public io.papermc.paper.registry.tag.@NotNull Tag getTag(final io.papermc.paper.registry.tag.@NotNull TagKey key) { ++ final io.papermc.paper.registry.tag.Tag 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 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 computeTooltipLines(@NotNull ItemStack itemStack, @NotNull io.papermc.paper.inventory.tooltip.TooltipContext tooltipContext, @Nullable org.bukkit.entity.Player player); // Paper - expose itemstack tooltip lines ++ ++ io.papermc.paper.registry.tag.@Nullable Tag getTag(io.papermc.paper.registry.tag.@NotNull TagKey tagKey); // Paper - hack to get tags for non-server backed registries + } diff --git a/patches/api/0476-WIP-Tag-API.patch b/patches/api/0476-WIP-Tag-API.patch deleted file mode 100644 index 32ed16f1cf..0000000000 --- a/patches/api/0476-WIP-Tag-API.patch +++ /dev/null @@ -1,62 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Sat, 15 Jun 2024 21:42:19 -0400 -Subject: [PATCH] WIP Tag API - - -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 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 the registry value type -+ */ -+ @Contract(value = "_, _ -> new", pure = true) -+ static @NonNull TagKey create(final @NonNull RegistryKey registryKey, final @NonNull Key key) { -+ return new TagKeyImpl<>(registryKey, key); -+ } -+ -+ /** -+ * Get the registry key for this tag key. -+ * -+ * @return the registry key -+ */ -+ @NonNull RegistryKey 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(RegistryKey registryKey, Key key) implements TagKey { -+} diff --git a/patches/server/0010-Adventure.patch b/patches/server/0010-Adventure.patch index 75e61f1b24..c6a160a49b 100644 --- a/patches/server/0010-Adventure.patch +++ b/patches/server/0010-Adventure.patch @@ -1157,15 +1157,16 @@ index 0000000000000000000000000000000000000000..2fd6c3e65354071af71c7d8ebb97b559 +} diff --git a/src/main/java/io/papermc/paper/adventure/PaperAdventure.java b/src/main/java/io/papermc/paper/adventure/PaperAdventure.java new file mode 100644 -index 0000000000000000000000000000000000000000..a6aef1ac31f3d2784b5d7b1af616965b5cd2c383 +index 0000000000000000000000000000000000000000..dc4837c577676115f0653acc35f55962a432e425 --- /dev/null +++ b/src/main/java/io/papermc/paper/adventure/PaperAdventure.java -@@ -0,0 +1,478 @@ +@@ -0,0 +1,479 @@ +package io.papermc.paper.adventure; + +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.serialization.JavaOps; ++import com.mojang.serialization.JsonOps; +import io.netty.util.AttributeKey; +import java.io.IOException; +import java.util.ArrayList; @@ -1302,7 +1303,7 @@ index 0000000000000000000000000000000000000000..a6aef1ac31f3d2784b5d7b1af616965b + return decoded.toString(); + } + }; -+ public static final ComponentSerializer WRAPPER_AWARE_SERIALIZER = new WrapperAwareSerializer(); ++ public static final ComponentSerializer WRAPPER_AWARE_SERIALIZER = new WrapperAwareSerializer(() -> CraftRegistry.getMinecraftRegistry().createSerializationContext(JavaOps.INSTANCE)); + + private PaperAdventure() { + } @@ -1641,27 +1642,36 @@ index 0000000000000000000000000000000000000000..a6aef1ac31f3d2784b5d7b1af616965b +} diff --git a/src/main/java/io/papermc/paper/adventure/WrapperAwareSerializer.java b/src/main/java/io/papermc/paper/adventure/WrapperAwareSerializer.java new file mode 100644 -index 0000000000000000000000000000000000000000..c786ddf0ef19757011452204fd11d24541c39d9e +index 0000000000000000000000000000000000000000..a16344476abbb4f3e8aac26d4add9da53b7fc7df --- /dev/null +++ b/src/main/java/io/papermc/paper/adventure/WrapperAwareSerializer.java -@@ -0,0 +1,34 @@ +@@ -0,0 +1,43 @@ +package io.papermc.paper.adventure; + ++import com.google.common.base.Suppliers; +import com.mojang.datafixers.util.Pair; +import com.mojang.serialization.JavaOps; ++import java.util.function.Supplier; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.ComponentSerializer; +import net.minecraft.network.chat.ComponentSerialization; +import net.minecraft.resources.RegistryOps; +import org.bukkit.craftbukkit.CraftRegistry; + -+final class WrapperAwareSerializer implements ComponentSerializer { ++public final class WrapperAwareSerializer implements ComponentSerializer { ++ ++ private final Supplier> javaOps; ++ ++ public WrapperAwareSerializer(final Supplier> javaOps) { ++ this.javaOps = Suppliers.memoize(javaOps::get); ++ } ++ + @Override + public Component deserialize(final net.minecraft.network.chat.Component input) { + if (input instanceof AdventureComponent) { + return ((AdventureComponent) input).adventure; + } -+ final RegistryOps ops = CraftRegistry.getMinecraftRegistry().createSerializationContext(JavaOps.INSTANCE); ++ final RegistryOps ops = this.javaOps.get(); + final Object obj = ComponentSerialization.CODEC.encodeStart(ops, input) + .getOrThrow(s -> new RuntimeException("Failed to encode Minecraft Component: " + input + "; " + s)); + final Pair converted = AdventureCodecs.COMPONENT_CODEC.decode(ops, obj) @@ -1671,7 +1681,7 @@ index 0000000000000000000000000000000000000000..c786ddf0ef19757011452204fd11d245 + + @Override + public net.minecraft.network.chat.Component serialize(final Component component) { -+ final RegistryOps ops = CraftRegistry.getMinecraftRegistry().createSerializationContext(JavaOps.INSTANCE); ++ final RegistryOps ops = this.javaOps.get(); + final Object obj = AdventureCodecs.COMPONENT_CODEC.encodeStart(ops, component) + .getOrThrow(s -> new RuntimeException("Failed to encode adventure Component: " + component + "; " + s)); + final Pair converted = ComponentSerialization.CODEC.decode(ops, obj) diff --git a/patches/server/0011-Use-TerminalConsoleAppender-for-console-improvements.patch b/patches/server/0011-Use-TerminalConsoleAppender-for-console-improvements.patch index cefaa74db4..8e5fbfaf15 100644 --- a/patches/server/0011-Use-TerminalConsoleAppender-for-console-improvements.patch +++ b/patches/server/0011-Use-TerminalConsoleAppender-for-console-improvements.patch @@ -216,10 +216,10 @@ index 0000000000000000000000000000000000000000..8f07539a82f449ad217e316a7513a170 + +} diff --git a/src/main/java/io/papermc/paper/adventure/PaperAdventure.java b/src/main/java/io/papermc/paper/adventure/PaperAdventure.java -index a6aef1ac31f3d2784b5d7b1af616965b5cd2c383..2390618c553dec2f32467dd8f76a6e4651f726c9 100644 +index dc4837c577676115f0653acc35f55962a432e425..22fe529890f34f66534c01248f654dc911b44c3b 100644 --- a/src/main/java/io/papermc/paper/adventure/PaperAdventure.java +++ b/src/main/java/io/papermc/paper/adventure/PaperAdventure.java -@@ -31,6 +31,7 @@ import net.kyori.adventure.text.flattener.ComponentFlattener; +@@ -32,6 +32,7 @@ import net.kyori.adventure.text.flattener.ComponentFlattener; import net.kyori.adventure.text.format.Style; import net.kyori.adventure.text.format.TextColor; import net.kyori.adventure.text.serializer.ComponentSerializer; @@ -227,7 +227,7 @@ index a6aef1ac31f3d2784b5d7b1af616965b5cd2c383..2390618c553dec2f32467dd8f76a6e46 import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.kyori.adventure.text.serializer.plain.PlainComponentSerializer; import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; -@@ -128,6 +129,7 @@ public final class PaperAdventure { +@@ -129,6 +130,7 @@ public final class PaperAdventure { public static final AttributeKey LOCALE_ATTRIBUTE = AttributeKey.valueOf("adventure:locale"); // init after FLATTENER because classloading triggered here might create a logger @Deprecated public static final PlainComponentSerializer PLAIN = PlainComponentSerializer.builder().flattener(FLATTENER).build(); diff --git a/patches/server/0019-Paper-Plugins.patch b/patches/server/0019-Paper-Plugins.patch index 17df0a46b5..8bb43515e2 100644 --- a/patches/server/0019-Paper-Plugins.patch +++ b/patches/server/0019-Paper-Plugins.patch @@ -760,16 +760,18 @@ index 0000000000000000000000000000000000000000..b38e1e0f3d3055086f51bb191fd4b60e +} diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/LaunchEntryPointHandler.java b/src/main/java/io/papermc/paper/plugin/entrypoint/LaunchEntryPointHandler.java new file mode 100644 -index 0000000000000000000000000000000000000000..6c0f2c315387734f8dd4a7eca633aa0a9856dd17 +index 0000000000000000000000000000000000000000..48bc745ca9632fc46b5f786ff570434702eb47f2 --- /dev/null +++ b/src/main/java/io/papermc/paper/plugin/entrypoint/LaunchEntryPointHandler.java -@@ -0,0 +1,65 @@ +@@ -0,0 +1,74 @@ +package io.papermc.paper.plugin.entrypoint; + +import io.papermc.paper.plugin.provider.PluginProvider; +import io.papermc.paper.plugin.storage.BootstrapProviderStorage; +import io.papermc.paper.plugin.storage.ProviderStorage; +import io.papermc.paper.plugin.storage.ServerPluginProviderStorage; ++import it.unimi.dsi.fastutil.objects.Object2BooleanMap; ++import it.unimi.dsi.fastutil.objects.Object2BooleanOpenHashMap; +import org.jetbrains.annotations.ApiStatus; + +import java.util.HashMap; @@ -782,9 +784,11 @@ index 0000000000000000000000000000000000000000..6c0f2c315387734f8dd4a7eca633aa0a + + public static final LaunchEntryPointHandler INSTANCE = new LaunchEntryPointHandler(); + private final Map, ProviderStorage> storage = new HashMap<>(); ++ private final Object2BooleanMap> enteredMap = new Object2BooleanOpenHashMap<>(); + + LaunchEntryPointHandler() { + this.populateProviderStorage(); ++ this.enteredMap.defaultReturnValue(false); + } + + // Utility @@ -800,6 +804,7 @@ index 0000000000000000000000000000000000000000..6c0f2c315387734f8dd4a7eca633aa0a + } + + storage.enter(); ++ this.enteredMap.put(entrypoint, true); + } + + @Override @@ -823,6 +828,10 @@ index 0000000000000000000000000000000000000000..6c0f2c315387734f8dd4a7eca633aa0a + return storage; + } + ++ public boolean hasEntered(Entrypoint entrypoint) { ++ return this.enteredMap.getBoolean(entrypoint); ++ } ++ + // Reload only + public void populateProviderStorage() { + this.storage.put(Entrypoint.BOOTSTRAPPER, new BootstrapProviderStorage()); diff --git a/patches/server/0237-Optimize-MappedRegistry.patch b/patches/server/0237-Optimize-MappedRegistry.patch index 8f45008c5f..17c68f31d7 100644 --- a/patches/server/0237-Optimize-MappedRegistry.patch +++ b/patches/server/0237-Optimize-MappedRegistry.patch @@ -8,7 +8,7 @@ Use larger initial sizes to increase bucket capacity on the BiMap BiMap.get was seen to be using a good bit of CPU time. diff --git a/src/main/java/net/minecraft/core/MappedRegistry.java b/src/main/java/net/minecraft/core/MappedRegistry.java -index 362e49f503f3c792fbecf41ec9f235bbc02644de..6e4d5c168acdb9aaa9fbbee090082e4dc25e89e9 100644 +index 1dcbde18bd9c462cca48887b904a9c43261e1854..edbbafd1705345282e5e6251eb71bfde5793b7d4 100644 --- a/src/main/java/net/minecraft/core/MappedRegistry.java +++ b/src/main/java/net/minecraft/core/MappedRegistry.java @@ -35,11 +35,11 @@ public class MappedRegistry implements WritableRegistry { diff --git a/patches/server/0475-Add-RegistryAccess-for-managing-Registries.patch b/patches/server/0475-Add-RegistryAccess-for-managing-Registries.patch index 7c5dbe8250..5a0885dc09 100644 --- a/patches/server/0475-Add-RegistryAccess-for-managing-Registries.patch +++ b/patches/server/0475-Add-RegistryAccess-for-managing-Registries.patch @@ -12,12 +12,13 @@ public net.minecraft.server.RegistryLayer STATIC_ACCESS diff --git a/src/main/java/io/papermc/paper/registry/PaperRegistries.java b/src/main/java/io/papermc/paper/registry/PaperRegistries.java new file mode 100644 -index 0000000000000000000000000000000000000000..c1ee87876af79d0fcacd7b930d17d110464ac9d1 +index 0000000000000000000000000000000000000000..1e098dc25bd338ff179491ff3382ac56aad9948e --- /dev/null +++ b/src/main/java/io/papermc/paper/registry/PaperRegistries.java -@@ -0,0 +1,122 @@ +@@ -0,0 +1,133 @@ +package io.papermc.paper.registry; + ++import io.papermc.paper.adventure.PaperAdventure; +import io.papermc.paper.registry.entry.RegistryEntry; +import java.util.Collections; +import java.util.IdentityHashMap; @@ -48,6 +49,7 @@ index 0000000000000000000000000000000000000000..c1ee87876af79d0fcacd7b930d17d110 +import org.bukkit.craftbukkit.inventory.trim.CraftTrimPattern; +import org.bukkit.craftbukkit.legacy.FieldRename; +import org.bukkit.craftbukkit.potion.CraftPotionEffectType; ++import org.bukkit.craftbukkit.util.CraftNamespacedKey; +import org.bukkit.damage.DamageType; +import org.bukkit.entity.Wolf; +import org.bukkit.entity.memory.MemoryKey; @@ -66,9 +68,9 @@ index 0000000000000000000000000000000000000000..c1ee87876af79d0fcacd7b930d17d110 +@DefaultQualifier(NonNull.class) +public final class PaperRegistries { + -+ static final List> REGISTRY_ENTRIES; -+ private static final Map, RegistryEntry> BY_REGISTRY_KEY; -+ private static final Map, RegistryEntry> BY_RESOURCE_KEY; ++ static final List> REGISTRY_ENTRIES; ++ private static final Map, RegistryEntry> BY_REGISTRY_KEY; ++ private static final Map, RegistryEntry> BY_RESOURCE_KEY; + static { + REGISTRY_ENTRIES = List.of( + // built-ins @@ -105,9 +107,9 @@ index 0000000000000000000000000000000000000000..c1ee87876af79d0fcacd7b930d17d110 + apiOnly(Registries.FROG_VARIANT, RegistryKey.FROG_VARIANT, () -> org.bukkit.Registry.FROG_VARIANT), + apiOnly(Registries.MAP_DECORATION_TYPE, RegistryKey.MAP_DECORATION_TYPE, () -> org.bukkit.Registry.MAP_DECORATION_TYPE) + ); -+ final Map, RegistryEntry> byRegistryKey = new IdentityHashMap<>(REGISTRY_ENTRIES.size()); -+ final Map, RegistryEntry> byResourceKey = new IdentityHashMap<>(REGISTRY_ENTRIES.size()); -+ for (final RegistryEntry entry : REGISTRY_ENTRIES) { ++ final Map, RegistryEntry> byRegistryKey = new IdentityHashMap<>(REGISTRY_ENTRIES.size()); ++ final Map, RegistryEntry> byResourceKey = new IdentityHashMap<>(REGISTRY_ENTRIES.size()); ++ for (final RegistryEntry entry : REGISTRY_ENTRIES) { + byRegistryKey.put(entry.apiKey(), entry); + byResourceKey.put(entry.mcKey(), entry); + } @@ -116,31 +118,40 @@ index 0000000000000000000000000000000000000000..c1ee87876af79d0fcacd7b930d17d110 + } + + @SuppressWarnings("unchecked") -+ public static > @Nullable RegistryEntry getEntry(final ResourceKey> resourceKey) { -+ return (RegistryEntry) BY_RESOURCE_KEY.get(resourceKey); ++ public static @Nullable RegistryEntry getEntry(final ResourceKey> resourceKey) { ++ return (RegistryEntry) BY_RESOURCE_KEY.get(resourceKey); + } + + @SuppressWarnings("unchecked") -+ public static > @Nullable RegistryEntry getEntry(final RegistryKey registryKey) { -+ return (RegistryEntry) BY_REGISTRY_KEY.get(registryKey); ++ public static @Nullable RegistryEntry getEntry(final RegistryKey registryKey) { ++ return (RegistryEntry) BY_REGISTRY_KEY.get(registryKey); + } + + @SuppressWarnings("unchecked") -+ public static RegistryKey fromNms(final ResourceKey> registryResourceKey) { ++ public static RegistryKey registryFromNms(final ResourceKey> registryResourceKey) { + return (RegistryKey) Objects.requireNonNull(BY_RESOURCE_KEY.get(registryResourceKey), registryResourceKey + " doesn't have an api RegistryKey").apiKey(); + } + + @SuppressWarnings("unchecked") -+ public static ResourceKey> toNms(final RegistryKey registryKey) { ++ public static ResourceKey> registryToNms(final RegistryKey registryKey) { + return (ResourceKey>) Objects.requireNonNull(BY_REGISTRY_KEY.get(registryKey), registryKey + " doesn't have an mc registry ResourceKey").mcKey(); + } + ++ public static TypedKey fromNms(final ResourceKey resourceKey) { ++ return TypedKey.create(registryFromNms(resourceKey.registryKey()), CraftNamespacedKey.fromMinecraft(resourceKey.location())); ++ } ++ ++ @SuppressWarnings({"unchecked", "RedundantCast"}) ++ public static ResourceKey toNms(final TypedKey typedKey) { ++ return ResourceKey.create((ResourceKey>) PaperRegistries.registryToNms(typedKey.registryKey()), PaperAdventure.asVanilla(typedKey.key())); ++ } ++ + private PaperRegistries() { + } +} diff --git a/src/main/java/io/papermc/paper/registry/PaperRegistryAccess.java b/src/main/java/io/papermc/paper/registry/PaperRegistryAccess.java new file mode 100644 -index 0000000000000000000000000000000000000000..9f2bcfe0d9e479466a1e46e503071d1151310e6a +index 0000000000000000000000000000000000000000..d591e3a2e19d5358a0d25a5a681368943622d231 --- /dev/null +++ b/src/main/java/io/papermc/paper/registry/PaperRegistryAccess.java @@ -0,0 +1,124 @@ @@ -190,7 +201,7 @@ index 0000000000000000000000000000000000000000..9f2bcfe0d9e479466a1e46e503071d11 + @Override + public @Nullable Registry getRegistry(final Class type) { + final RegistryKey registryKey; -+ final @Nullable RegistryEntry entry; ++ final @Nullable RegistryEntry entry; + registryKey = requireNonNull(byType(type), () -> type + " is not a valid registry type"); + entry = PaperRegistries.getEntry(registryKey); + final @Nullable RegistryHolder registry = (RegistryHolder) this.registries.get(registryKey); @@ -198,7 +209,7 @@ index 0000000000000000000000000000000000000000..9f2bcfe0d9e479466a1e46e503071d11 + // if the registry exists, return right away. Since this is the "legacy" method, we return DelayedRegistry + // for the non-builtin Registry instances stored as fields in Registry. + return registry.get(); -+ } else if (entry instanceof DelayedRegistryEntry) { ++ } else if (entry instanceof DelayedRegistryEntry) { + // if the registry doesn't exist and the entry is marked as "delayed", we create a registry holder that is empty + // which will later be filled with the actual registry. This is so the fields on org.bukkit.Registry can be populated with + // registries that don't exist at the time org.bukkit.Registry is statically initialized. @@ -243,7 +254,7 @@ index 0000000000000000000000000000000000000000..9f2bcfe0d9e479466a1e46e503071d11 + + @SuppressWarnings("unchecked") // this method should be called right after any new MappedRegistry instances are created to later be used by the server. + private > void registerRegistry(final ResourceKey> resourceKey, final net.minecraft.core.Registry registry, final boolean replace) { -+ final @Nullable RegistryEntry entry = PaperRegistries.getEntry(resourceKey); ++ final @Nullable RegistryEntry entry = PaperRegistries.getEntry(resourceKey); + if (entry == null) { // skip registries that don't have API entries + return; + } @@ -252,7 +263,7 @@ index 0000000000000000000000000000000000000000..9f2bcfe0d9e479466a1e46e503071d11 + // if the holder doesn't exist yet, or is marked as "replaceable", put it in the map. + this.registries.put(entry.apiKey(), entry.createRegistryHolder(registry)); + } else { -+ if (registryHolder instanceof RegistryHolder.Delayed && entry instanceof final DelayedRegistryEntry delayedEntry) { ++ if (registryHolder instanceof RegistryHolder.Delayed && entry instanceof final DelayedRegistryEntry delayedEntry) { + // if the registry holder is delayed, and the entry is marked as "delayed", then load the holder with the CraftRegistry instance that wraps the actual nms Registry. + ((RegistryHolder.Delayed) registryHolder).loadFrom(delayedEntry, registry); + } else { @@ -270,7 +281,7 @@ index 0000000000000000000000000000000000000000..9f2bcfe0d9e479466a1e46e503071d11 +} diff --git a/src/main/java/io/papermc/paper/registry/RegistryHolder.java b/src/main/java/io/papermc/paper/registry/RegistryHolder.java new file mode 100644 -index 0000000000000000000000000000000000000000..02402ef647c3e78ed56fd6b2687bf7c67448f891 +index 0000000000000000000000000000000000000000..a31bdd9f02fe75a87fceb2ebe8c36b3232a561cc --- /dev/null +++ b/src/main/java/io/papermc/paper/registry/RegistryHolder.java @@ -0,0 +1,47 @@ @@ -312,7 +323,7 @@ index 0000000000000000000000000000000000000000..02402ef647c3e78ed56fd6b2687bf7c6 + return this.delayedRegistry; + } + -+ void loadFrom(final DelayedRegistryEntry delayedEntry, final net.minecraft.core.Registry registry) { ++ void loadFrom(final DelayedRegistryEntry delayedEntry, final net.minecraft.core.Registry registry) { + final RegistryHolder delegateHolder = delayedEntry.delegate().createRegistryHolder(registry); + if (!(delegateHolder instanceof RegistryHolder.Memoized)) { + throw new IllegalArgumentException(delegateHolder + " must be a memoized holder"); @@ -323,7 +334,7 @@ index 0000000000000000000000000000000000000000..02402ef647c3e78ed56fd6b2687bf7c6 +} diff --git a/src/main/java/io/papermc/paper/registry/entry/ApiRegistryEntry.java b/src/main/java/io/papermc/paper/registry/entry/ApiRegistryEntry.java new file mode 100644 -index 0000000000000000000000000000000000000000..b2281a21eafd1f22f0ce261787e29af8a8637147 +index 0000000000000000000000000000000000000000..2295b0d145cbaabef5d29482c817575dcbe2ba54 --- /dev/null +++ b/src/main/java/io/papermc/paper/registry/entry/ApiRegistryEntry.java @@ -0,0 +1,27 @@ @@ -336,7 +347,7 @@ index 0000000000000000000000000000000000000000..b2281a21eafd1f22f0ce261787e29af8 +import net.minecraft.resources.ResourceKey; +import org.bukkit.Keyed; + -+public class ApiRegistryEntry extends BaseRegistryEntry> { ++public class ApiRegistryEntry extends BaseRegistryEntry { + + private final Supplier> registrySupplier; + @@ -356,7 +367,7 @@ index 0000000000000000000000000000000000000000..b2281a21eafd1f22f0ce261787e29af8 +} diff --git a/src/main/java/io/papermc/paper/registry/entry/BaseRegistryEntry.java b/src/main/java/io/papermc/paper/registry/entry/BaseRegistryEntry.java new file mode 100644 -index 0000000000000000000000000000000000000000..1be8a5feccd27779fcd8ebb2c362f17d78d307da +index 0000000000000000000000000000000000000000..ceb217dbbb84e8bd51365dd47bf91971e364d298 --- /dev/null +++ b/src/main/java/io/papermc/paper/registry/entry/BaseRegistryEntry.java @@ -0,0 +1,27 @@ @@ -367,7 +378,7 @@ index 0000000000000000000000000000000000000000..1be8a5feccd27779fcd8ebb2c362f17d +import net.minecraft.resources.ResourceKey; +import org.bukkit.Keyed; + -+public abstract class BaseRegistryEntry> implements RegistryEntry { // TODO remove Keyed ++public abstract class BaseRegistryEntry implements RegistryEntry { // TODO remove Keyed + + private final ResourceKey> minecraftRegistryKey; + private final RegistryKey apiRegistryKey; @@ -389,7 +400,7 @@ index 0000000000000000000000000000000000000000..1be8a5feccd27779fcd8ebb2c362f17d +} diff --git a/src/main/java/io/papermc/paper/registry/entry/CraftRegistryEntry.java b/src/main/java/io/papermc/paper/registry/entry/CraftRegistryEntry.java new file mode 100644 -index 0000000000000000000000000000000000000000..46b2560de884ef381cb7fc8669cad8f5a1fa3645 +index 0000000000000000000000000000000000000000..568984894a5463ccfa68bb6944b409ab0a2d7ad7 --- /dev/null +++ b/src/main/java/io/papermc/paper/registry/entry/CraftRegistryEntry.java @@ -0,0 +1,49 @@ @@ -408,13 +419,13 @@ index 0000000000000000000000000000000000000000..46b2560de884ef381cb7fc8669cad8f5 +import org.checkerframework.framework.qual.DefaultQualifier; + +@DefaultQualifier(NonNull.class) -+public class CraftRegistryEntry extends BaseRegistryEntry> { // TODO remove Keyed ++public class CraftRegistryEntry extends BaseRegistryEntry { // TODO remove Keyed + + private static final BiFunction EMPTY = (namespacedKey, apiVersion) -> namespacedKey; + + protected final Class classToPreload; + protected final BiFunction minecraftToBukkit; -+ private BiFunction updater = EMPTY; ++ protected BiFunction updater = EMPTY; + + protected CraftRegistryEntry( + final ResourceKey> mcKey, @@ -428,7 +439,7 @@ index 0000000000000000000000000000000000000000..46b2560de884ef381cb7fc8669cad8f5 + } + + @Override -+ public RegistryEntry> withSerializationUpdater(final BiFunction updater) { ++ public RegistryEntry withSerializationUpdater(final BiFunction updater) { + this.updater = updater; + return this; + } @@ -444,10 +455,10 @@ index 0000000000000000000000000000000000000000..46b2560de884ef381cb7fc8669cad8f5 +} diff --git a/src/main/java/io/papermc/paper/registry/entry/RegistryEntry.java b/src/main/java/io/papermc/paper/registry/entry/RegistryEntry.java new file mode 100644 -index 0000000000000000000000000000000000000000..c97bda87742852c921d73f4886721f1ee56b0a85 +index 0000000000000000000000000000000000000000..15991bf13894d850f360a520d1815711d25973ec --- /dev/null +++ b/src/main/java/io/papermc/paper/registry/entry/RegistryEntry.java -@@ -0,0 +1,52 @@ +@@ -0,0 +1,51 @@ +package io.papermc.paper.registry.entry; + +import io.papermc.paper.registry.RegistryHolder; @@ -459,17 +470,16 @@ index 0000000000000000000000000000000000000000..c97bda87742852c921d73f4886721f1e +import net.minecraft.resources.ResourceKey; +import org.bukkit.Keyed; +import org.bukkit.NamespacedKey; -+import org.bukkit.craftbukkit.CraftRegistry; +import org.bukkit.craftbukkit.util.ApiVersion; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.framework.qual.DefaultQualifier; + +@DefaultQualifier(NonNull.class) -+public interface RegistryEntry> extends RegistryEntryInfo { // TODO remove Keyed ++public interface RegistryEntry extends RegistryEntryInfo { // TODO remove Keyed + + RegistryHolder createRegistryHolder(Registry nmsRegistry); + -+ default RegistryEntry withSerializationUpdater(final BiFunction updater) { ++ default RegistryEntry withSerializationUpdater(final BiFunction updater) { + return this; + } + @@ -479,11 +489,11 @@ index 0000000000000000000000000000000000000000..c97bda87742852c921d73f4886721f1e + * as fields, but instead be obtained via {@link io.papermc.paper.registry.RegistryAccess#getRegistry(RegistryKey)} + */ + @Deprecated -+ default RegistryEntry delayed() { ++ default RegistryEntry delayed() { + return new DelayedRegistryEntry<>(this); + } + -+ static RegistryEntry> entry( ++ static RegistryEntry entry( + final ResourceKey> mcKey, + final RegistryKey apiKey, + final Class classToPreload, @@ -492,7 +502,7 @@ index 0000000000000000000000000000000000000000..c97bda87742852c921d73f4886721f1e + return new CraftRegistryEntry<>(mcKey, apiKey, classToPreload, minecraftToBukkit); + } + -+ static RegistryEntry> apiOnly( ++ static RegistryEntry apiOnly( + final ResourceKey> mcKey, + final RegistryKey apiKey, + final Supplier> apiRegistrySupplier @@ -592,7 +602,7 @@ index 0000000000000000000000000000000000000000..5562e8da5ebaef2a3add46e88d64358b +} diff --git a/src/main/java/io/papermc/paper/registry/legacy/DelayedRegistryEntry.java b/src/main/java/io/papermc/paper/registry/legacy/DelayedRegistryEntry.java new file mode 100644 -index 0000000000000000000000000000000000000000..5f615f50ac0cdbc47cf7a39b630b653e0d30cdf5 +index 0000000000000000000000000000000000000000..110b8d559f49f9e4f181b47663962a139a273a72 --- /dev/null +++ b/src/main/java/io/papermc/paper/registry/legacy/DelayedRegistryEntry.java @@ -0,0 +1,26 @@ @@ -605,7 +615,7 @@ index 0000000000000000000000000000000000000000..5f615f50ac0cdbc47cf7a39b630b653e +import net.minecraft.resources.ResourceKey; +import org.bukkit.Keyed; + -+public record DelayedRegistryEntry>(RegistryEntry delegate) implements RegistryEntry { ++public record DelayedRegistryEntry(RegistryEntry delegate) implements RegistryEntry { + + @Override + public ResourceKey> mcKey() { @@ -898,10 +908,10 @@ index 0000000000000000000000000000000000000000..b9d00e65639521eecd44bd2be3e01226 + } +} diff --git a/src/test/java/io/papermc/paper/registry/RegistryKeyTest.java b/src/test/java/io/papermc/paper/registry/RegistryKeyTest.java -index e1c14886064cde56be7fcd8f22a6ecb2d222a762..f4da2afd8977030e3200ac5d4bf51b7206a90bd7 100644 +index e1c14886064cde56be7fcd8f22a6ecb2d222a762..69cece1537bb558b80e1947fdb1fe25555e82628 100644 --- a/src/test/java/io/papermc/paper/registry/RegistryKeyTest.java +++ b/src/test/java/io/papermc/paper/registry/RegistryKeyTest.java -@@ -1,15 +1,18 @@ +@@ -1,15 +1,19 @@ package io.papermc.paper.registry; +import io.papermc.paper.registry.entry.RegistryEntry; @@ -912,6 +922,7 @@ index e1c14886064cde56be7fcd8f22a6ecb2d222a762..f4da2afd8977030e3200ac5d4bf51b72 import net.minecraft.resources.ResourceLocation; +import org.bukkit.Keyed; import org.bukkit.support.AbstractTestingBase; ++import org.checkerframework.checker.nullness.qual.Nullable; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -920,7 +931,7 @@ index e1c14886064cde56be7fcd8f22a6ecb2d222a762..f4da2afd8977030e3200ac5d4bf51b72 import static org.junit.jupiter.api.Assertions.assertTrue; class RegistryKeyTest extends AbstractTestingBase { -@@ -28,6 +31,12 @@ class RegistryKeyTest extends AbstractTestingBase { +@@ -28,6 +32,12 @@ class RegistryKeyTest extends AbstractTestingBase { void testApiRegistryKeysExist(final RegistryKey key) { final Optional> registry = AbstractTestingBase.REGISTRY_CUSTOM.registry(ResourceKey.createRegistryKey(ResourceLocation.parse(key.key().asString()))); assertTrue(registry.isPresent(), "Missing vanilla registry for " + key.key().asString()); @@ -929,7 +940,7 @@ index e1c14886064cde56be7fcd8f22a6ecb2d222a762..f4da2afd8977030e3200ac5d4bf51b72 + @ParameterizedTest + @MethodSource("data") + void testRegistryEntryExists(final RegistryKey key) { -+ final RegistryEntry entry = PaperRegistries.getEntry(key); ++ final @Nullable RegistryEntry entry = PaperRegistries.getEntry(key); + assertNotNull(entry, "Missing PaperRegistries entry for " + key); } } diff --git a/patches/server/0934-Add-Lifecycle-Event-system.patch b/patches/server/0934-Add-Lifecycle-Event-system.patch index 89149b0c71..1b988c3e25 100644 --- a/patches/server/0934-Add-Lifecycle-Event-system.patch +++ b/patches/server/0934-Add-Lifecycle-Event-system.patch @@ -54,7 +54,7 @@ index 30b50e6294c6eaade5e17cfaf34600d122e6251c..0bb7694188d5fb75bb756ce75d0060ea } diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/LifecycleEventRunner.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/LifecycleEventRunner.java new file mode 100644 -index 0000000000000000000000000000000000000000..f84c9c80e701231e5c33ac3c5573f1093e80f38b +index 0000000000000000000000000000000000000000..d0ef7fa0b3e5935d48f894596be6672b0016948a --- /dev/null +++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/LifecycleEventRunner.java @@ -0,0 +1,110 @@ @@ -117,15 +117,15 @@ index 0000000000000000000000000000000000000000..f84c9c80e701231e5c33ac3c5573f109 + } + + public void callEvent(final LifecycleEventType eventType, final E event, final Predicate ownerPredicate) { -+ final AbstractLifecycleEventType lifecycleEventType = (AbstractLifecycleEventType) eventType; -+ lifecycleEventType.forEachHandler(registeredHandler -> { ++ final AbstractLifecycleEventType lifecycleEventType = (AbstractLifecycleEventType) eventType; ++ lifecycleEventType.forEachHandler(event, registeredHandler -> { + try { + if (event instanceof final OwnerAwareLifecycleEvent ownerAwareEvent) { + ownerAwareGenericHelper(ownerAwareEvent, registeredHandler.owner()); + } + registeredHandler.lifecycleEventHandler().run(event); + } catch (final Throwable ex) { -+ LOGGER.error("Could not run '{}' lifecycle event handler from {}", lifecycleEventType.name(), registeredHandler.owner().getPluginMeta().getDisplayName(), ex); ++ throw new RuntimeException("Could not run '%s' lifecycle event handler from %s".formatted(lifecycleEventType.name(), registeredHandler.owner().getPluginMeta().getDisplayName()), ex); + } finally { + if (event instanceof final OwnerAwareLifecycleEvent ownerAwareEvent) { + ownerAwareEvent.setOwner(null); @@ -151,7 +151,7 @@ index 0000000000000000000000000000000000000000..f84c9c80e701231e5c33ac3c5573f109 + } + + private void removeEventHandlersOwnedBy(final LifecycleEventType eventType, final Plugin possibleOwner) { -+ final AbstractLifecycleEventType lifecycleEventType = (AbstractLifecycleEventType) eventType; ++ final AbstractLifecycleEventType lifecycleEventType = (AbstractLifecycleEventType) eventType; + lifecycleEventType.removeMatching(registeredHandler -> registeredHandler.owner().getPluginMeta().getName().equals(possibleOwner.getPluginMeta().getName())); + } + @@ -186,7 +186,7 @@ index 0000000000000000000000000000000000000000..e941405269a773e8a77e26ffd1afd84f +} diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/PaperLifecycleEventManager.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/PaperLifecycleEventManager.java new file mode 100644 -index 0000000000000000000000000000000000000000..f1be5b9a29435bae0afd2bd951bfe88d1669e7eb +index 0000000000000000000000000000000000000000..d05334016bd01201c755dea04c0cea56b6dfcb50 --- /dev/null +++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/PaperLifecycleEventManager.java @@ -0,0 +1,26 @@ @@ -213,15 +213,15 @@ index 0000000000000000000000000000000000000000..f1be5b9a29435bae0afd2bd951bfe88d + @Override + public void registerEventHandler(final LifecycleEventHandlerConfiguration handlerConfiguration) { + Preconditions.checkState(this.registrationCheck.getAsBoolean(), "Cannot register lifecycle event handlers"); -+ ((AbstractLifecycleEventHandlerConfiguration) handlerConfiguration).registerFrom(this.owner); ++ ((AbstractLifecycleEventHandlerConfiguration) handlerConfiguration).registerFrom(this.owner); + } +} diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/AbstractLifecycleEventHandlerConfiguration.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/AbstractLifecycleEventHandlerConfiguration.java new file mode 100644 -index 0000000000000000000000000000000000000000..6a85a4f581612efff04c1a955493aa2e32476277 +index 0000000000000000000000000000000000000000..fa216e6fd804859293385ed43c53dfca057f317f --- /dev/null +++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/AbstractLifecycleEventHandlerConfiguration.java -@@ -0,0 +1,26 @@ +@@ -0,0 +1,28 @@ +package io.papermc.paper.plugin.lifecycle.event.handler.configuration; + +import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent; @@ -232,28 +232,30 @@ index 0000000000000000000000000000000000000000..6a85a4f581612efff04c1a955493aa2e +import org.checkerframework.framework.qual.DefaultQualifier; + +@DefaultQualifier(NonNull.class) -+public abstract class AbstractLifecycleEventHandlerConfiguration> implements LifecycleEventHandlerConfiguration { ++public abstract class AbstractLifecycleEventHandlerConfiguration implements LifecycleEventHandlerConfiguration { + + private final LifecycleEventHandler handler; -+ private final AbstractLifecycleEventType type; ++ private final AbstractLifecycleEventType type; + -+ protected AbstractLifecycleEventHandlerConfiguration(final LifecycleEventHandler handler, final AbstractLifecycleEventType type) { ++ protected AbstractLifecycleEventHandlerConfiguration(final LifecycleEventHandler handler, final AbstractLifecycleEventType type) { + this.handler = handler; + this.type = type; + } + -+ public abstract CI config(); -+ + public final void registerFrom(final O owner) { -+ this.type.tryRegister(owner, this.handler, this.config()); ++ this.type.tryRegister(owner, this); ++ } ++ ++ public LifecycleEventHandler handler() { ++ return this.handler; + } +} diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/MonitorLifecycleEventHandlerConfigurationImpl.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/MonitorLifecycleEventHandlerConfigurationImpl.java new file mode 100644 -index 0000000000000000000000000000000000000000..e0699fcd0a098abc5e1206e7c0fa80b96eca7884 +index 0000000000000000000000000000000000000000..ab444d60d72bd692843052df5d7b24fbb5621cf7 --- /dev/null +++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/MonitorLifecycleEventHandlerConfigurationImpl.java -@@ -0,0 +1,33 @@ +@@ -0,0 +1,28 @@ +package io.papermc.paper.plugin.lifecycle.event.handler.configuration; + +import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent; @@ -264,19 +266,14 @@ index 0000000000000000000000000000000000000000..e0699fcd0a098abc5e1206e7c0fa80b9 +import org.checkerframework.framework.qual.DefaultQualifier; + +@DefaultQualifier(NonNull.class) -+public class MonitorLifecycleEventHandlerConfigurationImpl extends AbstractLifecycleEventHandlerConfiguration> implements MonitorLifecycleEventHandlerConfiguration { ++public class MonitorLifecycleEventHandlerConfigurationImpl extends AbstractLifecycleEventHandlerConfiguration implements MonitorLifecycleEventHandlerConfiguration { + + private boolean monitor = false; + -+ public MonitorLifecycleEventHandlerConfigurationImpl(final LifecycleEventHandler handler, final AbstractLifecycleEventType> eventType) { ++ public MonitorLifecycleEventHandlerConfigurationImpl(final LifecycleEventHandler handler, final AbstractLifecycleEventType eventType) { + super(handler, eventType); + } + -+ @Override -+ public MonitorLifecycleEventHandlerConfigurationImpl config() { -+ return this; -+ } -+ + public boolean isMonitor() { + return this.monitor; + } @@ -289,10 +286,10 @@ index 0000000000000000000000000000000000000000..e0699fcd0a098abc5e1206e7c0fa80b9 +} diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/PrioritizedLifecycleEventHandlerConfigurationImpl.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/PrioritizedLifecycleEventHandlerConfigurationImpl.java new file mode 100644 -index 0000000000000000000000000000000000000000..c1d0070fc1594f7a7c29d7dc679da7b347a7140b +index 0000000000000000000000000000000000000000..ccdad31717bf12b844cbeaf11a49247485ec77f1 --- /dev/null +++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/PrioritizedLifecycleEventHandlerConfigurationImpl.java -@@ -0,0 +1,43 @@ +@@ -0,0 +1,40 @@ +package io.papermc.paper.plugin.lifecycle.event.handler.configuration; + +import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent; @@ -304,22 +301,19 @@ index 0000000000000000000000000000000000000000..c1d0070fc1594f7a7c29d7dc679da7b3 +import org.checkerframework.framework.qual.DefaultQualifier; + +@DefaultQualifier(NonNull.class) -+public class PrioritizedLifecycleEventHandlerConfigurationImpl extends AbstractLifecycleEventHandlerConfiguration> implements PrioritizedLifecycleEventHandlerConfiguration { ++public class PrioritizedLifecycleEventHandlerConfigurationImpl ++ extends AbstractLifecycleEventHandlerConfiguration ++ implements PrioritizedLifecycleEventHandlerConfiguration { + + private static final OptionalInt DEFAULT_PRIORITY = OptionalInt.of(0); + private static final OptionalInt MONITOR_PRIORITY = OptionalInt.empty(); + + private OptionalInt priority = DEFAULT_PRIORITY; + -+ public PrioritizedLifecycleEventHandlerConfigurationImpl(final LifecycleEventHandler handler, final AbstractLifecycleEventType> eventType) { ++ public PrioritizedLifecycleEventHandlerConfigurationImpl(final LifecycleEventHandler handler, final AbstractLifecycleEventType eventType) { + super(handler, eventType); + } + -+ @Override -+ public PrioritizedLifecycleEventHandlerConfigurationImpl config() { -+ return this; -+ } -+ + public OptionalInt priority() { + return this.priority; + } @@ -435,10 +429,10 @@ index 0000000000000000000000000000000000000000..6d530c52aaf0dc2cdfe3bd56af557274 +} diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/AbstractLifecycleEventType.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/AbstractLifecycleEventType.java new file mode 100644 -index 0000000000000000000000000000000000000000..a65fb37f4a729e2fe9fb81af822db626ec7e6d7b +index 0000000000000000000000000000000000000000..9359a36d26970742da3a7abb0050158cd6c64e8e --- /dev/null +++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/AbstractLifecycleEventType.java -@@ -0,0 +1,50 @@ +@@ -0,0 +1,54 @@ +package io.papermc.paper.plugin.lifecycle.event.types; + +import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent; @@ -453,7 +447,7 @@ index 0000000000000000000000000000000000000000..a65fb37f4a729e2fe9fb81af822db626 +import org.checkerframework.framework.qual.DefaultQualifier; + +@DefaultQualifier(NonNull.class) -+public abstract class AbstractLifecycleEventType, CI extends AbstractLifecycleEventHandlerConfiguration> implements LifecycleEventType { ++public abstract class AbstractLifecycleEventType> implements LifecycleEventType { + + private final String name; + private final Class ownerType; @@ -474,24 +468,28 @@ index 0000000000000000000000000000000000000000..a65fb37f4a729e2fe9fb81af822db626 + } + } + -+ public abstract void forEachHandler(Consumer> consumer, Predicate> predicate); ++ public abstract void forEachHandler(E event, Consumer> consumer, Predicate> predicate); + -+ public abstract void removeMatching(Predicate> predicate); ++ public abstract void removeMatching(Predicate> predicate); + -+ protected abstract void register(O owner, LifecycleEventHandler handler, CI config); ++ protected abstract void register(O owner, AbstractLifecycleEventHandlerConfiguration config); + -+ public final void tryRegister(final O owner, final LifecycleEventHandler handler, final CI config) { ++ public final void tryRegister(final O owner, final AbstractLifecycleEventHandlerConfiguration config) { + this.verifyOwner(owner); + LifecycleEventRunner.INSTANCE.checkRegisteredHandler(owner, this); -+ this.register(owner, handler, config); ++ this.register(owner, config); + } + -+ public record RegisteredHandler(O owner, LifecycleEventHandler lifecycleEventHandler) { ++ public record RegisteredHandler(O owner, AbstractLifecycleEventHandlerConfiguration config) { ++ ++ public LifecycleEventHandler lifecycleEventHandler() { ++ return this.config().handler(); ++ } + } +} diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/LifecycleEventTypeProviderImpl.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/LifecycleEventTypeProviderImpl.java new file mode 100644 -index 0000000000000000000000000000000000000000..0886edad92b40276f268bd745b31bac359fd28af +index 0000000000000000000000000000000000000000..af0cb3298d9c737417c6e54b360f8dc50a5caf04 --- /dev/null +++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/LifecycleEventTypeProviderImpl.java @@ -0,0 +1,25 @@ @@ -517,20 +515,21 @@ index 0000000000000000000000000000000000000000..0886edad92b40276f268bd745b31bac3 + + @Override + public LifecycleEventType.Prioritizable prioritized(final String name, final Class ownerType) { -+ return LifecycleEventRunner.INSTANCE.addEventType(new PrioritizableLifecycleEventType<>(name, ownerType)); ++ return LifecycleEventRunner.INSTANCE.addEventType(new PrioritizableLifecycleEventType.Simple<>(name, ownerType)); + } +} diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/MonitorableLifecycleEventType.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/MonitorableLifecycleEventType.java new file mode 100644 -index 0000000000000000000000000000000000000000..6d92c1d3adf220154dfe7cba3a3f8158356c3e3c +index 0000000000000000000000000000000000000000..c71912f0050ce0cc6e416948a354c8a66da606a8 --- /dev/null +++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/MonitorableLifecycleEventType.java -@@ -0,0 +1,54 @@ +@@ -0,0 +1,58 @@ +package io.papermc.paper.plugin.lifecycle.event.types; + +import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent; +import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner; +import io.papermc.paper.plugin.lifecycle.event.handler.LifecycleEventHandler; ++import io.papermc.paper.plugin.lifecycle.event.handler.configuration.AbstractLifecycleEventHandlerConfiguration; +import io.papermc.paper.plugin.lifecycle.event.handler.configuration.MonitorLifecycleEventHandlerConfiguration; +import io.papermc.paper.plugin.lifecycle.event.handler.configuration.MonitorLifecycleEventHandlerConfigurationImpl; +import java.util.ArrayList; @@ -541,7 +540,7 @@ index 0000000000000000000000000000000000000000..6d92c1d3adf220154dfe7cba3a3f8158 +import org.checkerframework.framework.qual.DefaultQualifier; + +@DefaultQualifier(NonNull.class) -+public class MonitorableLifecycleEventType extends AbstractLifecycleEventType, MonitorLifecycleEventHandlerConfigurationImpl> implements LifecycleEventType.Monitorable { ++public class MonitorableLifecycleEventType extends AbstractLifecycleEventType> implements LifecycleEventType.Monitorable { + + final List> handlers = new ArrayList<>(); + int nonMonitorIdx = 0; @@ -556,9 +555,12 @@ index 0000000000000000000000000000000000000000..6d92c1d3adf220154dfe7cba3a3f8158 + } + + @Override -+ protected void register(final O owner, final LifecycleEventHandler handler, final MonitorLifecycleEventHandlerConfigurationImpl config) { -+ final RegisteredHandler registeredHandler = new RegisteredHandler<>(owner, handler); -+ if (!config.isMonitor()) { ++ protected void register(final O owner, final AbstractLifecycleEventHandlerConfiguration config) { ++ if (!(config instanceof final MonitorLifecycleEventHandlerConfigurationImpl monitor)) { ++ throw new IllegalArgumentException("Configuration must be a MonitorLifecycleEventHandlerConfiguration"); ++ } ++ final RegisteredHandler registeredHandler = new RegisteredHandler<>(owner, config); ++ if (!monitor.isMonitor()) { + this.handlers.add(this.nonMonitorIdx, registeredHandler); + this.nonMonitorIdx++; + } else { @@ -567,7 +569,7 @@ index 0000000000000000000000000000000000000000..6d92c1d3adf220154dfe7cba3a3f8158 + } + + @Override -+ public void forEachHandler(final Consumer> consumer, final Predicate> predicate) { ++ public void forEachHandler(final E event, final Consumer> consumer, final Predicate> predicate) { + for (final RegisteredHandler handler : this.handlers) { + if (predicate.test(handler)) { + consumer.accept(handler); @@ -576,7 +578,7 @@ index 0000000000000000000000000000000000000000..6d92c1d3adf220154dfe7cba3a3f8158 + } + + @Override -+ public void removeMatching(final Predicate> predicate) { ++ public void removeMatching(final Predicate> predicate) { + this.handlers.removeIf(predicate); + } +} @@ -603,30 +605,35 @@ index 0000000000000000000000000000000000000000..3e7e7474f301c0725fa2bcd6e19e476f +} diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/PrioritizableLifecycleEventType.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/PrioritizableLifecycleEventType.java new file mode 100644 -index 0000000000000000000000000000000000000000..6629f7fabf66ce761024268043cc30076ba8a3f1 +index 0000000000000000000000000000000000000000..76f92a6fc84c0315f3973dc4e92649b66babc3d5 --- /dev/null +++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/PrioritizableLifecycleEventType.java -@@ -0,0 +1,64 @@ +@@ -0,0 +1,74 @@ +package io.papermc.paper.plugin.lifecycle.event.types; + ++import com.google.common.base.Preconditions; +import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent; +import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner; +import io.papermc.paper.plugin.lifecycle.event.handler.LifecycleEventHandler; ++import io.papermc.paper.plugin.lifecycle.event.handler.configuration.AbstractLifecycleEventHandlerConfiguration; +import io.papermc.paper.plugin.lifecycle.event.handler.configuration.PrioritizedLifecycleEventHandlerConfiguration; +import io.papermc.paper.plugin.lifecycle.event.handler.configuration.PrioritizedLifecycleEventHandlerConfigurationImpl; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; -+import java.util.OptionalInt; +import java.util.function.Consumer; +import java.util.function.Predicate; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.framework.qual.DefaultQualifier; + +@DefaultQualifier(NonNull.class) -+public class PrioritizableLifecycleEventType extends AbstractLifecycleEventType, PrioritizedLifecycleEventHandlerConfigurationImpl> implements LifecycleEventType.Prioritizable { ++public abstract class PrioritizableLifecycleEventType< ++ O extends LifecycleEventOwner, ++ E extends LifecycleEvent, ++ C extends PrioritizedLifecycleEventHandlerConfiguration ++> extends AbstractLifecycleEventType { + -+ private static final Comparator> COMPARATOR = Comparator.comparing(PrioritizedHandler::priority, (o1, o2) -> { ++ private static final Comparator> COMPARATOR = Comparator.comparing(handler -> ((PrioritizedLifecycleEventHandlerConfigurationImpl) handler.config()).priority(), (o1, o2) -> { + if (o1.equals(o2)) { + return 0; + } else if (o1.isEmpty()) { @@ -638,38 +645,43 @@ index 0000000000000000000000000000000000000000..6629f7fabf66ce761024268043cc3007 + } + }); + -+ private final List> handlers = new ArrayList<>(); ++ private final List> handlers = new ArrayList<>(); + + public PrioritizableLifecycleEventType(final String name, final Class ownerType) { + super(name, ownerType); + } + + @Override -+ public PrioritizedLifecycleEventHandlerConfiguration newHandler(final LifecycleEventHandler handler) { -+ return new PrioritizedLifecycleEventHandlerConfigurationImpl<>(handler, this); -+ } -+ -+ @Override -+ protected void register(final O owner, final LifecycleEventHandler handler, final PrioritizedLifecycleEventHandlerConfigurationImpl config) { -+ this.handlers.add(new PrioritizedHandler<>(new RegisteredHandler<>(owner, handler), config.priority())); ++ protected void register(final O owner, final AbstractLifecycleEventHandlerConfiguration config) { ++ Preconditions.checkArgument(config instanceof PrioritizedLifecycleEventHandlerConfigurationImpl, "Configuration must be a PrioritizedLifecycleEventHandlerConfiguration"); ++ this.handlers.add(new RegisteredHandler<>(owner, config)); + this.handlers.sort(COMPARATOR); + } + + @Override -+ public void forEachHandler(final Consumer> consumer, final Predicate> predicate) { -+ for (final PrioritizedHandler handler : this.handlers) { -+ if (predicate.test(handler.handler())) { -+ consumer.accept(handler.handler()); ++ public void forEachHandler(final E event, final Consumer> consumer, final Predicate> predicate) { ++ for (final RegisteredHandler handler : this.handlers) { ++ if (predicate.test(handler)) { ++ consumer.accept(handler); + } + } + } + + @Override -+ public void removeMatching(final Predicate> predicate) { -+ this.handlers.removeIf(prioritizedHandler -> predicate.test(prioritizedHandler.handler())); ++ public void removeMatching(final Predicate> predicate) { ++ this.handlers.removeIf(predicate); + } + -+ private record PrioritizedHandler(RegisteredHandler handler, OptionalInt priority) {} ++ public static class Simple extends PrioritizableLifecycleEventType> implements LifecycleEventType.Prioritizable { ++ public Simple(final String name, final Class ownerType) { ++ super(name, ownerType); ++ } ++ ++ @Override ++ public PrioritizedLifecycleEventHandlerConfiguration newHandler(final LifecycleEventHandler handler) { ++ return new PrioritizedLifecycleEventHandlerConfigurationImpl<>(handler, this); ++ } ++ } +} diff --git a/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java b/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java index 834b85f24df023642f8abf7213fe578ac8c17a3e..3e82ea07ca4194844c5528446e2c4a46ff4acee5 100644 diff --git a/patches/server/0975-Brigadier-based-command-API.patch b/patches/server/0975-Brigadier-based-command-API.patch index 96d91304d7..51f1f66e5f 100644 --- a/patches/server/0975-Brigadier-based-command-API.patch +++ b/patches/server/0975-Brigadier-based-command-API.patch @@ -1070,7 +1070,7 @@ index 0000000000000000000000000000000000000000..72966584089d3fee9778f572727c9b7f +} diff --git a/src/main/java/io/papermc/paper/command/brigadier/argument/VanillaArgumentProviderImpl.java b/src/main/java/io/papermc/paper/command/brigadier/argument/VanillaArgumentProviderImpl.java new file mode 100644 -index 0000000000000000000000000000000000000000..93edb22c8500e79f86b101ef38955bca45a8d3a9 +index 0000000000000000000000000000000000000000..1b389cd0e77c24874b2a825608b612e3fc4f3dd6 --- /dev/null +++ b/src/main/java/io/papermc/paper/command/brigadier/argument/VanillaArgumentProviderImpl.java @@ -0,0 +1,354 @@ @@ -1361,7 +1361,7 @@ index 0000000000000000000000000000000000000000..93edb22c8500e79f86b101ef38955bca + @Override + public ArgumentType> resourceKey(final RegistryKey registryKey) { + return this.wrap( -+ ResourceKeyArgument.key(PaperRegistries.toNms(registryKey)), ++ ResourceKeyArgument.key(PaperRegistries.registryToNms(registryKey)), + nmsRegistryKey -> TypedKey.create(registryKey, CraftNamespacedKey.fromMinecraft(nmsRegistryKey.location())) + ); + } @@ -1375,7 +1375,7 @@ index 0000000000000000000000000000000000000000..93edb22c8500e79f86b101ef38955bca + private ArgumentType resourceRaw(final RegistryKey registryKeyRaw) { // TODO remove Keyed + final RegistryKey registryKey = registryKeyRaw; + return (ArgumentType) this.wrap( -+ ResourceArgument.resource(PaperCommands.INSTANCE.getBuildContext(), PaperRegistries.toNms(registryKey)), ++ ResourceArgument.resource(PaperCommands.INSTANCE.getBuildContext(), PaperRegistries.registryToNms(registryKey)), + resource -> requireNonNull( + RegistryAccess.registryAccess() + .getRegistry(registryKey) @@ -1982,7 +1982,7 @@ index 0000000000000000000000000000000000000000..0c3c82b28e581286b798ee58ca4193ef + +} diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/LifecycleEventRunner.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/LifecycleEventRunner.java -index f84c9c80e701231e5c33ac3c5573f1093e80f38b..6c072e44a8144de6658b4eb818c996f0eac5805b 100644 +index d0ef7fa0b3e5935d48f894596be6672b0016948a..cca76f2d1623952017a83fdb027f77a601c79b3e 100644 --- a/src/main/java/io/papermc/paper/plugin/lifecycle/event/LifecycleEventRunner.java +++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/LifecycleEventRunner.java @@ -9,6 +9,7 @@ import io.papermc.paper.plugin.lifecycle.event.registrar.RegistrarEventImpl; diff --git a/patches/server/1021-Registry-Modification-API.patch b/patches/server/1021-Registry-Modification-API.patch new file mode 100644 index 0000000000..d8e13337bd --- /dev/null +++ b/patches/server/1021-Registry-Modification-API.patch @@ -0,0 +1,1427 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Mon, 27 Feb 2023 18:28:39 -0800 +Subject: [PATCH] Registry Modification API + +== AT == +public net.minecraft.core.MappedRegistry validateWrite(Lnet/minecraft/resources/ResourceKey;)V +public net.minecraft.resources.RegistryOps lookupProvider +public net.minecraft.resources.RegistryOps$HolderLookupAdapter + +diff --git a/src/main/java/io/papermc/paper/registry/PaperRegistries.java b/src/main/java/io/papermc/paper/registry/PaperRegistries.java +index 1e098dc25bd338ff179491ff3382ac56aad9948e..a688af29273ebfbb4f75dd74cd30627fc481c96c 100644 +--- a/src/main/java/io/papermc/paper/registry/PaperRegistries.java ++++ b/src/main/java/io/papermc/paper/registry/PaperRegistries.java +@@ -2,6 +2,7 @@ package io.papermc.paper.registry; + + import io.papermc.paper.adventure.PaperAdventure; + import io.papermc.paper.registry.entry.RegistryEntry; ++import io.papermc.paper.registry.tag.TagKey; + import java.util.Collections; + import java.util.IdentityHashMap; + import java.util.List; +@@ -46,6 +47,7 @@ import org.checkerframework.framework.qual.DefaultQualifier; + + import static io.papermc.paper.registry.entry.RegistryEntry.apiOnly; + import static io.papermc.paper.registry.entry.RegistryEntry.entry; ++import static io.papermc.paper.registry.entry.RegistryEntry.writable; + + @DefaultQualifier(NonNull.class) + public final class PaperRegistries { +@@ -128,6 +130,15 @@ public final class PaperRegistries { + return ResourceKey.create((ResourceKey>) PaperRegistries.registryToNms(typedKey.registryKey()), PaperAdventure.asVanilla(typedKey.key())); + } + ++ public static TagKey fromNms(final net.minecraft.tags.TagKey tagKey) { ++ return TagKey.create(registryFromNms(tagKey.registry()), CraftNamespacedKey.fromMinecraft(tagKey.location())); ++ } ++ ++ @SuppressWarnings({"unchecked", "RedundantCast"}) ++ public static net.minecraft.tags.TagKey toNms(final TagKey tagKey) { ++ return net.minecraft.tags.TagKey.create((ResourceKey>) registryToNms(tagKey.registryKey()), PaperAdventure.asVanilla(tagKey.key())); ++ } ++ + private PaperRegistries() { + } + } +diff --git a/src/main/java/io/papermc/paper/registry/PaperRegistryAccess.java b/src/main/java/io/papermc/paper/registry/PaperRegistryAccess.java +index d591e3a2e19d5358a0d25a5a681368943622d231..f05ebf453406a924da3de6fb250f4793a1b3c612 100644 +--- a/src/main/java/io/papermc/paper/registry/PaperRegistryAccess.java ++++ b/src/main/java/io/papermc/paper/registry/PaperRegistryAccess.java +@@ -80,6 +80,14 @@ public class PaperRegistryAccess implements RegistryAccess { + return possiblyUnwrap(registryHolder.get()); + } + ++ public > WritableCraftRegistry getWritableRegistry(final RegistryKey key) { ++ final Registry registry = this.getRegistry(key); ++ if (registry instanceof WritableCraftRegistry) { ++ return (WritableCraftRegistry) registry; ++ } ++ throw new IllegalArgumentException(key + " does not point to a writable registry"); ++ } ++ + private static Registry possiblyUnwrap(final Registry registry) { + if (registry instanceof final DelayedRegistry delayedRegistry) { // if not coming from legacy, unwrap the delayed registry + return delayedRegistry.delegate(); +diff --git a/src/main/java/io/papermc/paper/registry/PaperRegistryBuilder.java b/src/main/java/io/papermc/paper/registry/PaperRegistryBuilder.java +new file mode 100644 +index 0000000000000000000000000000000000000000..6932ffef54f90cc486f517561f73c2e4daf3983b +--- /dev/null ++++ b/src/main/java/io/papermc/paper/registry/PaperRegistryBuilder.java +@@ -0,0 +1,26 @@ ++package io.papermc.paper.registry; ++ ++import io.papermc.paper.registry.data.util.Conversions; ++import net.minecraft.resources.RegistryOps; ++import org.checkerframework.checker.nullness.qual.Nullable; ++ ++public interface PaperRegistryBuilder extends RegistryBuilder { ++ ++ M build(); ++ ++ @FunctionalInterface ++ interface Filler> { ++ ++ B fill(@Nullable Conversions conversions, TypedKey key, @Nullable M nms); ++ ++ default Factory asFactory() { ++ return (lookup, key) -> this.fill(lookup, key, null); ++ } ++ } ++ ++ @FunctionalInterface ++ interface Factory> { ++ ++ B create(@Nullable Conversions conversions, TypedKey key); ++ } ++} +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..a7f2b264d4f37f5293ae72195b4c78faf35351c9 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/registry/PaperRegistryListenerManager.java +@@ -0,0 +1,183 @@ ++package io.papermc.paper.registry; ++ ++import com.google.common.base.Preconditions; ++import com.mojang.serialization.Lifecycle; ++import io.papermc.paper.plugin.bootstrap.BootstrapContext; ++import io.papermc.paper.plugin.entrypoint.Entrypoint; ++import io.papermc.paper.plugin.entrypoint.LaunchEntryPointHandler; ++import io.papermc.paper.plugin.lifecycle.event.LifecycleEventRunner; ++import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEventType; ++import io.papermc.paper.registry.data.util.Conversions; ++import io.papermc.paper.registry.entry.RegistryEntry; ++import io.papermc.paper.registry.entry.RegistryEntryInfo; ++import io.papermc.paper.registry.event.RegistryEntryAddEventImpl; ++import io.papermc.paper.registry.event.RegistryEventMap; ++import io.papermc.paper.registry.event.RegistryEventProvider; ++import io.papermc.paper.registry.event.RegistryFreezeEvent; ++import io.papermc.paper.registry.event.RegistryFreezeEventImpl; ++import io.papermc.paper.registry.event.type.RegistryEntryAddEventType; ++import io.papermc.paper.registry.event.type.RegistryEntryAddEventTypeImpl; ++import io.papermc.paper.registry.event.type.RegistryLifecycleEventType; ++import java.util.Optional; ++import net.kyori.adventure.key.Key; ++import net.minecraft.core.Holder; ++import net.minecraft.core.MappedRegistry; ++import net.minecraft.core.RegistrationInfo; ++import net.minecraft.core.Registry; ++import net.minecraft.core.WritableRegistry; ++import net.minecraft.core.registries.BuiltInRegistries; ++import net.minecraft.resources.ResourceKey; ++import net.minecraft.resources.ResourceLocation; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.intellij.lang.annotations.Subst; ++ ++public final class PaperRegistryListenerManager { ++ ++ public static final PaperRegistryListenerManager INSTANCE = new PaperRegistryListenerManager(); ++ ++ public final RegistryEventMap valueAddHooks = new RegistryEventMap("value add"); ++ public final RegistryEventMap freezeHooks = new RegistryEventMap("freeze"); ++ ++ private PaperRegistryListenerManager() { ++ } ++ ++ /** ++ * For {@link Registry#register(Registry, String, Object)} ++ */ ++ public M registerWithListeners(final Registry registry, final String id, final M nms) { ++ return this.registerWithListeners(registry, ResourceLocation.withDefaultNamespace(id), nms); ++ } ++ ++ /** ++ * For {@link Registry#register(Registry, ResourceLocation, Object)} ++ */ ++ public M registerWithListeners(final Registry registry, final ResourceLocation loc, final M nms) { ++ return this.registerWithListeners(registry, ResourceKey.create(registry.key(), loc), nms); ++ } ++ ++ /** ++ * For {@link Registry#register(Registry, ResourceKey, Object)} ++ */ ++ public M registerWithListeners(final Registry registry, final ResourceKey key, final M nms) { ++ return this.registerWithListeners(registry, key, nms, RegistrationInfo.BUILT_IN, PaperRegistryListenerManager::registerWithInstance, BuiltInRegistries.BUILT_IN_CONVERSIONS); ++ } ++ ++ /** ++ * For {@link Registry#registerForHolder(Registry, ResourceLocation, Object)} ++ */ ++ public Holder.Reference registerForHolderWithListeners(final Registry registry, final ResourceLocation loc, final M nms) { ++ return this.registerForHolderWithListeners(registry, ResourceKey.create(registry.key(), loc), nms); ++ } ++ ++ /** ++ * For {@link Registry#registerForHolder(Registry, ResourceKey, Object)} ++ */ ++ public Holder.Reference registerForHolderWithListeners(final Registry registry, final ResourceKey key, final M nms) { ++ return this.registerWithListeners(registry, key, nms, RegistrationInfo.BUILT_IN, WritableRegistry::register, BuiltInRegistries.BUILT_IN_CONVERSIONS); ++ } ++ ++ public void registerWithListeners( ++ final Registry registry, ++ final ResourceKey key, ++ final M nms, ++ final RegistrationInfo registrationInfo, ++ final Conversions conversions ++ ) { ++ this.registerWithListeners(registry, key, nms, registrationInfo, WritableRegistry::register, conversions); ++ } ++ ++ // TODO remove Keyed ++ public , R> R registerWithListeners( ++ final Registry registry, ++ final ResourceKey key, ++ final M nms, ++ final RegistrationInfo registrationInfo, ++ final RegisterMethod registerMethod, ++ final Conversions conversions ++ ) { ++ Preconditions.checkState(LaunchEntryPointHandler.INSTANCE.hasEntered(Entrypoint.BOOTSTRAPPER), registry.key() + " tried to run modification listeners before bootstrappers have been called"); // verify that bootstrappers have been called ++ final @Nullable RegistryEntryInfo entry = PaperRegistries.getEntry(registry.key()); ++ if (!RegistryEntry.Modifiable.isModifiable(entry) || !this.valueAddHooks.hasHooks(entry.apiKey())) { ++ return registerMethod.register((WritableRegistry) registry, key, nms, registrationInfo); ++ } ++ final RegistryEntry.Modifiable modifiableEntry = RegistryEntry.Modifiable.asModifiable(entry); ++ @SuppressWarnings("PatternValidation") final TypedKey typedKey = TypedKey.create(entry.apiKey(), Key.key(key.location().getNamespace(), key.location().getPath())); ++ final B builder = modifiableEntry.fillBuilder(conversions, typedKey, nms); ++ return this.registerWithListeners(registry, modifiableEntry, key, nms, builder, registrationInfo, registerMethod, conversions); ++ } ++ ++ > void registerWithListeners( // TODO remove Keyed ++ final WritableRegistry registry, ++ final RegistryEntryInfo entry, ++ final ResourceKey key, ++ final B builder, ++ final RegistrationInfo registrationInfo, ++ final Conversions conversions ++ ) { ++ if (!RegistryEntry.Modifiable.isModifiable(entry) || !this.valueAddHooks.hasHooks(entry.apiKey())) { ++ registry.register(key, builder.build(), registrationInfo); ++ return; ++ } ++ this.registerWithListeners(registry, RegistryEntry.Modifiable.asModifiable(entry), key, null, builder, registrationInfo, WritableRegistry::register, conversions); ++ } ++ ++ public , R> R registerWithListeners( // TODO remove Keyed ++ final Registry registry, ++ final RegistryEntry.Modifiable entry, ++ final ResourceKey key, ++ final @Nullable M oldNms, ++ final B builder, ++ RegistrationInfo registrationInfo, ++ final RegisterMethod registerMethod, ++ final Conversions conversions ++ ) { ++ @Subst("namespace:key") final ResourceLocation beingAdded = key.location(); ++ @SuppressWarnings("PatternValidation") final TypedKey typedKey = TypedKey.create(entry.apiKey(), Key.key(beingAdded.getNamespace(), beingAdded.getPath())); ++ final RegistryEntryAddEventImpl event = entry.createEntryAddEvent(typedKey, builder, conversions); ++ LifecycleEventRunner.INSTANCE.callEvent(this.valueAddHooks.getHook(entry.apiKey()), event); ++ if (oldNms != null) { ++ ((MappedRegistry) registry).clearIntrusiveHolder(oldNms); ++ } ++ final M newNms = event.builder().build(); ++ if (oldNms != null && !newNms.equals(oldNms)) { ++ registrationInfo = new RegistrationInfo(Optional.empty(), Lifecycle.experimental()); ++ } ++ return registerMethod.register((WritableRegistry) registry, key, newNms, registrationInfo); ++ } ++ ++ private static M registerWithInstance(final WritableRegistry writableRegistry, final ResourceKey key, final M value, final RegistrationInfo registrationInfo) { ++ writableRegistry.register(key, value, registrationInfo); ++ return value; ++ } ++ ++ @FunctionalInterface ++ public interface RegisterMethod { ++ ++ R register(WritableRegistry writableRegistry, ResourceKey key, M value, RegistrationInfo registrationInfo); ++ } ++ ++ public > void runFreezeListeners(final ResourceKey> resourceKey, final Conversions conversions) { ++ final @Nullable RegistryEntryInfo entry = PaperRegistries.getEntry(resourceKey); ++ if (!RegistryEntry.Addable.isAddable(entry) || !this.freezeHooks.hasHooks(entry.apiKey())) { ++ return; ++ } ++ final RegistryEntry.Addable writableEntry = RegistryEntry.Addable.asAddable(entry); ++ final WritableCraftRegistry writableRegistry = PaperRegistryAccess.instance().getWritableRegistry(entry.apiKey()); ++ final RegistryFreezeEventImpl event = writableEntry.createFreezeEvent(writableRegistry, conversions); ++ LifecycleEventRunner.INSTANCE.callEvent(this.freezeHooks.getHook(entry.apiKey()), event); ++ } ++ ++ public > RegistryEntryAddEventType getRegistryValueAddEventType(final RegistryEventProvider type) { ++ if (!RegistryEntry.Modifiable.isModifiable(PaperRegistries.getEntry(type.registryKey()))) { ++ throw new IllegalArgumentException(type.registryKey() + " does not support RegistryEntryAddEvent"); ++ } ++ return this.valueAddHooks.getOrCreate(type, RegistryEntryAddEventTypeImpl::new); ++ } ++ ++ public > LifecycleEventType.Prioritizable> getRegistryFreezeEventType(final RegistryEventProvider type) { ++ if (!RegistryEntry.Addable.isAddable(PaperRegistries.getEntry(type.registryKey()))) { ++ throw new IllegalArgumentException(type.registryKey() + " does not support RegistryFreezeEvent"); ++ } ++ return this.freezeHooks.getOrCreate(type, RegistryLifecycleEventType::new); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/registry/WritableCraftRegistry.java b/src/main/java/io/papermc/paper/registry/WritableCraftRegistry.java +new file mode 100644 +index 0000000000000000000000000000000000000000..78317c7ab42a666f19634593a8f3b696700764c8 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/registry/WritableCraftRegistry.java +@@ -0,0 +1,92 @@ ++package io.papermc.paper.registry; ++ ++import com.mojang.serialization.Lifecycle; ++import io.papermc.paper.adventure.PaperAdventure; ++import io.papermc.paper.registry.data.util.Conversions; ++import io.papermc.paper.registry.entry.RegistryEntry; ++import io.papermc.paper.registry.event.WritableRegistry; ++import java.util.Optional; ++import java.util.function.BiFunction; ++import java.util.function.Consumer; ++import net.minecraft.core.MappedRegistry; ++import net.minecraft.core.RegistrationInfo; ++import net.minecraft.resources.ResourceKey; ++import org.bukkit.Keyed; ++import org.bukkit.NamespacedKey; ++import org.bukkit.craftbukkit.CraftRegistry; ++import org.bukkit.craftbukkit.util.ApiVersion; ++import org.checkerframework.checker.nullness.qual.Nullable; ++ ++public class WritableCraftRegistry> extends CraftRegistry { ++ ++ private static final RegistrationInfo FROM_PLUGIN = new RegistrationInfo(Optional.empty(), Lifecycle.experimental()); ++ ++ private final RegistryEntry.BuilderHolder entry; ++ private final MappedRegistry registry; ++ private final PaperRegistryBuilder.Factory builderFactory; ++ private final BiFunction minecraftToBukkit; ++ ++ public WritableCraftRegistry( ++ final RegistryEntry.BuilderHolder entry, ++ final Class classToPreload, ++ final MappedRegistry registry, ++ final BiFunction serializationUpdater, ++ final PaperRegistryBuilder.Factory builderFactory, ++ final BiFunction minecraftToBukkit ++ ) { ++ super(classToPreload, registry, null, serializationUpdater); ++ this.entry = entry; ++ this.registry = registry; ++ this.builderFactory = builderFactory; ++ this.minecraftToBukkit = minecraftToBukkit; ++ } ++ ++ public void register(final TypedKey key, final Consumer value, final Conversions conversions) { ++ final ResourceKey resourceKey = ResourceKey.create(this.registry.key(), PaperAdventure.asVanilla(key.key())); ++ this.registry.validateWrite(resourceKey); ++ final B builder = this.newBuilder(conversions, key); ++ value.accept(builder); ++ PaperRegistryListenerManager.INSTANCE.registerWithListeners( ++ this.registry, ++ RegistryEntry.Modifiable.asModifiable(this.entry), ++ resourceKey, ++ builder, ++ FROM_PLUGIN, ++ conversions ++ ); ++ } ++ ++ @Override ++ public final @Nullable T createBukkit(final NamespacedKey namespacedKey, final @Nullable M minecraft) { ++ if (minecraft == null) { ++ return null; ++ } ++ return this.minecraftToBukkit(namespacedKey, minecraft); ++ } ++ ++ public WritableRegistry createApiWritableRegistry(final Conversions conversions) { ++ return new ApiWritableRegistry(conversions); ++ } ++ ++ public T minecraftToBukkit(final NamespacedKey namespacedKey, final M minecraft) { ++ return this.minecraftToBukkit.apply(namespacedKey, minecraft); ++ } ++ ++ protected B newBuilder(final Conversions conversions, final TypedKey key) { ++ return this.builderFactory.create(conversions, key); ++ } ++ ++ public class ApiWritableRegistry implements WritableRegistry { ++ ++ private final Conversions conversions; ++ ++ public ApiWritableRegistry(final Conversions conversions) { ++ this.conversions = conversions; ++ } ++ ++ @Override ++ public void register(final TypedKey key, final Consumer value) { ++ WritableCraftRegistry.this.register(key, value, this.conversions); ++ } ++ } ++} +diff --git a/src/main/java/io/papermc/paper/registry/data/util/Conversions.java b/src/main/java/io/papermc/paper/registry/data/util/Conversions.java +new file mode 100644 +index 0000000000000000000000000000000000000000..eda5cc7d45ef59ccc1c9c7e027c1f044f1dcc86b +--- /dev/null ++++ b/src/main/java/io/papermc/paper/registry/data/util/Conversions.java +@@ -0,0 +1,36 @@ ++package io.papermc.paper.registry.data.util; ++ ++import com.mojang.serialization.JavaOps; ++import io.papermc.paper.adventure.WrapperAwareSerializer; ++import net.kyori.adventure.text.Component; ++import net.minecraft.resources.RegistryOps; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.checkerframework.framework.qual.DefaultQualifier; ++import org.jetbrains.annotations.Contract; ++ ++@DefaultQualifier(NonNull.class) ++public class Conversions { ++ ++ private final RegistryOps.RegistryInfoLookup lookup; ++ private final WrapperAwareSerializer serializer; ++ ++ public Conversions(final RegistryOps.RegistryInfoLookup lookup) { ++ this.lookup = lookup; ++ this.serializer = new WrapperAwareSerializer(() -> RegistryOps.create(JavaOps.INSTANCE, lookup)); ++ } ++ ++ public RegistryOps.RegistryInfoLookup lookup() { ++ return this.lookup; ++ } ++ ++ @Contract("null -> null; !null -> !null") ++ public net.minecraft.network.chat.@Nullable Component asVanilla(final @Nullable Component adventure) { ++ if (adventure == null) return null; ++ return this.serializer.serialize(adventure); ++ } ++ ++ public Component asAdventure(final net.minecraft.network.chat.@Nullable Component vanilla) { ++ return vanilla == null ? Component.empty() : this.serializer.deserialize(vanilla); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/registry/entry/AddableRegistryEntry.java b/src/main/java/io/papermc/paper/registry/entry/AddableRegistryEntry.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a93ee8a52c6ec8b8e9712e8449a9c0e6c3fd4046 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/registry/entry/AddableRegistryEntry.java +@@ -0,0 +1,46 @@ ++package io.papermc.paper.registry.entry; ++ ++import io.papermc.paper.registry.PaperRegistryBuilder; ++import io.papermc.paper.registry.RegistryHolder; ++import io.papermc.paper.registry.RegistryKey; ++import io.papermc.paper.registry.TypedKey; ++import io.papermc.paper.registry.WritableCraftRegistry; ++import io.papermc.paper.registry.data.util.Conversions; ++import java.util.function.BiFunction; ++import net.minecraft.core.MappedRegistry; ++import net.minecraft.core.Registry; ++import net.minecraft.resources.RegistryOps; ++import net.minecraft.resources.ResourceKey; ++import org.bukkit.Keyed; ++import org.bukkit.NamespacedKey; ++import org.checkerframework.checker.nullness.qual.Nullable; ++ ++public class AddableRegistryEntry> extends CraftRegistryEntry implements RegistryEntry.Addable { ++ ++ private final PaperRegistryBuilder.Filler builderFiller; ++ ++ protected AddableRegistryEntry( ++ final ResourceKey> mcKey, ++ final RegistryKey apiKey, ++ final Class classToPreload, ++ final BiFunction minecraftToBukkit, ++ final PaperRegistryBuilder.Filler builderFiller ++ ) { ++ super(mcKey, apiKey, classToPreload, minecraftToBukkit); ++ this.builderFiller = builderFiller; ++ } ++ ++ private WritableCraftRegistry createRegistry(final Registry registry) { ++ return new WritableCraftRegistry<>(this, this.classToPreload, (MappedRegistry) registry, this.updater, this.builderFiller.asFactory(), this.minecraftToBukkit); ++ } ++ ++ @Override ++ public RegistryHolder createRegistryHolder(final Registry nmsRegistry) { ++ return new RegistryHolder.Memoized<>(() -> this.createRegistry(nmsRegistry)); ++ } ++ ++ @Override ++ public B fillBuilder(final @Nullable Conversions conversions, final TypedKey key, final M nms) { ++ return this.builderFiller.fill(conversions, key, nms); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/registry/entry/ModifiableRegistryEntry.java b/src/main/java/io/papermc/paper/registry/entry/ModifiableRegistryEntry.java +new file mode 100644 +index 0000000000000000000000000000000000000000..cdc490c5254a80f8c38ff0b56c0fcd973df87f59 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/registry/entry/ModifiableRegistryEntry.java +@@ -0,0 +1,33 @@ ++package io.papermc.paper.registry.entry; ++ ++import io.papermc.paper.registry.PaperRegistryBuilder; ++import io.papermc.paper.registry.RegistryKey; ++import io.papermc.paper.registry.TypedKey; ++import io.papermc.paper.registry.data.util.Conversions; ++import java.util.function.BiFunction; ++import net.minecraft.core.Registry; ++import net.minecraft.resources.ResourceKey; ++import org.bukkit.Keyed; ++import org.bukkit.NamespacedKey; ++import org.checkerframework.checker.nullness.qual.Nullable; ++ ++public class ModifiableRegistryEntry> extends CraftRegistryEntry implements RegistryEntry.Modifiable { ++ ++ protected final PaperRegistryBuilder.Filler builderFiller; ++ ++ protected ModifiableRegistryEntry( ++ final ResourceKey> mcKey, ++ final RegistryKey apiKey, ++ final Class toPreload, ++ final BiFunction minecraftToBukkit, ++ final PaperRegistryBuilder.Filler builderFiller ++ ) { ++ super(mcKey, apiKey, toPreload, minecraftToBukkit); ++ this.builderFiller = builderFiller; ++ } ++ ++ @Override ++ public B fillBuilder(final @Nullable Conversions conversions, final TypedKey key, final M nms) { ++ return this.builderFiller.fill(conversions, key, nms); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/registry/entry/RegistryEntry.java b/src/main/java/io/papermc/paper/registry/entry/RegistryEntry.java +index 15991bf13894d850f360a520d1815711d25973ec..4712ff8be61994f4b0fa7c14a688c828ef13a175 100644 +--- a/src/main/java/io/papermc/paper/registry/entry/RegistryEntry.java ++++ b/src/main/java/io/papermc/paper/registry/entry/RegistryEntry.java +@@ -1,16 +1,24 @@ + package io.papermc.paper.registry.entry; + ++import io.papermc.paper.registry.PaperRegistryBuilder; + import io.papermc.paper.registry.RegistryHolder; + import io.papermc.paper.registry.RegistryKey; ++import io.papermc.paper.registry.TypedKey; ++import io.papermc.paper.registry.WritableCraftRegistry; ++import io.papermc.paper.registry.data.util.Conversions; ++import io.papermc.paper.registry.event.RegistryEntryAddEventImpl; ++import io.papermc.paper.registry.event.RegistryFreezeEventImpl; + import io.papermc.paper.registry.legacy.DelayedRegistryEntry; + import java.util.function.BiFunction; + import java.util.function.Supplier; + import net.minecraft.core.Registry; ++import net.minecraft.resources.RegistryOps; + import net.minecraft.resources.ResourceKey; + import org.bukkit.Keyed; + import org.bukkit.NamespacedKey; + import org.bukkit.craftbukkit.util.ApiVersion; + import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; + import org.checkerframework.framework.qual.DefaultQualifier; + + @DefaultQualifier(NonNull.class) +@@ -32,6 +40,65 @@ public interface RegistryEntry extends RegistryEntryInfo(this); + } + ++ interface BuilderHolder> extends RegistryEntryInfo { ++ ++ B fillBuilder(@Nullable Conversions conversions, TypedKey key, M nms); ++ } ++ ++ /** ++ * Can mutate values being added to the registry ++ */ ++ interface Modifiable> extends BuilderHolder { ++ ++ static boolean isModifiable(final @Nullable RegistryEntryInfo entry) { ++ return entry instanceof RegistryEntry.Modifiable || (entry instanceof final DelayedRegistryEntry delayed && delayed.delegate() instanceof RegistryEntry.Modifiable); ++ } ++ ++ static > Modifiable asModifiable(final RegistryEntryInfo entry) { // TODO remove Keyed ++ return (Modifiable) possiblyUnwrap(entry); ++ } ++ ++ default RegistryEntryAddEventImpl createEntryAddEvent(final TypedKey key, final B initialBuilder, final Conversions conversions) { ++ return new RegistryEntryAddEventImpl<>(key, initialBuilder, this.apiKey(), conversions); ++ } ++ } ++ ++ /** ++ * Can only add new values to the registry, not modify any values. ++ */ ++ interface Addable> extends BuilderHolder { // TODO remove Keyed ++ ++ default RegistryFreezeEventImpl createFreezeEvent(final WritableCraftRegistry writableRegistry, final Conversions conversions) { ++ return new RegistryFreezeEventImpl<>(this.apiKey(), writableRegistry.createApiWritableRegistry(conversions), conversions); ++ } ++ ++ static boolean isAddable(final @Nullable RegistryEntryInfo entry) { ++ return entry instanceof RegistryEntry.Addable || (entry instanceof final DelayedRegistryEntry delayed && delayed.delegate() instanceof RegistryEntry.Addable); ++ } ++ ++ static > Addable asAddable(final RegistryEntryInfo entry) { ++ return (Addable) possiblyUnwrap(entry); ++ } ++ } ++ ++ /** ++ * Can mutate values and add new values. ++ */ ++ interface Writable> extends Modifiable, Addable { // TODO remove Keyed ++ ++ static boolean isWritable(final @Nullable RegistryEntryInfo entry) { ++ return entry instanceof RegistryEntry.Writable || (entry instanceof final DelayedRegistryEntry delayed && delayed.delegate() instanceof RegistryEntry.Writable); ++ } ++ ++ static > Writable asWritable(final RegistryEntryInfo entry) { // TODO remove Keyed ++ return (Writable) possiblyUnwrap(entry); ++ } ++ } ++ ++ private static RegistryEntryInfo possiblyUnwrap(final RegistryEntryInfo entry) { ++ return entry instanceof final DelayedRegistryEntry delayed ? delayed.delegate() : entry; ++ } ++ + static RegistryEntry entry( + final ResourceKey> mcKey, + final RegistryKey apiKey, +@@ -48,4 +115,24 @@ public interface RegistryEntry extends RegistryEntryInfo(mcKey, apiKey, apiRegistrySupplier); + } ++ ++ static > RegistryEntry modifiable( ++ final ResourceKey> mcKey, ++ final RegistryKey apiKey, ++ final Class toPreload, ++ final BiFunction minecraftToBukkit, ++ final PaperRegistryBuilder.Filler filler ++ ) { ++ return new ModifiableRegistryEntry<>(mcKey, apiKey, toPreload, minecraftToBukkit, filler); ++ } ++ ++ static > RegistryEntry writable( ++ final ResourceKey> mcKey, ++ final RegistryKey apiKey, ++ final Class toPreload, ++ final BiFunction minecraftToBukkit, ++ final PaperRegistryBuilder.Filler filler ++ ) { ++ return new WritableRegistryEntry<>(mcKey, apiKey, toPreload, minecraftToBukkit, filler); ++ } + } +diff --git a/src/main/java/io/papermc/paper/registry/entry/WritableRegistryEntry.java b/src/main/java/io/papermc/paper/registry/entry/WritableRegistryEntry.java +new file mode 100644 +index 0000000000000000000000000000000000000000..562accce731630327d116afd1c9d559df7e386bd +--- /dev/null ++++ b/src/main/java/io/papermc/paper/registry/entry/WritableRegistryEntry.java +@@ -0,0 +1,22 @@ ++package io.papermc.paper.registry.entry; ++ ++import io.papermc.paper.registry.PaperRegistryBuilder; ++import io.papermc.paper.registry.RegistryKey; ++import java.util.function.BiFunction; ++import net.minecraft.core.Registry; ++import net.minecraft.resources.ResourceKey; ++import org.bukkit.Keyed; ++import org.bukkit.NamespacedKey; ++ ++public class WritableRegistryEntry> extends AddableRegistryEntry implements RegistryEntry.Writable { // TODO remove Keyed ++ ++ protected WritableRegistryEntry( ++ final ResourceKey> mcKey, ++ final RegistryKey apiKey, ++ final Class classToPreload, ++ final BiFunction minecraftToBukkit, ++ final PaperRegistryBuilder.Filler builderFiller ++ ) { ++ super(mcKey, apiKey, classToPreload, minecraftToBukkit, builderFiller); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/registry/event/RegistryEntryAddEventImpl.java b/src/main/java/io/papermc/paper/registry/event/RegistryEntryAddEventImpl.java +new file mode 100644 +index 0000000000000000000000000000000000000000..cc9c8fd313f530777af80ad79e03903f3f8f9829 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/registry/event/RegistryEntryAddEventImpl.java +@@ -0,0 +1,30 @@ ++package io.papermc.paper.registry.event; ++ ++import io.papermc.paper.plugin.lifecycle.event.PaperLifecycleEvent; ++import io.papermc.paper.registry.PaperRegistries; ++import io.papermc.paper.registry.RegistryBuilder; ++import io.papermc.paper.registry.RegistryKey; ++import io.papermc.paper.registry.TypedKey; ++import io.papermc.paper.registry.data.util.Conversions; ++import io.papermc.paper.registry.set.NamedRegistryKeySetImpl; ++import io.papermc.paper.registry.tag.Tag; ++import io.papermc.paper.registry.tag.TagKey; ++import net.minecraft.core.HolderSet; ++import net.minecraft.resources.RegistryOps; ++import org.bukkit.Keyed; ++import org.checkerframework.checker.nullness.qual.NonNull; ++ ++public record RegistryEntryAddEventImpl>( ++ TypedKey key, ++ B builder, ++ RegistryKey registryKey, ++ Conversions conversions ++) implements RegistryEntryAddEvent, PaperLifecycleEvent { ++ ++ @Override ++ public @NonNull Tag getOrCreateTag(final TagKey tagKey) { ++ final RegistryOps.RegistryInfo registryInfo = this.conversions.lookup().lookup(PaperRegistries.registryToNms(tagKey.registryKey())).orElseThrow(); ++ final HolderSet.Named tagSet = registryInfo.getter().getOrThrow(PaperRegistries.toNms(tagKey)); ++ return new NamedRegistryKeySetImpl<>(tagKey, tagSet); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/registry/event/RegistryEventMap.java b/src/main/java/io/papermc/paper/registry/event/RegistryEventMap.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f5ea23173dcbe491742c3dd051c147ef397307a0 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/registry/event/RegistryEventMap.java +@@ -0,0 +1,44 @@ ++package io.papermc.paper.registry.event; ++ ++import io.papermc.paper.plugin.bootstrap.BootstrapContext; ++import io.papermc.paper.plugin.lifecycle.event.LifecycleEventRunner; ++import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEventType; ++import io.papermc.paper.registry.RegistryBuilder; ++import io.papermc.paper.registry.RegistryKey; ++import java.util.HashMap; ++import java.util.Map; ++import java.util.Objects; ++import java.util.function.BiFunction; ++ ++public final class RegistryEventMap { ++ ++ private final Map, LifecycleEventType, ?>> hooks = new HashMap<>(); ++ private final String name; ++ ++ public RegistryEventMap(final String name) { ++ this.name = name; ++ } ++ ++ @SuppressWarnings("unchecked") ++ public , E extends RegistryEvent, ET extends LifecycleEventType> ET getOrCreate(final RegistryEventProvider type, final BiFunction, ? super String, ET> eventTypeCreator) { ++ final ET registerHook; ++ if (this.hooks.containsKey(type.registryKey())) { ++ registerHook = (ET) this.hooks.get(type.registryKey()); ++ } else { ++ registerHook = eventTypeCreator.apply(type, this.name); ++ LifecycleEventRunner.INSTANCE.addEventType(registerHook); ++ this.hooks.put(type.registryKey(), registerHook); ++ } ++ return registerHook; ++ } ++ ++ @SuppressWarnings("unchecked") ++ public > LifecycleEventType getHook(final RegistryKey registryKey) { ++ return (LifecycleEventType) Objects.requireNonNull(this.hooks.get(registryKey), "No hook for " + registryKey); ++ } ++ ++ public boolean hasHooks(final RegistryKey registryKey) { ++ return this.hooks.containsKey(registryKey); ++ } ++ ++} +diff --git a/src/main/java/io/papermc/paper/registry/event/RegistryEventTypeProviderImpl.java b/src/main/java/io/papermc/paper/registry/event/RegistryEventTypeProviderImpl.java +new file mode 100644 +index 0000000000000000000000000000000000000000..34c842ffa355e3c8001dd7b8551bcb49229a6391 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/registry/event/RegistryEventTypeProviderImpl.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.PaperRegistryListenerManager; ++import io.papermc.paper.registry.RegistryBuilder; ++import io.papermc.paper.registry.event.type.RegistryEntryAddEventType; ++ ++public class RegistryEventTypeProviderImpl implements RegistryEventTypeProvider { ++ ++ public static RegistryEventTypeProviderImpl instance() { ++ return (RegistryEventTypeProviderImpl) RegistryEventTypeProvider.provider(); ++ } ++ ++ @Override ++ public > RegistryEntryAddEventType registryEntryAdd(final RegistryEventProvider type) { ++ return PaperRegistryListenerManager.INSTANCE.getRegistryValueAddEventType(type); ++ } ++ ++ @Override ++ public > LifecycleEventType.Prioritizable> registryFreeze(final RegistryEventProvider type) { ++ return PaperRegistryListenerManager.INSTANCE.getRegistryFreezeEventType(type); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/registry/event/RegistryFreezeEventImpl.java b/src/main/java/io/papermc/paper/registry/event/RegistryFreezeEventImpl.java +new file mode 100644 +index 0000000000000000000000000000000000000000..63957d2509e68ccc6eb2fd9ecaa35bfad7b71b81 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/registry/event/RegistryFreezeEventImpl.java +@@ -0,0 +1,28 @@ ++package io.papermc.paper.registry.event; ++ ++import io.papermc.paper.plugin.lifecycle.event.PaperLifecycleEvent; ++import io.papermc.paper.registry.PaperRegistries; ++import io.papermc.paper.registry.RegistryBuilder; ++import io.papermc.paper.registry.RegistryKey; ++import io.papermc.paper.registry.data.util.Conversions; ++import io.papermc.paper.registry.set.NamedRegistryKeySetImpl; ++import io.papermc.paper.registry.tag.Tag; ++import io.papermc.paper.registry.tag.TagKey; ++import net.minecraft.core.HolderSet; ++import net.minecraft.resources.RegistryOps; ++import org.bukkit.Keyed; ++import org.checkerframework.checker.nullness.qual.NonNull; ++ ++public record RegistryFreezeEventImpl>( ++ RegistryKey registryKey, ++ WritableRegistry registry, ++ Conversions conversions ++) implements RegistryFreezeEvent, PaperLifecycleEvent { ++ ++ @Override ++ public @NonNull Tag getOrCreateTag(final TagKey tagKey) { ++ final RegistryOps.RegistryInfo registryInfo = this.conversions.lookup().lookup(PaperRegistries.registryToNms(tagKey.registryKey())).orElseThrow(); ++ final HolderSet.Named tagSet = registryInfo.getter().getOrThrow(PaperRegistries.toNms(tagKey)); ++ return new NamedRegistryKeySetImpl<>(tagKey, tagSet); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/registry/event/package-info.java b/src/main/java/io/papermc/paper/registry/event/package-info.java +new file mode 100644 +index 0000000000000000000000000000000000000000..14d2d9766b8dee763f220c397aba3ad432d02aaa +--- /dev/null ++++ b/src/main/java/io/papermc/paper/registry/event/package-info.java +@@ -0,0 +1,5 @@ ++@DefaultQualifier(NonNull.class) ++package io.papermc.paper.registry.event; ++ ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; +diff --git a/src/main/java/io/papermc/paper/registry/event/type/RegistryEntryAddEventTypeImpl.java b/src/main/java/io/papermc/paper/registry/event/type/RegistryEntryAddEventTypeImpl.java +new file mode 100644 +index 0000000000000000000000000000000000000000..5d709ed04e1078b631f5b9c74ca35f042251e14f +--- /dev/null ++++ b/src/main/java/io/papermc/paper/registry/event/type/RegistryEntryAddEventTypeImpl.java +@@ -0,0 +1,32 @@ ++package io.papermc.paper.registry.event.type; ++ ++import io.papermc.paper.plugin.bootstrap.BootstrapContext; ++import io.papermc.paper.plugin.lifecycle.event.handler.LifecycleEventHandler; ++import io.papermc.paper.plugin.lifecycle.event.types.PrioritizableLifecycleEventType; ++import io.papermc.paper.registry.RegistryBuilder; ++import io.papermc.paper.registry.event.RegistryEntryAddEvent; ++import io.papermc.paper.registry.event.RegistryEventProvider; ++import java.util.function.Consumer; ++import java.util.function.Predicate; ++ ++public class RegistryEntryAddEventTypeImpl> extends PrioritizableLifecycleEventType, RegistryEntryAddConfiguration> implements RegistryEntryAddEventType { ++ ++ public RegistryEntryAddEventTypeImpl(final RegistryEventProvider type, final String eventName) { ++ super(type.registryKey() + " / " + eventName, BootstrapContext.class); ++ } ++ ++ @Override ++ public RegistryEntryAddConfiguration newHandler(final LifecycleEventHandler> handler) { ++ return new RegistryEntryAddHandlerConfiguration<>(handler, this); ++ } ++ ++ @Override ++ public void forEachHandler(final RegistryEntryAddEvent event, final Consumer>> consumer, final Predicate>> predicate) { ++ super.forEachHandler(event, consumer, predicate.and(handler -> this.matchesTarget(event, handler))); ++ } ++ ++ private boolean matchesTarget(final RegistryEntryAddEvent event, final RegisteredHandler> handler) { ++ final RegistryEntryAddHandlerConfiguration config = (RegistryEntryAddHandlerConfiguration) handler.config(); ++ return config.filter() == null || config.filter().test(event.key()); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/registry/event/type/RegistryEntryAddHandlerConfiguration.java b/src/main/java/io/papermc/paper/registry/event/type/RegistryEntryAddHandlerConfiguration.java +new file mode 100644 +index 0000000000000000000000000000000000000000..548f5bf979e88708e98d04dfe22ccaa300c91ddd +--- /dev/null ++++ b/src/main/java/io/papermc/paper/registry/event/type/RegistryEntryAddHandlerConfiguration.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.LifecycleEventHandler; ++import io.papermc.paper.plugin.lifecycle.event.handler.configuration.PrioritizedLifecycleEventHandlerConfigurationImpl; ++import io.papermc.paper.plugin.lifecycle.event.types.AbstractLifecycleEventType; ++import io.papermc.paper.registry.RegistryBuilder; ++import io.papermc.paper.registry.TypedKey; ++import io.papermc.paper.registry.event.RegistryEntryAddEvent; ++import java.util.function.Predicate; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.jetbrains.annotations.Contract; ++ ++public class RegistryEntryAddHandlerConfiguration> extends PrioritizedLifecycleEventHandlerConfigurationImpl> implements RegistryEntryAddConfiguration { ++ ++ private @Nullable Predicate> filter; ++ ++ public RegistryEntryAddHandlerConfiguration(final LifecycleEventHandler> handler, final AbstractLifecycleEventType, ?> eventType) { ++ super(handler, eventType); ++ } ++ ++ @Contract(pure = true) ++ public @Nullable Predicate> filter() { ++ return this.filter; ++ } ++ ++ @Override ++ public RegistryEntryAddConfiguration filter(final Predicate> filter) { ++ this.filter = filter; ++ return this; ++ } ++ ++ @Override ++ public RegistryEntryAddConfiguration priority(final int priority) { ++ return (RegistryEntryAddConfiguration) super.priority(priority); ++ } ++ ++ @Override ++ public RegistryEntryAddConfiguration monitor() { ++ return (RegistryEntryAddConfiguration) super.monitor(); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/registry/event/type/RegistryLifecycleEventType.java b/src/main/java/io/papermc/paper/registry/event/type/RegistryLifecycleEventType.java +new file mode 100644 +index 0000000000000000000000000000000000000000..dcc0f6b337840a78d38abdf2eb3f4bbd1676f58f +--- /dev/null ++++ b/src/main/java/io/papermc/paper/registry/event/type/RegistryLifecycleEventType.java +@@ -0,0 +1,14 @@ ++package io.papermc.paper.registry.event.type; ++ ++import io.papermc.paper.plugin.bootstrap.BootstrapContext; ++import io.papermc.paper.plugin.lifecycle.event.types.PrioritizableLifecycleEventType; ++import io.papermc.paper.registry.RegistryBuilder; ++import io.papermc.paper.registry.event.RegistryEvent; ++import io.papermc.paper.registry.event.RegistryEventProvider; ++ ++public final class RegistryLifecycleEventType, E extends RegistryEvent> extends PrioritizableLifecycleEventType.Simple { ++ ++ public RegistryLifecycleEventType(final RegistryEventProvider type, final String eventName) { ++ super(type.registryKey() + " / " + eventName, BootstrapContext.class); ++ } ++} +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> imple + public NamespacedKey getKey(final T value) { + return this.delegate().getKey(value); + } ++ ++ @Override ++ public boolean hasTag(final TagKey key) { ++ return this.delegate().hasTag(key); ++ } ++ ++ @Override ++ public @NotNull Tag getTag(final TagKey key) { ++ return this.delegate().getTag(key); ++ } + } +diff --git a/src/main/java/io/papermc/paper/registry/package-info.java b/src/main/java/io/papermc/paper/registry/package-info.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0b80179ff90e085568d7ceafd9b17511789dc99b +--- /dev/null ++++ b/src/main/java/io/papermc/paper/registry/package-info.java +@@ -0,0 +1,5 @@ ++@DefaultQualifier(NonNull.class) ++package io.papermc.paper.registry; ++ ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; +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..e8c2c18a1ed5cd587266bd415170610781531a12 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/registry/set/NamedRegistryKeySetImpl.java +@@ -0,0 +1,76 @@ ++package io.papermc.paper.registry.set; ++ ++import com.google.common.collect.ImmutableList; ++import com.google.common.collect.Iterables; ++import io.papermc.paper.registry.PaperRegistries; ++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( // TODO remove Keyed ++ TagKey tagKey, ++ HolderSet.Named namedSet ++) implements Tag, org.bukkit.Tag { ++ ++ @Override ++ public @Unmodifiable Collection> values() { ++ final ImmutableList.Builder> builder = ImmutableList.builder(); ++ for (final Holder holder : this.namedSet) { ++ builder.add(TypedKey.create(this.tagKey.registryKey(), CraftNamespacedKey.fromMinecraft(((Holder.Reference) holder).key().location()))); ++ } ++ return builder.build(); ++ } ++ ++ @Override ++ public RegistryKey registryKey() { ++ return this.tagKey.registryKey(); ++ } ++ ++ @Override ++ public boolean contains(final TypedKey valueKey) { ++ return Iterables.any(this.namedSet, h -> { ++ return PaperRegistries.fromNms(((Holder.Reference) h).key()).equals(valueKey); ++ }); ++ } ++ ++ @Override ++ public @Unmodifiable Collection resolve(final Registry registry) { ++ final ImmutableList.Builder builder = ImmutableList.builder(); ++ for (final Holder 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 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..f09ce9c8547ef05153847245746473dd9a8acbe6 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/registry/set/PaperRegistrySets.java +@@ -0,0 +1,48 @@ ++package io.papermc.paper.registry.set; ++ ++import io.papermc.paper.registry.PaperRegistries; ++import io.papermc.paper.registry.RegistryKey; ++import io.papermc.paper.registry.TypedKey; ++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.RegistryOps; ++import net.minecraft.resources.ResourceKey; ++import org.bukkit.Keyed; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++@DefaultQualifier(NonNull.class) ++public final class PaperRegistrySets { ++ ++ public static HolderSet convertToNms(final ResourceKey> resourceKey, final RegistryOps.RegistryInfoLookup lookup, final RegistryKeySet registryKeySet) { // TODO remove Keyed ++ if (registryKeySet instanceof NamedRegistryKeySetImpl) { ++ return ((NamedRegistryKeySetImpl) registryKeySet).namedSet(); ++ } else { ++ final RegistryOps.RegistryInfo registryInfo = lookup.lookup(resourceKey).orElseThrow(); ++ return HolderSet.direct(key -> { ++ return registryInfo.getter().getOrThrow(PaperRegistries.toNms(key)); ++ }, registryKeySet.values()); ++ } ++ } ++ ++ public static RegistryKeySet convertToApi(final RegistryKey registryKey, final HolderSet holders) { // TODO remove Keyed ++ if (holders instanceof final HolderSet.Named named) { ++ return new NamedRegistryKeySetImpl<>(PaperRegistries.fromNms(named.key()), named); ++ } else { ++ final List> keys = new ArrayList<>(); ++ for (final Holder holder : holders) { ++ if (!(holder instanceof final Holder.Reference reference)) { ++ throw new UnsupportedOperationException("Cannot convert a holder set containing direct holders"); ++ } ++ keys.add(PaperRegistries.fromNms(reference.key())); ++ } ++ return RegistrySet.keySet(registryKey, keys); ++ } ++ } ++ ++ private PaperRegistrySets() { ++ } ++} +diff --git a/src/main/java/net/minecraft/core/MappedRegistry.java b/src/main/java/net/minecraft/core/MappedRegistry.java +index edbbafd1705345282e5e6251eb71bfde5793b7d4..f22d22ebcedcc9c20225677844c86a1ad27c4211 100644 +--- a/src/main/java/net/minecraft/core/MappedRegistry.java ++++ b/src/main/java/net/minecraft/core/MappedRegistry.java +@@ -441,4 +441,12 @@ public class MappedRegistry implements WritableRegistry { + public HolderLookup.RegistryLookup asLookup() { + return this.lookup; + } ++ // Paper start ++ // used to clear intrusive holders from GameEvent, Item, Block, EntityType, and Fluid from unused instances of those types ++ public void clearIntrusiveHolder(final T instance) { ++ if (this.unregisteredIntrusiveHolders != null) { ++ this.unregisteredIntrusiveHolders.remove(instance); ++ } ++ } ++ // Paper end + } +diff --git a/src/main/java/net/minecraft/core/registries/BuiltInRegistries.java b/src/main/java/net/minecraft/core/registries/BuiltInRegistries.java +index 44b7927081b476813505cab6b3a2da2ec2942c54..0497318e8f647453f38f3a16a8be6bd9aa19253f 100644 +--- a/src/main/java/net/minecraft/core/registries/BuiltInRegistries.java ++++ b/src/main/java/net/minecraft/core/registries/BuiltInRegistries.java +@@ -288,6 +288,17 @@ public class BuiltInRegistries { + Registries.ENCHANTMENT_PROVIDER_TYPE, EnchantmentProviderTypes::bootstrap + ); + public static final Registry> REGISTRY = WRITABLE_REGISTRY; ++ // Paper start - add built-in registry conversions ++ public static final io.papermc.paper.registry.data.util.Conversions BUILT_IN_CONVERSIONS = new io.papermc.paper.registry.data.util.Conversions(new net.minecraft.resources.RegistryOps.RegistryInfoLookup() { ++ @Override ++ public java.util.Optional> lookup(final ResourceKey> registryRef) { ++ final Registry registry = net.minecraft.server.RegistryLayer.STATIC_ACCESS.registryOrThrow(registryRef); ++ return java.util.Optional.of( ++ new net.minecraft.resources.RegistryOps.RegistryInfo<>(registry.asLookup(), registry.asTagAddingLookup(), Lifecycle.experimental()) ++ ); ++ } ++ }); ++ // Paper end - add built-in registry conversions + + private static Registry registerSimple(ResourceKey> key, BuiltInRegistries.RegistryBootstrap initializer) { + return internalRegister(key, new MappedRegistry<>(key, Lifecycle.stable(), false), initializer); +@@ -328,6 +339,7 @@ public class BuiltInRegistries { + } + public static void bootStrap(Runnable runnable) { + // Paper end ++ REGISTRY.freeze(); // Paper - freeze main registry early + createContents(); + runnable.run(); // Paper + freeze(); +@@ -346,6 +358,7 @@ public class BuiltInRegistries { + REGISTRY.freeze(); + + for (Registry registry : REGISTRY) { ++ io.papermc.paper.registry.PaperRegistryListenerManager.INSTANCE.runFreezeListeners(registry.key(), BUILT_IN_CONVERSIONS); // Paper + registry.freeze(); + } + } +diff --git a/src/main/java/net/minecraft/resources/RegistryDataLoader.java b/src/main/java/net/minecraft/resources/RegistryDataLoader.java +index abadf4abe08dc3bb6612b42cbb3f7df3ffa28ce9..3053243866c655829fe2e980446b4abf1da6d37c 100644 +--- a/src/main/java/net/minecraft/resources/RegistryDataLoader.java ++++ b/src/main/java/net/minecraft/resources/RegistryDataLoader.java +@@ -115,7 +115,7 @@ public class RegistryDataLoader { + ); + + public static RegistryAccess.Frozen load(ResourceManager resourceManager, RegistryAccess registryManager, List> entries) { +- return load((loader, infoGetter) -> loader.loadFromResources(resourceManager, infoGetter), registryManager, entries); ++ return load((loader, infoGetter, conversions) -> loader.loadFromResources(resourceManager, infoGetter, conversions), registryManager, entries); // Paper - pass conversions + } + + public static RegistryAccess.Frozen load( +@@ -124,7 +124,7 @@ public class RegistryDataLoader { + RegistryAccess registryManager, + List> entries + ) { +- return load((loader, infoGetter) -> loader.loadFromNetwork(data, factory, infoGetter), registryManager, entries); ++ return load((loader, infoGetter, conversions) -> loader.loadFromNetwork(data, factory, infoGetter, conversions), registryManager, entries); // Paper - pass conversions + } + + private static RegistryAccess.Frozen load( +@@ -133,9 +133,11 @@ public class RegistryDataLoader { + Map, Exception> map = new HashMap<>(); + List> list = entries.stream().map(entry -> entry.create(Lifecycle.stable(), map)).collect(Collectors.toUnmodifiableList()); + RegistryOps.RegistryInfoLookup registryInfoLookup = createContext(baseRegistryManager, list); +- list.forEach(loader -> loadable.apply((RegistryDataLoader.Loader)loader, registryInfoLookup)); ++ final io.papermc.paper.registry.data.util.Conversions conversions = new io.papermc.paper.registry.data.util.Conversions(registryInfoLookup); // Paper - create conversions ++ list.forEach(loader -> loadable.apply((RegistryDataLoader.Loader)loader, registryInfoLookup, conversions)); + list.forEach(loader -> { + Registry registry = loader.registry(); ++ io.papermc.paper.registry.PaperRegistryListenerManager.INSTANCE.runFreezeListeners(loader.registry.key(), conversions); // Paper - run pre-freeze listeners + + try { + registry.freeze(); +@@ -193,13 +195,13 @@ public class RegistryDataLoader { + } + + private static void loadElementFromResource( +- WritableRegistry registry, Decoder decoder, RegistryOps ops, ResourceKey key, Resource resource, RegistrationInfo entryInfo ++ WritableRegistry registry, Decoder decoder, RegistryOps ops, ResourceKey key, Resource resource, RegistrationInfo entryInfo, io.papermc.paper.registry.data.util.Conversions conversions + ) throws IOException { + try (Reader reader = resource.openAsReader()) { + JsonElement jsonElement = JsonParser.parseReader(reader); + DataResult 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, conversions); // Paper - register with listeners + } + } + +@@ -208,7 +210,8 @@ public class RegistryDataLoader { + RegistryOps.RegistryInfoLookup infoGetter, + WritableRegistry registry, + Decoder elementDecoder, +- Map, Exception> errors ++ Map, Exception> errors, ++ io.papermc.paper.registry.data.util.Conversions conversions // Paper - pass conversions + ) { + String string = Registries.elementsDirPath(registry.key()); + FileToIdConverter fileToIdConverter = FileToIdConverter.json(string); +@@ -221,7 +224,7 @@ public class RegistryDataLoader { + RegistrationInfo registrationInfo = REGISTRATION_INFO_CACHE.apply(resource.knownPackInfo()); + + try { +- loadElementFromResource(registry, elementDecoder, registryOps, resourceKey, resource, registrationInfo); ++ loadElementFromResource(registry, elementDecoder, registryOps, resourceKey, resource, registrationInfo, conversions); // Paper - pass conversions + } catch (Exception var15) { + errors.put( + resourceKey, +@@ -237,7 +240,8 @@ public class RegistryDataLoader { + RegistryOps.RegistryInfoLookup infoGetter, + WritableRegistry registry, + Decoder decoder, +- Map, Exception> loadingErrors ++ Map, Exception> loadingErrors, ++ io.papermc.paper.registry.data.util.Conversions conversions // Paper - pass conversions + ) { + List list = data.get(registry.key()); + if (list != null) { +@@ -264,7 +268,7 @@ public class RegistryDataLoader { + + try { + Resource resource = factory.getResourceOrThrow(resourceLocation); +- loadElementFromResource(registry, decoder, registryOps2, resourceKey, resource, NETWORK_REGISTRATION_INFO); ++ loadElementFromResource(registry, decoder, registryOps2, resourceKey, resource, NETWORK_REGISTRATION_INFO, conversions); // Paper - pass conversions + } catch (Exception var18) { + loadingErrors.put(resourceKey, new IllegalStateException("Failed to parse local data", var18)); + } +@@ -274,22 +278,23 @@ public class RegistryDataLoader { + } + + static record Loader(RegistryDataLoader.RegistryData data, WritableRegistry registry, Map, Exception> loadingErrors) { +- public void loadFromResources(ResourceManager resourceManager, RegistryOps.RegistryInfoLookup infoGetter) { +- RegistryDataLoader.loadContentsFromManager(resourceManager, infoGetter, this.registry, this.data.elementCodec, this.loadingErrors); ++ public void loadFromResources(ResourceManager resourceManager, RegistryOps.RegistryInfoLookup infoGetter, io.papermc.paper.registry.data.util.Conversions conversions) { // Paper - pass conversions ++ RegistryDataLoader.loadContentsFromManager(resourceManager, infoGetter, this.registry, this.data.elementCodec, this.loadingErrors, conversions); // Paper - pass conversions + } + + public void loadFromNetwork( + Map>, List> data, + ResourceProvider factory, +- RegistryOps.RegistryInfoLookup infoGetter ++ RegistryOps.RegistryInfoLookup infoGetter, ++ io.papermc.paper.registry.data.util.Conversions conversions // Paper + ) { +- RegistryDataLoader.loadContentsFromNetwork(data, factory, infoGetter, this.registry, this.data.elementCodec, this.loadingErrors); ++ RegistryDataLoader.loadContentsFromNetwork(data, factory, infoGetter, this.registry, this.data.elementCodec, this.loadingErrors, conversions); // Paper - pass conversions + } + } + + @FunctionalInterface + interface LoadingFunction { +- void apply(RegistryDataLoader.Loader loader, RegistryOps.RegistryInfoLookup infoGetter); ++ void apply(RegistryDataLoader.Loader loader, RegistryOps.RegistryInfoLookup infoGetter, io.papermc.paper.registry.data.util.Conversions conversions); // Paper - pass conversions + } + + public static record RegistryData(ResourceKey> key, Codec elementCodec, boolean requiredNonEmpty) { +diff --git a/src/main/java/net/minecraft/server/ReloadableServerRegistries.java b/src/main/java/net/minecraft/server/ReloadableServerRegistries.java +index 397bdacab9517354875ebc0bc68d35059b3c318b..908431652a0fea79b5a0cee1efd0c7a7d524b614 100644 +--- a/src/main/java/net/minecraft/server/ReloadableServerRegistries.java ++++ b/src/main/java/net/minecraft/server/ReloadableServerRegistries.java +@@ -47,15 +47,16 @@ public class ReloadableServerRegistries { + ) { + RegistryAccess.Frozen frozen = dynamicRegistries.getAccessForLoading(RegistryLayer.RELOADABLE); + RegistryOps registryOps = new ReloadableServerRegistries.EmptyTagLookupWrapper(frozen).createSerializationContext(JsonOps.INSTANCE); ++ final io.papermc.paper.registry.data.util.Conversions conversions = new io.papermc.paper.registry.data.util.Conversions(registryOps.lookupProvider); // Paper + List>> list = LootDataType.values() +- .map(type -> scheduleElementParse((LootDataType)type, registryOps, resourceManager, prepareExecutor)) ++ .map(type -> scheduleElementParse((LootDataType)type, registryOps, resourceManager, prepareExecutor, conversions)) // Paper + .toList(); + CompletableFuture>> completableFuture = Util.sequence(list); + return completableFuture.thenApplyAsync(registries -> apply(dynamicRegistries, (List>)registries), prepareExecutor); + } + + private static CompletableFuture> scheduleElementParse( +- LootDataType type, RegistryOps ops, ResourceManager resourceManager, Executor prepareExecutor ++ LootDataType type, RegistryOps ops, ResourceManager resourceManager, Executor prepareExecutor, io.papermc.paper.registry.data.util.Conversions conversions // Paper + ) { + return CompletableFuture.supplyAsync( + () -> { +@@ -66,7 +67,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, conversions)) // 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..a47421425a8d5d2f07e08890fded0f7bfec4efb7 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java +@@ -156,11 +156,11 @@ public class CraftRegistry implements Registry { + private final Map cache = new HashMap<>(); + private final Map byValue = new java.util.IdentityHashMap<>(); // Paper - improve Registry + private final net.minecraft.core.Registry minecraftRegistry; +- private final BiFunction minecraftToBukkit; ++ private final BiFunction minecraftToBukkit; // Paper + private final BiFunction serializationUpdater; // Paper - rename to make it *clear* what it is *only* for + private boolean init; + +- public CraftRegistry(Class bukkitClass, net.minecraft.core.Registry minecraftRegistry, BiFunction minecraftToBukkit, BiFunction serializationUpdater) { // Paper - relax preload class ++ public CraftRegistry(Class bukkitClass, net.minecraft.core.Registry minecraftRegistry, BiFunction minecraftToBukkit, BiFunction serializationUpdater) { // Paper - relax preload class + this.bukkitClass = bukkitClass; + this.minecraftRegistry = minecraftRegistry; + this.minecraftToBukkit = minecraftToBukkit; +@@ -233,4 +233,17 @@ public class CraftRegistry implements Registry { + return this.byValue.get(value); + } + // Paper end - improve Registry ++ ++ // Paper start - RegistrySet API ++ @Override ++ public boolean hasTag(final io.papermc.paper.registry.tag.TagKey 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 getTag(final io.papermc.paper.registry.tag.TagKey key) { ++ final net.minecraft.core.HolderSet.Named namedHolderSet = this.minecraftRegistry.getTag(io.papermc.paper.registry.PaperRegistries.toNms(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..8dba6c4a2e1f305cf576e8bfdca5d0c07ab871ae 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -685,6 +685,21 @@ public final class CraftMagicNumbers implements UnsafeValues { + } + // Paper end - lifecycle event API + ++ // Paper start - hack to get tags for non server-backed registries ++ @Override ++ public io.papermc.paper.registry.tag.Tag getTag(final io.papermc.paper.registry.tag.TagKey tagKey) { // TODO remove Keyed ++ if (tagKey.registryKey() != io.papermc.paper.registry.RegistryKey.ENTITY_TYPE || tagKey.registryKey() != io.papermc.paper.registry.RegistryKey.FLUID) { ++ throw new UnsupportedOperationException(tagKey.registryKey() + " doesn't have tags"); ++ } ++ final net.minecraft.resources.ResourceKey> nmsKey = io.papermc.paper.registry.PaperRegistries.registryToNms(tagKey.registryKey()); ++ final net.minecraft.core.Registry nmsRegistry = org.bukkit.craftbukkit.CraftRegistry.getMinecraftRegistry().registryOrThrow(nmsKey); ++ return nmsRegistry ++ .getTag(io.papermc.paper.registry.PaperRegistries.toNms(tagKey)) ++ .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. + *

+diff --git a/src/main/resources/META-INF/services/io.papermc.paper.registry.event.RegistryEventTypeProvider b/src/main/resources/META-INF/services/io.papermc.paper.registry.event.RegistryEventTypeProvider +new file mode 100644 +index 0000000000000000000000000000000000000000..8bee1a5ed877a04e4d027593df1f42cefdd824e7 +--- /dev/null ++++ b/src/main/resources/META-INF/services/io.papermc.paper.registry.event.RegistryEventTypeProvider +@@ -0,0 +1 @@ ++io.papermc.paper.registry.event.RegistryEventTypeProviderImpl +diff --git a/src/test/java/io/papermc/paper/registry/RegistryBuilderTest.java b/src/test/java/io/papermc/paper/registry/RegistryBuilderTest.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f27e5e0037b719b1fc10703f8d298d2326b00432 +--- /dev/null ++++ b/src/test/java/io/papermc/paper/registry/RegistryBuilderTest.java +@@ -0,0 +1,34 @@ ++package io.papermc.paper.registry; ++ ++import io.papermc.paper.registry.data.util.Conversions; ++import java.util.List; ++import java.util.Map; ++import net.minecraft.core.Registry; ++import net.minecraft.resources.RegistryOps; ++import net.minecraft.resources.ResourceKey; ++import org.bukkit.support.AbstractTestingBase; ++import org.junit.jupiter.api.Disabled; ++import org.junit.jupiter.params.ParameterizedTest; ++import org.junit.jupiter.params.provider.Arguments; ++import org.junit.jupiter.params.provider.MethodSource; ++ ++import static org.junit.jupiter.api.Assertions.assertEquals; ++ ++class RegistryBuilderTest extends AbstractTestingBase { ++ ++ static List registries() { ++ return List.of( ++ ); ++ } ++ ++ @Disabled ++ @ParameterizedTest ++ @MethodSource("registries") ++ void testEquality(final ResourceKey> resourceKey, final PaperRegistryBuilder.Filler filler) { ++ final Registry registry = AbstractTestingBase.REGISTRY_CUSTOM.registryOrThrow(resourceKey); ++ for (final Map.Entry, M> entry : registry.entrySet()) { ++ final M built = filler.fill(new Conversions(new RegistryOps.HolderLookupAdapter(AbstractTestingBase.REGISTRY_CUSTOM)), PaperRegistries.fromNms(entry.getKey()), entry.getValue()).build(); ++ assertEquals(entry.getValue(), built); ++ } ++ } ++}