aboutsummaryrefslogtreecommitdiffhomepage
path: root/patches/server/1001-Registry-Modification-API.patch
diff options
context:
space:
mode:
Diffstat (limited to 'patches/server/1001-Registry-Modification-API.patch')
-rw-r--r--patches/server/1001-Registry-Modification-API.patch852
1 files changed, 852 insertions, 0 deletions
diff --git a/patches/server/1001-Registry-Modification-API.patch b/patches/server/1001-Registry-Modification-API.patch
new file mode 100644
index 0000000000..e139aba408
--- /dev/null
+++ b/patches/server/1001-Registry-Modification-API.patch
@@ -0,0 +1,852 @@
+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.server.RegistryLayer STATIC_ACCESS
+public net.minecraft.core.MappedRegistry validateWrite(Lnet/minecraft/resources/ResourceKey;)V
+
+diff --git a/src/main/java/io/papermc/paper/registry/PaperRegistries.java b/src/main/java/io/papermc/paper/registry/PaperRegistries.java
+index 40ac461d2f1906059377c77229612f540e827d75..7a2fb53ab280893b1c9c886ccb3480b695343cbf 100644
+--- a/src/main/java/io/papermc/paper/registry/PaperRegistries.java
++++ b/src/main/java/io/papermc/paper/registry/PaperRegistries.java
+@@ -44,6 +44,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 {
+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 <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..4cf32102a134ebef67d3893cfd24bf0add321eb9
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/PaperRegistryBuilder.java
+@@ -0,0 +1,24 @@
++package io.papermc.paper.registry;
++
++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(final TypedKey<T> key, final @Nullable M nms);
++
++ default Factory<M, T, B> asFactory() {
++ return key -> this.fill(key, null);
++ }
++ }
++
++ @FunctionalInterface
++ interface Factory<M, T, B extends PaperRegistryBuilder<M, T>> {
++
++ B create(final 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..6b3ba059b4f530b9e3d1c3d6bbc033e96c6a9bc2
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/PaperRegistryListenerManager.java
+@@ -0,0 +1,167 @@
++package io.papermc.paper.registry;
++
++import com.google.common.base.Preconditions;
++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.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 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.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> M registerWithListeners(final Registry<M> registry, final String id, final M nms) {
++ return this.registerWithListeners(registry, new ResourceLocation(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);
++ }
++
++ /**
++ * 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);
++ }
++
++ public <M, T extends org.bukkit.Keyed, B extends PaperRegistryBuilder<M, T>> void registerWithListeners(
++ final Registry<M> registry,
++ final ResourceKey<M> key,
++ final M nms,
++ final RegistrationInfo registrationInfo
++ ) {
++ this.registerWithListeners(registry, key, nms, registrationInfo, WritableRegistry::register);
++ }
++
++ 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
++ ) {
++ 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 (!(entry instanceof RegistryEntry.Modifiable<?, ?, ?>) || !this.valueAddHooks.hasHooks(entry.apiKey())) {
++ return registerMethod.register((WritableRegistry<M>) registry, key, nms, registrationInfo);
++ }
++ final RegistryEntry.Modifiable<M, T, B> modifiableEntry = (RegistryEntry.Modifiable<M, T, B>) entry;
++ @SuppressWarnings("PatternValidation") final TypedKey<T> typedKey = TypedKey.create(entry.apiKey(), Key.key(key.location().getNamespace(), key.location().getPath()));
++ final B builder = modifiableEntry.fillBuilder(typedKey, nms);
++ return this.registerWithListeners(registry, modifiableEntry, key, nms, builder, registrationInfo, registerMethod);
++ }
++
++ public <M, T extends org.bukkit.Keyed, B extends PaperRegistryBuilder<M, T>> void registerWithListeners(
++ final Registry<M> registry,
++ final RegistryEntry.Modifiable<M, T, B> entry,
++ final ResourceKey<M> key,
++ final @Nullable M oldNms,
++ final B builder,
++ final RegistrationInfo registrationInfo
++ ) {
++ this.registerWithListeners(registry, entry, key, oldNms, builder, registrationInfo, WritableRegistry::register);
++ }
++
++ public <M, T extends org.bukkit.Keyed, B extends PaperRegistryBuilder<M, T>, R> R registerWithListeners(
++ final Registry<M> registry,
++ final RegistryEntry.Modifiable<M, T, B> entry,
++ final ResourceKey<M> key,
++ final @Nullable M oldNms,
++ final B builder,
++ final RegistrationInfo registrationInfo,
++ final RegisterMethod<M, R> registerMethod
++ ) {
++ @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);
++ LifecycleEventRunner.INSTANCE.callEvent(this.valueAddHooks.getHook(entry.apiKey()), event);
++ if (oldNms != null) {
++ ((MappedRegistry<M>) registry).clearIntrusiveHolder(oldNms);
++ }
++ final M newNms = event.builder().build();
++ 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 @Nullable RegistryEntryInfo<M, T> entry = PaperRegistries.getEntry(resourceKey);
++ if (!(entry instanceof RegistryEntry.Writable<?, ?, ?>) || !this.freezeHooks.hasHooks(entry.apiKey())) {
++ return;
++ }
++ final RegistryEntry.Writable<M, T, B> writableEntry = (RegistryEntry.Writable<M, T, B>) entry;
++ final RegistryFreezeEventImpl<T, B> event = writableEntry.createFreezeEvent(PaperRegistryAccess.instance().getWritableRegistry(entry.apiKey()));
++ LifecycleEventRunner.INSTANCE.callEvent(this.freezeHooks.getHook(entry.apiKey()), event);
++ }
++
++ public <T, B extends RegistryBuilder<T>> RegistryEntryAddEventType<T, B> getRegistryValueAddEventType(final RegistryEventProvider<T, B> type) {
++ if (!(PaperRegistries.getEntry(type.registryKey()) instanceof RegistryEntry.Modifiable)) {
++ throw new IllegalArgumentException(type.registryKey() + " does not support RegistryAdditionEvent");
++ }
++ return this.valueAddHooks.getOrCreate(type, RegistryEntryAddEventTypeImpl::new);
++ }
++
++ public <T, B extends RegistryBuilder<T>> LifecycleEventType.Prioritizable<BootstrapContext, RegistryFreezeEvent<T, B>> getRegistryFreezeEventType(final RegistryEventProvider<T, B> type) {
++ if (!(PaperRegistries.getEntry(type.registryKey()) instanceof RegistryEntry.Writable)) {
++ throw new IllegalArgumentException(type.registryKey() + " does not support RegistryPreFreezeEvent");
++ }
++ 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..aff4895d5add72e22ae9f723b77843435ff044aa
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/WritableCraftRegistry.java
+@@ -0,0 +1,85 @@
++package io.papermc.paper.registry;
++
++import com.mojang.serialization.Lifecycle;
++import io.papermc.paper.adventure.PaperAdventure;
++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;
++ public final WritableRegistry<T, B> apiWritableRegistry = new ApiWritableRegistry();
++ 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 ResourceKey<M> resourceKey = ResourceKey.create(this.registry.key(), PaperAdventure.asVanilla(key.key()));
++ this.registry.validateWrite(resourceKey);
++ final B builder = this.newBuilder(key);
++ value.accept(builder);
++ if (this.entry instanceof final RegistryEntry.Modifiable<M, T, B> modifiable && PaperRegistryListenerManager.INSTANCE.valueAddHooks.hasHooks(this.entry.apiKey())) {
++ PaperRegistryListenerManager.INSTANCE.registerWithListeners(
++ this.registry,
++ modifiable,
++ resourceKey,
++ null,
++ builder,
++ FROM_PLUGIN
++ );
++ } else {
++ this.registry.register(resourceKey, builder.build(), FROM_PLUGIN);
++ }
++ }
++
++ @Override
++ public final @Nullable T createBukkit(final NamespacedKey namespacedKey, final @Nullable M minecraft) {
++ if (minecraft == null) {
++ return null;
++ }
++ return this.minecraftToBukkit(namespacedKey, minecraft);
++ }
++
++ public T minecraftToBukkit(final NamespacedKey namespacedKey, final M minecraft) {
++ return this.minecraftToBukkit.apply(namespacedKey, minecraft);
++ }
++
++ protected B newBuilder(final TypedKey<T> key) {
++ return this.builderFactory.create(key);
++ }
++
++ public class ApiWritableRegistry implements WritableRegistry<T, B> {
++ @Override
++ public void register(final TypedKey<T> key, final Consumer<? super B> value) {
++ WritableCraftRegistry.this.register(key, value);
++ }
++ }
++}
+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..a0f24d396e3a258be1978b2baa3f8e07ff54538f
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/entry/AddableRegistryEntry.java
+@@ -0,0 +1,43 @@
++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 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 TypedKey<T> key, final M nms) {
++ return this.builderFiller.fill(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..df10008f6bde1047c8a13523c0a8fc01222a4e62
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/entry/ModifiableRegistryEntry.java
+@@ -0,0 +1,31 @@
++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 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 TypedKey<T> key, final M nms) {
++ return this.builderFiller.fill(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..c49170d469dca809dc42421168d14e57e24d734b 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,12 @@
+ 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.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;
+@@ -32,6 +37,37 @@ 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(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> {
++
++ default RegistryEntryAddEventImpl<T, B> createEntryAddEvent(final TypedKey<T> key, final B initialBuilder) {
++ return new RegistryEntryAddEventImpl<>(key, initialBuilder, this.apiKey());
++ }
++ }
++
++ /**
++ * 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) {
++ return new RegistryFreezeEventImpl<>(this.apiKey(), writableRegistry.apiWritableRegistry);
++ }
++ }
++
++ /**
++ * 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 <M, B extends Keyed> RegistryEntry<M, B> entry(
+ final ResourceKey<? extends Registry<M>> mcKey,
+ final RegistryKey<B> apiKey,
+@@ -48,4 +84,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..a1d747c1a46d3aa8bcdef82bebb48f1fd760e61f
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/event/RegistryEntryAddEventImpl.java
+@@ -0,0 +1,13 @@
++package io.papermc.paper.registry.event;
++
++import io.papermc.paper.plugin.lifecycle.event.PaperLifecycleEvent;
++import io.papermc.paper.registry.RegistryBuilder;
++import io.papermc.paper.registry.RegistryKey;
++import io.papermc.paper.registry.TypedKey;
++
++public record RegistryEntryAddEventImpl<T, B extends RegistryBuilder<T>>(
++ TypedKey<T> key,
++ B builder,
++ RegistryKey<T> registryKey
++) implements RegistryEntryAddEvent<T, B>, PaperLifecycleEvent {
++}
+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<RegistryKey<?>, LifecycleEventType<BootstrapContext, ? extends RegistryEvent<?>, ?>> hooks = new HashMap<>();
++ private final String name;
++
++ public RegistryEventMap(final String name) {
++ this.name = name;
++ }
++
++ @SuppressWarnings("unchecked")
++ public <T, B extends RegistryBuilder<T>, E extends RegistryEvent<T>, ET extends LifecycleEventType<BootstrapContext, E, ?>> ET getOrCreate(final RegistryEventProvider<T, B> type, final BiFunction<? super RegistryEventProvider<T, B>, ? 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 <T, E extends RegistryEvent<T>> LifecycleEventType<BootstrapContext, E, ?> getHook(final RegistryKey<T> registryKey) {
++ return (LifecycleEventType<BootstrapContext, E, ?>) 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 <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..2db5d33d0b72ec3c9ff1c3042d9246dfa4344346
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/event/RegistryFreezeEventImpl.java
+@@ -0,0 +1,11 @@
++package io.papermc.paper.registry.event;
++
++import io.papermc.paper.plugin.lifecycle.event.PaperLifecycleEvent;
++import io.papermc.paper.registry.RegistryBuilder;
++import io.papermc.paper.registry.RegistryKey;
++
++public record RegistryFreezeEventImpl<T, B extends RegistryBuilder<T>>(
++ RegistryKey<T> registryKey,
++ WritableRegistry<T, B> registry
++) implements RegistryFreezeEvent<T, B>, PaperLifecycleEvent {
++}
+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..32303ea9b3da736cbe26d06e57f5dcc3aa32a99b
+--- /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<T, B extends RegistryBuilder<T>> extends PrioritizableLifecycleEventType<BootstrapContext, RegistryEntryAddEvent<T, B>, RegistryEntryAddConfiguration<T>> implements RegistryEntryAddEventType<T, B> {
++
++ public RegistryEntryAddEventTypeImpl(final RegistryEventProvider<T, B> type, final String eventName) {
++ super(type.registryKey() + " / " + eventName, BootstrapContext.class);
++ }
++
++ @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.target() == null || event.key().equals(config.target());
++ }
++}
+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..53df2dd1a9e1cef90bd8504c717b1cc6374b6f4e
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/event/type/RegistryEntryAddHandlerConfiguration.java
+@@ -0,0 +1,39 @@
++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 org.checkerframework.checker.nullness.qual.Nullable;
++
++public class RegistryEntryAddHandlerConfiguration<T, B extends RegistryBuilder<T>> extends PrioritizedLifecycleEventHandlerConfigurationImpl<BootstrapContext, RegistryEntryAddEvent<T, B>> implements RegistryEntryAddConfiguration<T> {
++
++ private @Nullable TypedKey<T> target;
++
++ public RegistryEntryAddHandlerConfiguration(final LifecycleEventHandler<? super RegistryEntryAddEvent<T, B>> handler, final AbstractLifecycleEventType<BootstrapContext, RegistryEntryAddEvent<T, B>, ?> eventType) {
++ super(handler, eventType);
++ }
++
++ public @Nullable TypedKey<T> target() {
++ return this.target;
++ }
++
++ @Override
++ public RegistryEntryAddConfiguration<T> onlyFor(final TypedKey<T> key) {
++ this.target = key;
++ 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..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<T, B extends RegistryBuilder<T>, E extends RegistryEvent<T>> extends PrioritizableLifecycleEventType.Simple<BootstrapContext, E> {
++
++ public RegistryLifecycleEventType(final RegistryEventProvider<T, B> type, final String eventName) {
++ super(type.registryKey() + " / " + eventName, BootstrapContext.class);
++ }
++}
+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/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<T> implements WritableRegistry<T> {
+ public HolderLookup.RegistryLookup<T> 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..c96bcd815ec9bf76625b5bedef461da49d7240ce 100644
+--- a/src/main/java/net/minecraft/core/registries/BuiltInRegistries.java
++++ b/src/main/java/net/minecraft/core/registries/BuiltInRegistries.java
+@@ -328,6 +328,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 +347,7 @@ public class BuiltInRegistries {
+ REGISTRY.freeze();
+
+ for (Registry<?> registry : REGISTRY) {
++ io.papermc.paper.registry.PaperRegistryListenerManager.INSTANCE.runFreezeListeners(registry.key()); // 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..ce65f2ea462b920e5e14b8cb687ef371c311d242 100644
+--- a/src/main/java/net/minecraft/resources/RegistryDataLoader.java
++++ b/src/main/java/net/minecraft/resources/RegistryDataLoader.java
+@@ -136,6 +136,7 @@ public class RegistryDataLoader {
+ list.forEach(loader -> loadable.apply((RegistryDataLoader.Loader<?>)loader, registryInfoLookup));
+ list.forEach(loader -> {
+ Registry<?> registry = loader.registry();
++ io.papermc.paper.registry.PaperRegistryListenerManager.INSTANCE.runFreezeListeners(loader.registry.key()); // Paper - run pre-freeze listeners
+
+ try {
+ registry.freeze();
+@@ -199,7 +200,7 @@ public class RegistryDataLoader {
+ 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); // Paper
+ }
+ }
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java b/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java
+index d21b7e39d71c785f47f790e1ad4be33a8e8e6e51..43d686a9958cff96f5b15d93e920c8f2313aa65b 100644
+--- a/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java
++++ b/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java
+@@ -156,11 +156,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;
+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