diff options
Diffstat (limited to 'patches/server/0993-Registry-Modification-API.patch')
-rw-r--r-- | patches/server/0993-Registry-Modification-API.patch | 1499 |
1 files changed, 1499 insertions, 0 deletions
diff --git a/patches/server/0993-Registry-Modification-API.patch b/patches/server/0993-Registry-Modification-API.patch new file mode 100644 index 0000000000..ac28f625c7 --- /dev/null +++ b/patches/server/0993-Registry-Modification-API.patch @@ -0,0 +1,1499 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic <[email protected]> +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 633b01431750d4b40159a57bf25fb35c6670ff1b..5cf598905ed6a7ac2b0d9ced3420adaf20ceb6af 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; +@@ -58,6 +59,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 { +@@ -141,6 +143,15 @@ public final class PaperRegistries { + return ResourceKey.create((ResourceKey<? extends Registry<M>>) PaperRegistries.registryToNms(typedKey.registryKey()), PaperAdventure.asVanilla(typedKey.key())); + } + ++ public static <M, T> TagKey<T> fromNms(final net.minecraft.tags.TagKey<M> tagKey) { ++ return TagKey.create(registryFromNms(tagKey.registry()), CraftNamespacedKey.fromMinecraft(tagKey.location())); ++ } ++ ++ @SuppressWarnings({"unchecked", "RedundantCast"}) ++ public static <M, T> net.minecraft.tags.TagKey<M> toNms(final TagKey<T> tagKey) { ++ return net.minecraft.tags.TagKey.create((ResourceKey<? extends Registry<M>>) 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 35b6a7c5bac9640332a833bd3627f2bcb1bbf2f3..1026b9c04f94ed73049b980822a2eafdbacea7fd 100644 +--- a/src/main/java/io/papermc/paper/registry/PaperRegistryAccess.java ++++ b/src/main/java/io/papermc/paper/registry/PaperRegistryAccess.java +@@ -83,6 +83,14 @@ public class PaperRegistryAccess implements RegistryAccess { + return possiblyUnwrap(registryHolder.get()); + } + ++ public <M, T extends Keyed, B extends PaperRegistryBuilder<M, T>> WritableCraftRegistry<M, T, B> getWritableRegistry(final RegistryKey<T> key) { ++ final Registry<T> registry = this.getRegistry(key); ++ if (registry instanceof WritableCraftRegistry<?, T, ?>) { ++ return (WritableCraftRegistry<M, T, B>) registry; ++ } ++ throw new IllegalArgumentException(key + " does not point to a writable registry"); ++ } ++ + private static <T extends Keyed> Registry<T> possiblyUnwrap(final Registry<T> registry) { + if (registry instanceof final DelayedRegistry<T, ?> 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..528c6ee1739d92f766f3904acd7fc5734c93388a +--- /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<M, T> extends RegistryBuilder<T> { ++ ++ M build(); ++ ++ @FunctionalInterface ++ interface Filler<M, T, B extends PaperRegistryBuilder<M, T>> { ++ ++ B fill(Conversions conversions, TypedKey<T> key, @Nullable M nms); ++ ++ default Factory<M, T, B> asFactory() { ++ return (lookup, key) -> this.fill(lookup, key, null); ++ } ++ } ++ ++ @FunctionalInterface ++ interface Factory<M, T, B extends PaperRegistryBuilder<M, T>> { ++ ++ B create(Conversions conversions, TypedKey<T> 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..69e946173407eb05b18a2b19b0d45cbb3213570b +--- /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 class PaperRegistryListenerManager { ++ ++ public static final PaperRegistryListenerManager INSTANCE = new PaperRegistryListenerManager(); ++ ++ public final RegistryEventMap valueAddEventTypes = new RegistryEventMap("value add"); ++ public final RegistryEventMap freezeEventTypes = new RegistryEventMap("freeze"); ++ ++ private PaperRegistryListenerManager() { ++ } ++ ++ /** ++ * For {@link Registry#register(Registry, String, Object)} ++ */ ++ public <M> M registerWithListeners(final Registry<M> registry, final String id, final M nms) { ++ return this.registerWithListeners(registry, ResourceLocation.withDefaultNamespace(id), nms); ++ } ++ ++ /** ++ * For {@link Registry#register(Registry, ResourceLocation, Object)} ++ */ ++ public <M> M registerWithListeners(final Registry<M> 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> M registerWithListeners(final Registry<M> registry, final ResourceKey<M> 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 <M> Holder.Reference<M> registerForHolderWithListeners(final Registry<M> 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 <M> Holder.Reference<M> registerForHolderWithListeners(final Registry<M> registry, final ResourceKey<M> key, final M nms) { ++ return this.registerWithListeners(registry, key, nms, RegistrationInfo.BUILT_IN, WritableRegistry::register, BuiltInRegistries.BUILT_IN_CONVERSIONS); ++ } ++ ++ public <M> void registerWithListeners( ++ final Registry<M> registry, ++ final ResourceKey<M> key, ++ final M nms, ++ final RegistrationInfo registrationInfo, ++ final Conversions conversions ++ ) { ++ this.registerWithListeners(registry, key, nms, registrationInfo, WritableRegistry::register, conversions); ++ } ++ ++ // TODO remove Keyed ++ public <M, T extends org.bukkit.Keyed, B extends PaperRegistryBuilder<M, T>, R> R registerWithListeners( ++ final Registry<M> registry, ++ final ResourceKey<M> key, ++ final M nms, ++ final RegistrationInfo registrationInfo, ++ final RegisterMethod<M, R> 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<M, T> entry = PaperRegistries.getEntry(registry.key()); ++ if (!RegistryEntry.Modifiable.isModifiable(entry) || !this.valueAddEventTypes.hasHandlers(entry.apiKey())) { ++ return registerMethod.register((WritableRegistry<M>) registry, key, nms, registrationInfo); ++ } ++ final RegistryEntry.Modifiable<M, T, B> modifiableEntry = RegistryEntry.Modifiable.asModifiable(entry); ++ @SuppressWarnings("PatternValidation") final TypedKey<T> 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); ++ } ++ ++ <M, T extends org.bukkit.Keyed, B extends PaperRegistryBuilder<M, T>> void registerWithListeners( // TODO remove Keyed ++ final WritableRegistry<M> registry, ++ final RegistryEntryInfo<M, T> entry, ++ final ResourceKey<M> key, ++ final B builder, ++ final RegistrationInfo registrationInfo, ++ final Conversions conversions ++ ) { ++ if (!RegistryEntry.Modifiable.isModifiable(entry) || !this.valueAddEventTypes.hasHandlers(entry.apiKey())) { ++ registry.register(key, builder.build(), registrationInfo); ++ return; ++ } ++ this.registerWithListeners(registry, RegistryEntry.Modifiable.asModifiable(entry), key, null, builder, registrationInfo, WritableRegistry::register, conversions); ++ } ++ ++ public <M, T extends org.bukkit.Keyed, B extends PaperRegistryBuilder<M, T>, R> R registerWithListeners( // TODO remove Keyed ++ final Registry<M> registry, ++ final RegistryEntry.Modifiable<M, T, B> entry, ++ final ResourceKey<M> key, ++ final @Nullable M oldNms, ++ final B builder, ++ RegistrationInfo registrationInfo, ++ final RegisterMethod<M, R> registerMethod, ++ final Conversions conversions ++ ) { ++ @Subst("namespace:key") final ResourceLocation beingAdded = key.location(); ++ @SuppressWarnings("PatternValidation") final TypedKey<T> typedKey = TypedKey.create(entry.apiKey(), Key.key(beingAdded.getNamespace(), beingAdded.getPath())); ++ final RegistryEntryAddEventImpl<T, B> event = entry.createEntryAddEvent(typedKey, builder, conversions); ++ LifecycleEventRunner.INSTANCE.callEvent(this.valueAddEventTypes.getEventType(entry.apiKey()), event); ++ if (oldNms != null) { ++ ((MappedRegistry<M>) 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<M>) registry, key, newNms, registrationInfo); ++ } ++ ++ private static <M> M registerWithInstance(final WritableRegistry<M> writableRegistry, final ResourceKey<M> key, final M value, final RegistrationInfo registrationInfo) { ++ writableRegistry.register(key, value, registrationInfo); ++ return value; ++ } ++ ++ @FunctionalInterface ++ public interface RegisterMethod<M, R> { ++ ++ R register(WritableRegistry<M> writableRegistry, ResourceKey<M> key, M value, RegistrationInfo registrationInfo); ++ } ++ ++ public <M, T extends org.bukkit.Keyed, B extends PaperRegistryBuilder<M, T>> void runFreezeListeners(final ResourceKey<? extends Registry<M>> resourceKey, final Conversions conversions) { ++ final @Nullable RegistryEntryInfo<M, T> entry = PaperRegistries.getEntry(resourceKey); ++ if (!RegistryEntry.Addable.isAddable(entry) || !this.freezeEventTypes.hasHandlers(entry.apiKey())) { ++ return; ++ } ++ final RegistryEntry.Addable<M, T, B> writableEntry = RegistryEntry.Addable.asAddable(entry); ++ final WritableCraftRegistry<M, T, B> writableRegistry = PaperRegistryAccess.instance().getWritableRegistry(entry.apiKey()); ++ final RegistryFreezeEventImpl<T, B> event = writableEntry.createFreezeEvent(writableRegistry, conversions); ++ LifecycleEventRunner.INSTANCE.callEvent(this.freezeEventTypes.getEventType(entry.apiKey()), event); ++ } ++ ++ public <T, B extends RegistryBuilder<T>> RegistryEntryAddEventType<T, B> getRegistryValueAddEventType(final RegistryEventProvider<T, B> type) { ++ if (!RegistryEntry.Modifiable.isModifiable(PaperRegistries.getEntry(type.registryKey()))) { ++ throw new IllegalArgumentException(type.registryKey() + " does not support RegistryEntryAddEvent"); ++ } ++ return this.valueAddEventTypes.getOrCreate(type.registryKey(), RegistryEntryAddEventTypeImpl::new); ++ } ++ ++ public <T, B extends RegistryBuilder<T>> LifecycleEventType.Prioritizable<BootstrapContext, RegistryFreezeEvent<T, B>> getRegistryFreezeEventType(final RegistryEventProvider<T, B> type) { ++ if (!RegistryEntry.Addable.isAddable(PaperRegistries.getEntry(type.registryKey()))) { ++ throw new IllegalArgumentException(type.registryKey() + " does not support RegistryFreezeEvent"); ++ } ++ return this.freezeEventTypes.getOrCreate(type.registryKey(), 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<M, T extends Keyed, B extends PaperRegistryBuilder<M, T>> extends CraftRegistry<T, M> { ++ ++ private static final RegistrationInfo FROM_PLUGIN = new RegistrationInfo(Optional.empty(), Lifecycle.experimental()); ++ ++ private final RegistryEntry.BuilderHolder<M, T, B> entry; ++ private final MappedRegistry<M> registry; ++ private final PaperRegistryBuilder.Factory<M, T, ? extends B> builderFactory; ++ private final BiFunction<? super NamespacedKey, M, T> minecraftToBukkit; ++ ++ public WritableCraftRegistry( ++ final RegistryEntry.BuilderHolder<M, T, B> entry, ++ final Class<?> classToPreload, ++ final MappedRegistry<M> registry, ++ final BiFunction<NamespacedKey, ApiVersion, NamespacedKey> serializationUpdater, ++ final PaperRegistryBuilder.Factory<M, T, ? extends B> builderFactory, ++ final BiFunction<? super NamespacedKey, M, T> minecraftToBukkit ++ ) { ++ super(classToPreload, registry, null, serializationUpdater); ++ this.entry = entry; ++ this.registry = registry; ++ this.builderFactory = builderFactory; ++ this.minecraftToBukkit = minecraftToBukkit; ++ } ++ ++ public void register(final TypedKey<T> key, final Consumer<? super B> value, final Conversions conversions) { ++ final ResourceKey<M> 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<T, B> 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<T> key) { ++ return this.builderFactory.create(conversions, key); ++ } ++ ++ public class ApiWritableRegistry implements WritableRegistry<T, B> { ++ ++ private final Conversions conversions; ++ ++ public ApiWritableRegistry(final Conversions conversions) { ++ this.conversions = conversions; ++ } ++ ++ @Override ++ public void register(final TypedKey<T> key, final Consumer<? super B> 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..aeec9b3ae2911f041d000b3db72f37974020ba60 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/registry/entry/AddableRegistryEntry.java +@@ -0,0 +1,44 @@ ++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.ResourceKey; ++import org.bukkit.Keyed; ++import org.bukkit.NamespacedKey; ++ ++public class AddableRegistryEntry<M, T extends Keyed, B extends PaperRegistryBuilder<M, T>> extends CraftRegistryEntry<M, T> implements RegistryEntry.Addable<M, T, B> { ++ ++ private final PaperRegistryBuilder.Filler<M, T, B> builderFiller; ++ ++ protected AddableRegistryEntry( ++ final ResourceKey<? extends Registry<M>> mcKey, ++ final RegistryKey<T> apiKey, ++ final Class<?> classToPreload, ++ final BiFunction<NamespacedKey, M, T> minecraftToBukkit, ++ final PaperRegistryBuilder.Filler<M, T, B> builderFiller ++ ) { ++ super(mcKey, apiKey, classToPreload, minecraftToBukkit); ++ this.builderFiller = builderFiller; ++ } ++ ++ private WritableCraftRegistry<M, T, B> createRegistry(final Registry<M> registry) { ++ return new WritableCraftRegistry<>(this, this.classToPreload, (MappedRegistry<M>) registry, this.updater, this.builderFiller.asFactory(), this.minecraftToBukkit); ++ } ++ ++ @Override ++ public RegistryHolder<T> createRegistryHolder(final Registry<M> nmsRegistry) { ++ return new RegistryHolder.Memoized<>(() -> this.createRegistry(nmsRegistry)); ++ } ++ ++ @Override ++ public B fillBuilder(final Conversions conversions, final TypedKey<T> 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..515a995e3862f8e7cb93d149315ea32e04a08716 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/registry/entry/ModifiableRegistryEntry.java +@@ -0,0 +1,32 @@ ++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; ++ ++public class ModifiableRegistryEntry<M, T extends Keyed, B extends PaperRegistryBuilder<M, T>> extends CraftRegistryEntry<M, T> implements RegistryEntry.Modifiable<M, T, B> { ++ ++ protected final PaperRegistryBuilder.Filler<M, T, B> builderFiller; ++ ++ protected ModifiableRegistryEntry( ++ final ResourceKey<? extends Registry<M>> mcKey, ++ final RegistryKey<T> apiKey, ++ final Class<?> toPreload, ++ final BiFunction<NamespacedKey, M, T> minecraftToBukkit, ++ final PaperRegistryBuilder.Filler<M, T, B> builderFiller ++ ) { ++ super(mcKey, apiKey, toPreload, minecraftToBukkit); ++ this.builderFiller = builderFiller; ++ } ++ ++ @Override ++ public B fillBuilder(final Conversions conversions, final TypedKey<T> 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..f2e919705301cb23ed1938ca3c1976378249172c 100644 +--- a/src/main/java/io/papermc/paper/registry/entry/RegistryEntry.java ++++ b/src/main/java/io/papermc/paper/registry/entry/RegistryEntry.java +@@ -1,7 +1,13 @@ + 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; +@@ -11,6 +17,7 @@ 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 +39,65 @@ public interface RegistryEntry<M, B extends Keyed> extends RegistryEntryInfo<M, + return new DelayedRegistryEntry<>(this); + } + ++ interface BuilderHolder<M, T, B extends PaperRegistryBuilder<M, T>> extends RegistryEntryInfo<M, T> { ++ ++ B fillBuilder(Conversions conversions, TypedKey<T> key, M nms); ++ } ++ ++ /** ++ * Can mutate values being added to the registry ++ */ ++ interface Modifiable<M, T, B extends PaperRegistryBuilder<M, T>> extends BuilderHolder<M, T, B> { ++ ++ static boolean isModifiable(final @Nullable RegistryEntryInfo<?, ?> entry) { ++ return entry instanceof RegistryEntry.Modifiable<?, ?, ?> || (entry instanceof final DelayedRegistryEntry<?, ?> delayed && delayed.delegate() instanceof RegistryEntry.Modifiable<?, ?, ?>); ++ } ++ ++ static <M, T extends Keyed, B extends PaperRegistryBuilder<M, T>> Modifiable<M, T, B> asModifiable(final RegistryEntryInfo<M, T> entry) { // TODO remove Keyed ++ return (Modifiable<M, T, B>) possiblyUnwrap(entry); ++ } ++ ++ default RegistryEntryAddEventImpl<T, B> createEntryAddEvent(final TypedKey<T> 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<M, T extends Keyed, B extends PaperRegistryBuilder<M, T>> extends BuilderHolder<M, T, B> { // TODO remove Keyed ++ ++ default RegistryFreezeEventImpl<T, B> createFreezeEvent(final WritableCraftRegistry<M, T, B> 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 <M, T extends Keyed, B extends PaperRegistryBuilder<M, T>> Addable<M, T, B> asAddable(final RegistryEntryInfo<M, T> entry) { ++ return (Addable<M, T, B>) possiblyUnwrap(entry); ++ } ++ } ++ ++ /** ++ * Can mutate values and add new values. ++ */ ++ interface Writable<M, T extends Keyed, B extends PaperRegistryBuilder<M, T>> extends Modifiable<M, T, B>, Addable<M, T, B> { // 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 <M, T extends Keyed, B extends PaperRegistryBuilder<M, T>> Writable<M, T, B> asWritable(final RegistryEntryInfo<M, T> entry) { // TODO remove Keyed ++ return (Writable<M, T, B>) possiblyUnwrap(entry); ++ } ++ } ++ ++ private static <M, B extends Keyed> RegistryEntryInfo<M, B> possiblyUnwrap(final RegistryEntryInfo<M, B> entry) { ++ return entry instanceof final DelayedRegistryEntry<M, B> delayed ? delayed.delegate() : entry; ++ } ++ + static <M, B extends Keyed> RegistryEntry<M, B> entry( + final ResourceKey<? extends Registry<M>> mcKey, + final RegistryKey<B> apiKey, +@@ -48,4 +114,24 @@ public interface RegistryEntry<M, B extends Keyed> extends RegistryEntryInfo<M, + ) { + return new ApiRegistryEntry<>(mcKey, apiKey, apiRegistrySupplier); + } ++ ++ static <M, T extends Keyed, B extends PaperRegistryBuilder<M, T>> RegistryEntry<M, T> modifiable( ++ final ResourceKey<? extends Registry<M>> mcKey, ++ final RegistryKey<T> apiKey, ++ final Class<?> toPreload, ++ final BiFunction<NamespacedKey, M, T> minecraftToBukkit, ++ final PaperRegistryBuilder.Filler<M, T, B> filler ++ ) { ++ return new ModifiableRegistryEntry<>(mcKey, apiKey, toPreload, minecraftToBukkit, filler); ++ } ++ ++ static <M, T extends Keyed, B extends PaperRegistryBuilder<M, T>> RegistryEntry<M, T> writable( ++ final ResourceKey<? extends Registry<M>> mcKey, ++ final RegistryKey<T> apiKey, ++ final Class<?> toPreload, ++ final BiFunction<NamespacedKey, M, T> minecraftToBukkit, ++ final PaperRegistryBuilder.Filler<M, T, B> 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<M, T extends Keyed, B extends PaperRegistryBuilder<M, T>> extends AddableRegistryEntry<M, T, B> implements RegistryEntry.Writable<M, T, B> { // TODO remove Keyed ++ ++ protected WritableRegistryEntry( ++ final ResourceKey<? extends Registry<M>> mcKey, ++ final RegistryKey<T> apiKey, ++ final Class<?> classToPreload, ++ final BiFunction<NamespacedKey, M, T> minecraftToBukkit, ++ final PaperRegistryBuilder.Filler<M, T, B> 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<T, B extends RegistryBuilder<T>>( ++ TypedKey<T> key, ++ B builder, ++ RegistryKey<T> registryKey, ++ Conversions conversions ++) implements RegistryEntryAddEvent<T, B>, PaperLifecycleEvent { ++ ++ @Override ++ public @NonNull <V extends Keyed> Tag<V> getOrCreateTag(final TagKey<V> tagKey) { ++ final RegistryOps.RegistryInfo<Object> 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..bfcd0884d778ca62817fb1d22f0f2ed1f2abe855 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/registry/event/RegistryEventMap.java +@@ -0,0 +1,45 @@ ++package io.papermc.paper.registry.event; ++ ++import io.papermc.paper.plugin.bootstrap.BootstrapContext; ++import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent; ++import io.papermc.paper.plugin.lifecycle.event.LifecycleEventRunner; ++import io.papermc.paper.plugin.lifecycle.event.types.AbstractLifecycleEventType; ++import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEventType; ++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<RegistryKey<?>, LifecycleEventType<BootstrapContext, ? extends LifecycleEvent, ?>> eventTypes = new HashMap<>(); ++ private final String name; ++ ++ public RegistryEventMap(final String name) { ++ this.name = name; ++ } ++ ++ @SuppressWarnings("unchecked") ++ public <T, E extends LifecycleEvent, ET extends LifecycleEventType<BootstrapContext, E, ?>> ET getOrCreate(final RegistryKey<T> registryKey, final BiFunction<? super RegistryKey<T>, ? super String, ET> eventTypeCreator) { ++ final ET eventType; ++ if (this.eventTypes.containsKey(registryKey)) { ++ eventType = (ET) this.eventTypes.get(registryKey); ++ } else { ++ eventType = eventTypeCreator.apply(registryKey, this.name); ++ this.eventTypes.put(registryKey, eventType); ++ } ++ return eventType; ++ } ++ ++ @SuppressWarnings("unchecked") ++ public <T, E extends LifecycleEvent> LifecycleEventType<BootstrapContext, E, ?> getEventType(final RegistryKey<T> registryKey) { ++ return (LifecycleEventType<BootstrapContext, E, ?>) Objects.requireNonNull(this.eventTypes.get(registryKey), "No hook for " + registryKey); ++ } ++ ++ public boolean hasHandlers(final RegistryKey<?> registryKey) { ++ final AbstractLifecycleEventType<?, ?, ?> type = ((AbstractLifecycleEventType<?, ?, ?>) this.eventTypes.get(registryKey)); ++ return type != null && type.hasHandlers(); ++ } ++ ++} +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 <T, B extends RegistryBuilder<T>> RegistryEntryAddEventType<T, B> registryEntryAdd(final RegistryEventProvider<T, B> type) { ++ return PaperRegistryListenerManager.INSTANCE.getRegistryValueAddEventType(type); ++ } ++ ++ @Override ++ public <T, B extends RegistryBuilder<T>> LifecycleEventType.Prioritizable<BootstrapContext, RegistryFreezeEvent<T, B>> registryFreeze(final RegistryEventProvider<T, B> 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<T, B extends RegistryBuilder<T>>( ++ RegistryKey<T> registryKey, ++ WritableRegistry<T, B> registry, ++ Conversions conversions ++) implements RegistryFreezeEvent<T, B>, PaperLifecycleEvent { ++ ++ @Override ++ public @NonNull <V extends Keyed> Tag<V> getOrCreateTag(final TagKey<V> tagKey) { ++ final RegistryOps.RegistryInfo<Object> 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..fbf853bf1cbb3c7bbef531afe185818b9454299b +--- /dev/null ++++ b/src/main/java/io/papermc/paper/registry/event/type/RegistryEntryAddEventTypeImpl.java +@@ -0,0 +1,37 @@ ++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.RegistryKey; ++import io.papermc.paper.registry.event.RegistryEntryAddEvent; ++import java.util.function.Consumer; ++import java.util.function.Predicate; ++ ++public class RegistryEntryAddEventTypeImpl<T, B extends RegistryBuilder<T>> extends PrioritizableLifecycleEventType<BootstrapContext, RegistryEntryAddEvent<T, B>, RegistryEntryAddConfiguration<T>> implements RegistryEntryAddEventType<T, B> { ++ ++ public RegistryEntryAddEventTypeImpl(final RegistryKey<T> registryKey, final String eventName) { ++ super(registryKey + " / " + eventName, BootstrapContext.class); ++ } ++ ++ @Override ++ public boolean blocksReloading(final BootstrapContext eventOwner) { ++ return false; // only runs once ++ } ++ ++ @Override ++ public RegistryEntryAddConfiguration<T> newHandler(final LifecycleEventHandler<? super RegistryEntryAddEvent<T, B>> handler) { ++ return new RegistryEntryAddHandlerConfiguration<>(handler, this); ++ } ++ ++ @Override ++ public void forEachHandler(final RegistryEntryAddEvent<T, B> event, final Consumer<RegisteredHandler<BootstrapContext, RegistryEntryAddEvent<T, B>>> consumer, final Predicate<RegisteredHandler<BootstrapContext, RegistryEntryAddEvent<T, B>>> predicate) { ++ super.forEachHandler(event, consumer, predicate.and(handler -> this.matchesTarget(event, handler))); ++ } ++ ++ private boolean matchesTarget(final RegistryEntryAddEvent<T, B> event, final RegisteredHandler<BootstrapContext, RegistryEntryAddEvent<T, B>> handler) { ++ final RegistryEntryAddHandlerConfiguration<T, B> config = (RegistryEntryAddHandlerConfiguration<T, B>) 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<T, B extends RegistryBuilder<T>> extends PrioritizedLifecycleEventHandlerConfigurationImpl<BootstrapContext, RegistryEntryAddEvent<T, B>> implements RegistryEntryAddConfiguration<T> { ++ ++ private @Nullable Predicate<TypedKey<T>> filter; ++ ++ public RegistryEntryAddHandlerConfiguration(final LifecycleEventHandler<? super RegistryEntryAddEvent<T, B>> handler, final AbstractLifecycleEventType<BootstrapContext, RegistryEntryAddEvent<T, B>, ?> eventType) { ++ super(handler, eventType); ++ } ++ ++ @Contract(pure = true) ++ public @Nullable Predicate<TypedKey<T>> filter() { ++ return this.filter; ++ } ++ ++ @Override ++ public RegistryEntryAddConfiguration<T> filter(final Predicate<TypedKey<T>> filter) { ++ this.filter = filter; ++ return this; ++ } ++ ++ @Override ++ public RegistryEntryAddConfiguration<T> priority(final int priority) { ++ return (RegistryEntryAddConfiguration<T>) super.priority(priority); ++ } ++ ++ @Override ++ public RegistryEntryAddConfiguration<T> monitor() { ++ return (RegistryEntryAddConfiguration<T>) 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..7ee77022198bf5f9f88c6a1917a1da30b1863883 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/registry/event/type/RegistryLifecycleEventType.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.PrioritizableLifecycleEventType; ++import io.papermc.paper.registry.RegistryKey; ++import io.papermc.paper.registry.event.RegistryEvent; ++ ++public final class RegistryLifecycleEventType<T, E extends RegistryEvent<T>> extends PrioritizableLifecycleEventType.Simple<BootstrapContext, E> { ++ ++ public RegistryLifecycleEventType(final RegistryKey<T> registryKey, final String eventName) { ++ super(registryKey + " / " + eventName, BootstrapContext.class); ++ } ++ ++ @Override ++ public boolean blocksReloading(final BootstrapContext eventOwner) { ++ return false; // only runs once ++ } ++} +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 9400fed345344a0a8e4fb301cca6a1867adf625b..0cdc92acd3ebb6efb10e1b66419cc05618301581 100644 +--- a/src/main/java/io/papermc/paper/registry/legacy/DelayedRegistry.java ++++ b/src/main/java/io/papermc/paper/registry/legacy/DelayedRegistry.java +@@ -1,5 +1,7 @@ + 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; +@@ -7,7 +9,6 @@ import net.kyori.adventure.key.Key; + 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; +@@ -58,4 +59,14 @@ public final class DelayedRegistry<T extends Keyed, R extends Registry<T>> imple + public NamespacedKey getKey(final T value) { + return this.delegate().getKey(value); + } ++ ++ @Override ++ public boolean hasTag(final TagKey<T> key) { ++ return this.delegate().hasTag(key); ++ } ++ ++ @Override ++ public @NotNull Tag<T> getTag(final TagKey<T> key) { ++ return this.delegate().getTag(key); ++ } + } +diff --git a/src/main/java/io/papermc/paper/registry/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<T extends Keyed, M>( // TODO remove Keyed ++ TagKey<T> tagKey, ++ HolderSet.Named<M> namedSet ++) implements Tag<T>, org.bukkit.Tag<T> { ++ ++ @Override ++ public @Unmodifiable Collection<TypedKey<T>> values() { ++ final ImmutableList.Builder<TypedKey<T>> builder = ImmutableList.builder(); ++ for (final Holder<M> holder : this.namedSet) { ++ builder.add(TypedKey.create(this.tagKey.registryKey(), CraftNamespacedKey.fromMinecraft(((Holder.Reference<?>) holder).key().location()))); ++ } ++ return builder.build(); ++ } ++ ++ @Override ++ public RegistryKey<T> registryKey() { ++ return this.tagKey.registryKey(); ++ } ++ ++ @Override ++ public boolean contains(final TypedKey<T> valueKey) { ++ return Iterables.any(this.namedSet, h -> { ++ return PaperRegistries.fromNms(((Holder.Reference<?>) h).key()).equals(valueKey); ++ }); ++ } ++ ++ @Override ++ public @Unmodifiable Collection<T> resolve(final Registry<T> registry) { ++ final ImmutableList.Builder<T> builder = ImmutableList.builder(); ++ for (final Holder<M> holder : this.namedSet) { ++ builder.add(registry.getOrThrow(CraftNamespacedKey.fromMinecraft(((Holder.Reference<?>) holder).key().location()))); ++ } ++ return builder.build(); ++ } ++ ++ @Override ++ public boolean isTagged(final T item) { ++ return this.getValues().contains(item); ++ } ++ ++ @Override ++ public Set<T> getValues() { ++ return Set.copyOf(this.resolve(RegistryAccess.registryAccess().getRegistry(this.registryKey()))); ++ } ++ ++ @Override ++ public @NotNull NamespacedKey getKey() { ++ final Key key = this.tagKey().key(); ++ return new NamespacedKey(key.namespace(), key.value()); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/registry/set/PaperRegistrySets.java b/src/main/java/io/papermc/paper/registry/set/PaperRegistrySets.java +new file mode 100644 +index 0000000000000000000000000000000000000000..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 <A extends Keyed, M> HolderSet<M> convertToNms(final ResourceKey<? extends Registry<M>> resourceKey, final RegistryOps.RegistryInfoLookup lookup, final RegistryKeySet<A> registryKeySet) { // TODO remove Keyed ++ if (registryKeySet instanceof NamedRegistryKeySetImpl<A, ?>) { ++ return ((NamedRegistryKeySetImpl<A, M>) registryKeySet).namedSet(); ++ } else { ++ final RegistryOps.RegistryInfo<M> registryInfo = lookup.lookup(resourceKey).orElseThrow(); ++ return HolderSet.direct(key -> { ++ return registryInfo.getter().getOrThrow(PaperRegistries.toNms(key)); ++ }, registryKeySet.values()); ++ } ++ } ++ ++ public static <A extends Keyed, M> RegistryKeySet<A> convertToApi(final RegistryKey<A> registryKey, final HolderSet<M> holders) { // TODO remove Keyed ++ if (holders instanceof final HolderSet.Named<M> named) { ++ return new NamedRegistryKeySetImpl<>(PaperRegistries.fromNms(named.key()), named); ++ } else { ++ final List<TypedKey<A>> keys = new ArrayList<>(); ++ for (final Holder<M> holder : holders) { ++ if (!(holder instanceof final Holder.Reference<M> reference)) { ++ throw new UnsupportedOperationException("Cannot convert a holder set containing direct holders"); ++ } ++ keys.add(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 71e04e5c1bc0722abf8ca2e0738bd60b6d7ae21c..063630c1ffcce099139c59d598fc5a210e21f640 100644 +--- a/src/main/java/net/minecraft/core/MappedRegistry.java ++++ b/src/main/java/net/minecraft/core/MappedRegistry.java +@@ -509,4 +509,12 @@ public class MappedRegistry<T> implements WritableRegistry<T> { + + Stream<HolderSet.Named<T>> getTags(); + } ++ // 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 4638ba98dbbdb0f880337347be85a6e0fbed2191..12ba8bc0a946c107b076e2c995aca6a3aeb3811f 100644 +--- a/src/main/java/net/minecraft/core/registries/BuiltInRegistries.java ++++ b/src/main/java/net/minecraft/core/registries/BuiltInRegistries.java +@@ -296,6 +296,17 @@ public class BuiltInRegistries { + public static final Registry<SlotDisplay.Type<?>> SLOT_DISPLAY = registerSimple(Registries.SLOT_DISPLAY, SlotDisplays::bootstrap); + public static final Registry<RecipeBookCategory> RECIPE_BOOK_CATEGORY = registerSimple(Registries.RECIPE_BOOK_CATEGORY, RecipeBookCategories::bootstrap); + public static final Registry<? extends 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 <T> java.util.Optional<net.minecraft.resources.RegistryOps.RegistryInfo<T>> lookup(final ResourceKey<? extends Registry<? extends T>> registryRef) { ++ final Registry<T> 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 <T> Registry<T> registerSimple(ResourceKey<? extends Registry<T>> key, BuiltInRegistries.RegistryBootstrap<T> initializer) { + return internalRegister(key, new MappedRegistry<>(key, Lifecycle.stable(), false), initializer); +@@ -336,6 +347,7 @@ public class BuiltInRegistries { + } + public static void bootStrap(Runnable runnable) { + // Paper end ++ REGISTRY.freeze(); // Paper - freeze main registry early + createContents(); + runnable.run(); // Paper + freeze(); +@@ -355,6 +367,7 @@ public class BuiltInRegistries { + + for (Registry<?> registry : REGISTRY) { + bindBootstrappedTagsToEmpty(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 144a6f5b0c53110804d6d099fe857d25f107d938..f2236665eaf3e6d0f9d44605db3cd5afe0cced4e 100644 +--- a/src/main/java/net/minecraft/resources/RegistryDataLoader.java ++++ b/src/main/java/net/minecraft/resources/RegistryDataLoader.java +@@ -130,7 +130,7 @@ public class RegistryDataLoader { + public static RegistryAccess.Frozen load( + ResourceManager resourceManager, List<HolderLookup.RegistryLookup<?>> registries, List<RegistryDataLoader.RegistryData<?>> entries + ) { +- return load((loader, infoGetter) -> loader.loadFromResources(resourceManager, infoGetter), registries, entries); ++ return load((loader, infoGetter, conversions) -> loader.loadFromResources(resourceManager, infoGetter, conversions), registries, entries); // Paper - pass conversions + } + + public static RegistryAccess.Frozen load( +@@ -139,7 +139,7 @@ public class RegistryDataLoader { + List<HolderLookup.RegistryLookup<?>> registries, + List<RegistryDataLoader.RegistryData<?>> entries + ) { +- return load((loader, infoGetter) -> loader.loadFromNetwork(data, factory, infoGetter), registries, entries); ++ return load((loader, infoGetter, conversions) -> loader.loadFromNetwork(data, factory, infoGetter, conversions), registries, entries); // Paper - pass conversions + } + + private static RegistryAccess.Frozen load( +@@ -148,9 +148,11 @@ public class RegistryDataLoader { + Map<ResourceKey<?>, Exception> map = new HashMap<>(); + List<RegistryDataLoader.Loader<?>> list = entries.stream().map(entry -> entry.create(Lifecycle.stable(), map)).collect(Collectors.toUnmodifiableList()); + RegistryOps.RegistryInfoLookup registryInfoLookup = createContext(registries, 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(); +@@ -238,13 +240,13 @@ public class RegistryDataLoader { + } + + private static <E> void loadElementFromResource( +- WritableRegistry<E> registry, Decoder<E> decoder, RegistryOps<JsonElement> ops, ResourceKey<E> key, Resource resource, RegistrationInfo entryInfo ++ WritableRegistry<E> registry, Decoder<E> decoder, RegistryOps<JsonElement> ops, ResourceKey<E> 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<E> dataResult = decoder.parse(ops, jsonElement); + E object = dataResult.getOrThrow(); +- registry.register(key, object, entryInfo); ++ io.papermc.paper.registry.PaperRegistryListenerManager.INSTANCE.registerWithListeners(registry, key, object, entryInfo, conversions); // Paper - register with listeners + } + } + +@@ -253,7 +255,8 @@ public class RegistryDataLoader { + RegistryOps.RegistryInfoLookup infoGetter, + WritableRegistry<E> registry, + Decoder<E> elementDecoder, +- Map<ResourceKey<?>, Exception> errors ++ Map<ResourceKey<?>, Exception> errors, ++ io.papermc.paper.registry.data.util.Conversions conversions // Paper - pass conversions + ) { + String string = Registries.elementsDirPath(registry.key()); + FileToIdConverter fileToIdConverter = FileToIdConverter.json(string); +@@ -266,7 +269,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, +@@ -284,7 +287,8 @@ public class RegistryDataLoader { + RegistryOps.RegistryInfoLookup infoGetter, + WritableRegistry<E> registry, + Decoder<E> decoder, +- Map<ResourceKey<?>, Exception> loadingErrors ++ Map<ResourceKey<?>, Exception> loadingErrors, ++ io.papermc.paper.registry.data.util.Conversions conversions // Paper - pass conversions + ) { + RegistryDataLoader.NetworkedRegistryData networkedRegistryData = data.get(registry.key()); + if (networkedRegistryData != null) { +@@ -311,7 +315,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)); + } +@@ -323,22 +327,23 @@ public class RegistryDataLoader { + } + + static record Loader<T>(RegistryDataLoader.RegistryData<T> data, WritableRegistry<T> registry, Map<ResourceKey<?>, 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<ResourceKey<? extends Registry<?>>, RegistryDataLoader.NetworkedRegistryData> 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 NetworkedRegistryData(List<RegistrySynchronization.PackedRegistryEntry> elements, TagNetworkSerialization.NetworkPayload tags) { +diff --git a/src/main/java/net/minecraft/server/ReloadableServerRegistries.java b/src/main/java/net/minecraft/server/ReloadableServerRegistries.java +index 6fddef967b6314ca0158f5bd4b8898670ea5e9ec..b5ca1a0acb16d0cd8dccc854f309d425a48b070d 100644 +--- a/src/main/java/net/minecraft/server/ReloadableServerRegistries.java ++++ b/src/main/java/net/minecraft/server/ReloadableServerRegistries.java +@@ -50,8 +50,9 @@ public class ReloadableServerRegistries { + ); + HolderLookup.Provider provider = HolderLookup.Provider.create(list.stream()); + RegistryOps<JsonElement> registryOps = provider.createSerializationContext(JsonOps.INSTANCE); ++ final io.papermc.paper.registry.data.util.Conversions conversions = new io.papermc.paper.registry.data.util.Conversions(registryOps.lookupProvider); // Paper + List<CompletableFuture<WritableRegistry<?>>> list2 = LootDataType.values() +- .map(type -> scheduleRegistryLoad((LootDataType<?>)type, registryOps, resourceManager, prepareExecutor)) ++ .map(type -> scheduleRegistryLoad((LootDataType<?>)type, registryOps, resourceManager, prepareExecutor, conversions)) // Paper + .toList(); + CompletableFuture<List<WritableRegistry<?>>> completableFuture = Util.sequence(list2); + return completableFuture.thenApplyAsync( +@@ -60,7 +61,7 @@ public class ReloadableServerRegistries { + } + + private static <T> CompletableFuture<WritableRegistry<?>> scheduleRegistryLoad( +- LootDataType<T> type, RegistryOps<JsonElement> ops, ResourceManager resourceManager, Executor prepareExecutor ++ LootDataType<T> type, RegistryOps<JsonElement> ops, ResourceManager resourceManager, Executor prepareExecutor, io.papermc.paper.registry.data.util.Conversions conversions // Paper + ) { + return CompletableFuture.supplyAsync(() -> { + WritableRegistry<T> writableRegistry = new MappedRegistry<>(type.registryKey(), Lifecycle.experimental()); +@@ -68,7 +69,7 @@ public class ReloadableServerRegistries { + Map<ResourceLocation, T> map = new HashMap<>(); + String string = Registries.elementsDirPath(type.registryKey()); + SimpleJsonResourceReloadListener.scanDirectory(resourceManager, string, ops, type.codec(), map); +- map.forEach((id, value) -> writableRegistry.register(ResourceKey.create(type.registryKey(), id), (T)value, DEFAULT_REGISTRATION_INFO)); ++ map.forEach((id, value) -> io.papermc.paper.registry.PaperRegistryListenerManager.INSTANCE.registerWithListeners(writableRegistry, ResourceKey.create(type.registryKey(), id), value, DEFAULT_REGISTRATION_INFO, conversions)); // Paper - register with listeners + TagLoader.loadTagsForRegistry(resourceManager, writableRegistry); + return writableRegistry; + }, prepareExecutor); +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java b/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java +index f8450a2abd1e96fac7827d252cc00038b9dee839..a812a42ea81b1543287e78ea55da6cbf4e0d27f8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java +@@ -167,11 +167,11 @@ public class CraftRegistry<B extends Keyed, M> implements Registry<B> { + private final Map<NamespacedKey, B> cache = new HashMap<>(); + private final Map<B, NamespacedKey> byValue = new java.util.IdentityHashMap<>(); // Paper - improve Registry + private final net.minecraft.core.Registry<M> minecraftRegistry; +- private final BiFunction<NamespacedKey, M, B> minecraftToBukkit; ++ private final BiFunction<? super NamespacedKey, M, B> minecraftToBukkit; // Paper + private final BiFunction<NamespacedKey, ApiVersion, NamespacedKey> serializationUpdater; // Paper - rename to make it *clear* what it is *only* for + private boolean init; + +- public CraftRegistry(Class<?> bukkitClass, net.minecraft.core.Registry<M> minecraftRegistry, BiFunction<NamespacedKey, M, B> minecraftToBukkit, BiFunction<NamespacedKey, ApiVersion, NamespacedKey> serializationUpdater) { // Paper - relax preload class ++ public CraftRegistry(Class<?> bukkitClass, net.minecraft.core.Registry<M> minecraftRegistry, BiFunction<? super NamespacedKey, M, B> minecraftToBukkit, BiFunction<NamespacedKey, ApiVersion, NamespacedKey> serializationUpdater) { // Paper - relax preload class + this.bukkitClass = bukkitClass; + this.minecraftRegistry = minecraftRegistry; + this.minecraftToBukkit = minecraftToBukkit; +@@ -254,4 +254,17 @@ public class CraftRegistry<B extends Keyed, M> implements Registry<B> { + return this.byValue.get(value); + } + // Paper end - improve Registry ++ ++ // Paper start - RegistrySet API ++ @Override ++ public boolean hasTag(final io.papermc.paper.registry.tag.TagKey<B> key) { ++ return this.minecraftRegistry.getTag(net.minecraft.tags.TagKey.create(this.minecraftRegistry.key(), io.papermc.paper.adventure.PaperAdventure.asVanilla(key.key()))).isPresent(); ++ } ++ ++ @Override ++ public io.papermc.paper.registry.tag.Tag<B> getTag(final io.papermc.paper.registry.tag.TagKey<B> key) { ++ final net.minecraft.core.HolderSet.Named<M> namedHolderSet = this.minecraftRegistry.getTag(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 b24ccbff89db873f5bdf62cbebcca0049b94a8d5..49b898ed5e9de2507a6a6aac61dea4fe902649ca 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -674,6 +674,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 <A extends Keyed, M> io.papermc.paper.registry.tag.Tag<A> getTag(final io.papermc.paper.registry.tag.TagKey<A> 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<? extends net.minecraft.core.Registry<M>> nmsKey = io.papermc.paper.registry.PaperRegistries.registryToNms(tagKey.registryKey()); ++ final net.minecraft.core.Registry<M> 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. + * <p> +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..47b8ebac8496179008b8932c5ca2aadc274e24e0 +--- /dev/null ++++ b/src/test/java/io/papermc/paper/registry/RegistryBuilderTest.java +@@ -0,0 +1,36 @@ ++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.RegistryHelper; ++import org.bukkit.support.environment.AllFeatures; ++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; ++ ++@AllFeatures ++class RegistryBuilderTest { ++ ++ static List<Arguments> registries() { ++ return List.of( ++ ); ++ } ++ ++ @Disabled ++ @ParameterizedTest ++ @MethodSource("registries") ++ <M, T> void testEquality(final ResourceKey<? extends Registry<M>> resourceKey, final PaperRegistryBuilder.Filler<M, T, ?> filler) { ++ final Registry<M> registry = RegistryHelper.getRegistry().registryOrThrow(resourceKey); ++ for (final Map.Entry<ResourceKey<M>, M> entry : registry.entrySet()) { ++ final M built = filler.fill(new Conversions(new RegistryOps.HolderLookupAdapter(RegistryHelper.getRegistry())), PaperRegistries.fromNms(entry.getKey()), entry.getValue()).build(); ++ assertEquals(entry.getValue(), built); ++ } ++ } ++} +diff --git a/src/test/java/org/bukkit/registry/RegistryClassTest.java b/src/test/java/org/bukkit/registry/RegistryClassTest.java +index ea3d37f387bdb0dd5ae3fba9231ace31d0cebd64..c118c911972fe5a9f0c3e306009306f04ae2e821 100644 +--- a/src/test/java/org/bukkit/registry/RegistryClassTest.java ++++ b/src/test/java/org/bukkit/registry/RegistryClassTest.java +@@ -111,7 +111,7 @@ public class RegistryClassTest { + outsideRequest.clear(); + MockUtil.resetMock(spyRegistry); + doAnswer(invocation -> { +- Keyed item = realRegistry.get(invocation.getArgument(0)); ++ Keyed item = realRegistry.get((NamespacedKey) invocation.getArgument(0)); // Paper - registry modification api - specifically call namespaced key overload + + if (item == null) { + nullValue.add(invocation.getArgument(0)); +@@ -120,10 +120,10 @@ public class RegistryClassTest { + nullAble.add(invocation.getArgument(0)); + + return item; +- }).when(spyRegistry).get(any()); ++ }).when(spyRegistry).get((NamespacedKey) any()); // Paper - registry modification api - specifically call namespaced key overload + + doAnswer(invocation -> { +- Keyed item = realRegistry.get(invocation.getArgument(0)); ++ Keyed item = realRegistry.get((NamespacedKey) invocation.getArgument(0)); // Paper - registry modification api - specifically call namespaced key overload + + if (item == null) { + nullValue.add(invocation.getArgument(0)); +@@ -138,7 +138,7 @@ public class RegistryClassTest { + notNullAble.add(invocation.getArgument(0)); + + return item; +- }).when(spyRegistry).getOrThrow(any()); ++ }).when(spyRegistry).getOrThrow((NamespacedKey) any()); // Paper - registry modification api - specifically call namespaced key overload + + // Load class + try { +@@ -171,13 +171,13 @@ public class RegistryClassTest { + outsideRequest + .computeIfAbsent(type, ty -> new ArrayList<>()).add(invocation.getArgument(0)); + return mock(type); +- }).when(spyRegistry).get(any()); ++ }).when(spyRegistry).get((NamespacedKey) any()); // Paper - registry modification api - specifically call namespaced key overload + + doAnswer(invocation -> { + outsideRequest + .computeIfAbsent(type, ty -> new ArrayList<>()).add(invocation.getArgument(0)); + return mock(type); +- }).when(spyRegistry).getOrThrow(any()); ++ }).when(spyRegistry).getOrThrow((NamespacedKey) any()); // Paper - registry modification api - specifically call namespaced key overload + } + + private static void initFieldDataCache() { +diff --git a/src/test/java/org/bukkit/support/extension/NormalExtension.java b/src/test/java/org/bukkit/support/extension/NormalExtension.java +index 8b5dcc4d0ecf7f9c51f73080c123ca08e31b1898..a809ea2f0d2b477c61857aa02a7e55024b2a7e0d 100644 +--- a/src/test/java/org/bukkit/support/extension/NormalExtension.java ++++ b/src/test/java/org/bukkit/support/extension/NormalExtension.java +@@ -62,7 +62,7 @@ public class NormalExtension extends BaseExtension { + + doAnswer(invocation -> + mocks.computeIfAbsent(invocation.getArgument(0), k -> mock(RegistryHelper.updateClass(keyed, invocation.getArgument(0)), withSettings().stubOnly().defaultAnswer(DEFAULT_ANSWER))) +- ).when(registry).get(any()); // Allow static classes to fill there fields, so that it does not error out, just by loading them ++ ).when(registry).get((NamespacedKey) any()); // Allow static classes to fill there fields, so that it does not error out, just by loading them // Paper - registry modification api - specifically call namespaced key overload + + return registry; + } |