aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJake Potrebic <[email protected]>2023-02-25 13:47:13 -0800
committerBjarne Koll <[email protected]>2024-06-15 15:38:46 +0200
commite3c7b1949cfc95fc1097951d81ca04b12482eb97 (patch)
treec33ed437a352731db4a7326a8e8b4a60cd7f5011
parent5d834b1b7136de9525dfa405cf6c466c62c3abc1 (diff)
downloadPaper-e3c7b1949cfc95fc1097951d81ca04b12482eb97.tar.gz
Paper-e3c7b1949cfc95fc1097951d81ca04b12482eb97.zip
Registry Modification API
-rw-r--r--.editorconfig2
-rw-r--r--patches/api/0478-Registry-Modification-API.patch399
-rw-r--r--patches/server/0019-Paper-Plugins.patch13
-rw-r--r--patches/server/0237-Optimize-MappedRegistry.patch2
-rw-r--r--patches/server/0475-Add-RegistryAccess-for-managing-Registries.patch80
-rw-r--r--patches/server/0935-Add-Lifecycle-Event-system.patch154
-rw-r--r--patches/server/0975-Brigadier-based-command-API.patch2
-rw-r--r--patches/server/0991-Registry-Modification-API.patch864
8 files changed, 1401 insertions, 115 deletions
diff --git a/.editorconfig b/.editorconfig
index 2874476cf4..198db6e8a1 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -29,6 +29,8 @@ ij_java_names_count_to_use_import_on_demand = 999999
ij_java_imports_layout = *,|,$*
ij_java_generate_final_locals = true
ij_java_generate_final_parameters = true
+ij_java_method_parameters_new_line_after_left_paren = true
+ij_java_method_parameters_right_paren_on_new_line = true
[test-plugin/**/*.java]
ij_java_use_fq_class_names = false
diff --git a/patches/api/0478-Registry-Modification-API.patch b/patches/api/0478-Registry-Modification-API.patch
new file mode 100644
index 0000000000..70f095bc1f
--- /dev/null
+++ b/patches/api/0478-Registry-Modification-API.patch
@@ -0,0 +1,399 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Wed, 2 Mar 2022 13:36:21 -0800
+Subject: [PATCH] Registry Modification API
+
+
+diff --git a/src/main/java/io/papermc/paper/registry/RegistryBuilder.java b/src/main/java/io/papermc/paper/registry/RegistryBuilder.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..6edf300c1d81c1001756141c9efd022ba0e372cd
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/RegistryBuilder.java
+@@ -0,0 +1,13 @@
++package io.papermc.paper.registry;
++
++import org.jetbrains.annotations.ApiStatus;
++
++/**
++ * To be implemented by any type used for modifying registries.
++ *
++ * @param <T> registry value type
++ */
++public interface RegistryBuilder<T> {
++}
+diff --git a/src/main/java/io/papermc/paper/registry/event/RegistryEntryAddEvent.java b/src/main/java/io/papermc/paper/registry/event/RegistryEntryAddEvent.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..d6722f7b55e260bab4bab5c361f9c0e9888c6752
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/event/RegistryEntryAddEvent.java
+@@ -0,0 +1,33 @@
++package io.papermc.paper.registry.event;
++
++import io.papermc.paper.registry.RegistryBuilder;
++import io.papermc.paper.registry.TypedKey;
++import org.checkerframework.checker.nullness.qual.NonNull;
++import org.jetbrains.annotations.ApiStatus;
++
++/**
++ * Event object for {@link RegistryEventProvider#entryAdd()}. This
++ * event is fired right before a specific entry is registered in/added to registry.
++ * It provides a way for plugins to modify parts of this entry.
++ *
++ * @param <T> registry entry type
++ * @param <B> registry entry builder type
++ */
++public interface RegistryEntryAddEvent<T, B extends RegistryBuilder<T>> extends RegistryEvent<T> {
++
++ /**
++ * Gets the builder for the entry being added to the registry.
++ *
++ * @return the object builder
++ */
++ @NonNull B builder();
++
++ /**
++ * Gets the key for this entry in the registry.
++ *
++ * @return the key
++ */
++ @NonNull TypedKey<T> key();
++}
+diff --git a/src/main/java/io/papermc/paper/registry/event/RegistryEvent.java b/src/main/java/io/papermc/paper/registry/event/RegistryEvent.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..498c5920926158e86c2aec2bd2129d5e8b8613a3
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/event/RegistryEvent.java
+@@ -0,0 +1,23 @@
++package io.papermc.paper.registry.event;
++
++import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent;
++import io.papermc.paper.registry.RegistryKey;
++import org.checkerframework.checker.nullness.qual.NonNull;
++import org.jetbrains.annotations.ApiStatus;
++
++/**
++ * Base type for all registry events.
++ *
++ * @param <T> registry entry type
++ */
++public interface RegistryEvent<T> extends LifecycleEvent {
++
++ /**
++ * Get the key for the registry this event pertains to.
++ *
++ * @return the registry key
++ */
++ @NonNull RegistryKey<T> registryKey();
++}
+diff --git a/src/main/java/io/papermc/paper/registry/event/RegistryEventProvider.java b/src/main/java/io/papermc/paper/registry/event/RegistryEventProvider.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..477ed0fd5acc923d429980529876f0dd7dd3a52a
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/event/RegistryEventProvider.java
+@@ -0,0 +1,58 @@
++package io.papermc.paper.registry.event;
++
++import io.papermc.paper.plugin.bootstrap.BootstrapContext;
++import io.papermc.paper.plugin.lifecycle.event.handler.LifecycleEventHandler;
++import io.papermc.paper.plugin.lifecycle.event.handler.configuration.LifecycleEventHandlerConfiguration;
++import io.papermc.paper.plugin.lifecycle.event.handler.configuration.PrioritizedLifecycleEventHandlerConfiguration;
++import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEventType;
++import io.papermc.paper.registry.RegistryBuilder;
++import io.papermc.paper.registry.RegistryKey;
++import io.papermc.paper.registry.event.type.RegistryEntryAddEventType;
++import org.checkerframework.checker.nullness.qual.NonNull;
++import org.jetbrains.annotations.ApiStatus;
++
++/**
++ * Provider for registry events for a specific registry.
++ * <p>
++ * Supported events are:
++ * <ul>
++ * <li>{@link RegistryEntryAddEvent} (via {@link #entryAdd()})</li>
++ * <li>{@link RegistryFreezeEvent} (via {@link #freeze()})</li>
++ * </ul>
++ *
++ * @param <T> registry entry type
++ * @param <B> registry entry builder type
++ */
++public interface RegistryEventProvider<T, B extends RegistryBuilder<T>> {
++
++ /**
++ * Gets the event type for {@link RegistryEntryAddEvent} which is fired just before
++ * an object is added to a registry.
++ * <p>
++ * Can be used in {@link io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager#registerEventHandler(LifecycleEventType, LifecycleEventHandler)}
++ * to register a handler for {@link RegistryEntryAddEvent}.
++ *
++ * @return the registry entry add event type
++ */
++ @NonNull RegistryEntryAddEventType<T, B> entryAdd();
++
++ /**
++ * Gets the event type for {@link RegistryFreezeEvent} which is fired just before
++ * a registry is frozen. It allows for the registration of new objects.
++ * <p>
++ * Can be used in {@link io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager#registerEventHandler(LifecycleEventType, LifecycleEventHandler)}
++ * to register a handler for {@link RegistryFreezeEvent}.
++ *
++ * @return the registry freeze event type
++ */
++ LifecycleEventType.@NonNull Prioritizable<BootstrapContext, RegistryFreezeEvent<T, B>> freeze();
++
++ /**
++ * Gets the registry key associated with this event type provider.
++ *
++ * @return the registry key
++ */
++ @NonNull RegistryKey<T> registryKey();
++}
+diff --git a/src/main/java/io/papermc/paper/registry/event/RegistryEventProviderImpl.java b/src/main/java/io/papermc/paper/registry/event/RegistryEventProviderImpl.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..cfe47c8bd0888db6d00867acfefc8db42ef314aa
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/event/RegistryEventProviderImpl.java
+@@ -0,0 +1,30 @@
++package io.papermc.paper.registry.event;
++
++import io.papermc.paper.plugin.bootstrap.BootstrapContext;
++import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEventType;
++import io.papermc.paper.registry.RegistryBuilder;
++import io.papermc.paper.registry.RegistryKey;
++import io.papermc.paper.registry.event.type.RegistryEntryAddEventType;
++import org.checkerframework.checker.nullness.qual.NonNull;
++import org.checkerframework.framework.qual.DefaultQualifier;
++import org.jetbrains.annotations.ApiStatus;
++
++@DefaultQualifier(NonNull.class)
++record RegistryEventProviderImpl<T, B extends RegistryBuilder<T>>(RegistryKey<T> registryKey) implements RegistryEventProvider<T, B> {
++
++ static <T, B extends RegistryBuilder<T>> RegistryEventProvider<T, B> create(final RegistryKey<T> registryKey) {
++ return new RegistryEventProviderImpl<>(registryKey);
++ }
++
++ @Override
++ public RegistryEntryAddEventType<T, B> entryAdd() {
++ return RegistryEventTypeProvider.provider().registryEntryAdd(this);
++ }
++
++ @Override
++ public LifecycleEventType.Prioritizable<BootstrapContext, RegistryFreezeEvent<T, B>> freeze() {
++ return RegistryEventTypeProvider.provider().registryFreeze(this);
++ }
++
++}
+diff --git a/src/main/java/io/papermc/paper/registry/event/RegistryEventTypeProvider.java b/src/main/java/io/papermc/paper/registry/event/RegistryEventTypeProvider.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..d807bd2f42c98e37a96cf110ad77820dfffc8398
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/event/RegistryEventTypeProvider.java
+@@ -0,0 +1,24 @@
++package io.papermc.paper.registry.event;
++
++import io.papermc.paper.plugin.bootstrap.BootstrapContext;
++import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEventType;
++import io.papermc.paper.registry.RegistryBuilder;
++import io.papermc.paper.registry.event.type.RegistryEntryAddEventType;
++import java.util.Optional;
++import java.util.ServiceLoader;
++import org.jetbrains.annotations.ApiStatus;
++
++interface RegistryEventTypeProvider {
++
++ Optional<RegistryEventTypeProvider> PROVIDER = ServiceLoader.load(RegistryEventTypeProvider.class)
++ .findFirst();
++
++ static RegistryEventTypeProvider provider() {
++ return PROVIDER.orElseThrow(() -> new IllegalStateException("Could not find a %s service implementation".formatted(RegistryEventTypeProvider.class.getSimpleName())));
++ }
++
++ <T, B extends RegistryBuilder<T>> RegistryEntryAddEventType<T, B> registryEntryAdd(RegistryEventProvider<T, B> type);
++
++ <T, B extends RegistryBuilder<T>> LifecycleEventType.Prioritizable<BootstrapContext, RegistryFreezeEvent<T, B>> registryFreeze(RegistryEventProvider<T, B> type);
++}
+diff --git a/src/main/java/io/papermc/paper/registry/event/RegistryEvents.java b/src/main/java/io/papermc/paper/registry/event/RegistryEvents.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..1f89945be2ed68f52a544f41f7a151b8fdfe113e
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/event/RegistryEvents.java
+@@ -0,0 +1,14 @@
++package io.papermc.paper.registry.event;
++
++import org.jetbrains.annotations.ApiStatus;
++
++/**
++ * Holds providers for {@link RegistryEntryAddEvent} and {@link RegistryFreezeEvent}
++ * handlers for each applicable registry.
++ */
++public final class RegistryEvents {
++
++ private RegistryEvents() {
++ }
++}
+diff --git a/src/main/java/io/papermc/paper/registry/event/RegistryFreezeEvent.java b/src/main/java/io/papermc/paper/registry/event/RegistryFreezeEvent.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..3a127b534dc37452b15c5fc5f922f8b6310b3ec0
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/event/RegistryFreezeEvent.java
+@@ -0,0 +1,25 @@
++package io.papermc.paper.registry.event;
++
++import io.papermc.paper.registry.RegistryBuilder;
++import org.checkerframework.checker.nullness.qual.NonNull;
++import org.jetbrains.annotations.ApiStatus;
++
++/**
++ * Event object for {@link RegistryEventProvider#freeze()}. This
++ * event is fired right before a registry is frozen disallowing further changes.
++ * It provides a way for plugins to add new objects to the registry.
++ *
++ * @param <T> registry entry type
++ * @param <B> registry entry builder type
++ */
++public interface RegistryFreezeEvent<T, B extends RegistryBuilder<T>> extends RegistryEvent<T> {
++
++ /**
++ * Get the writable registry.
++ *
++ * @return a writable registry
++ */
++ @NonNull WritableRegistry<T, B> registry();
++}
+diff --git a/src/main/java/io/papermc/paper/registry/event/WritableRegistry.java b/src/main/java/io/papermc/paper/registry/event/WritableRegistry.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..6de377275097f065c38dd59c6db9704018ac81fc
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/event/WritableRegistry.java
+@@ -0,0 +1,27 @@
++package io.papermc.paper.registry.event;
++
++import io.papermc.paper.registry.RegistryBuilder;
++import io.papermc.paper.registry.TypedKey;
++import java.util.function.Consumer;
++import org.checkerframework.checker.nullness.qual.NonNull;
++import org.jetbrains.annotations.ApiStatus;
++
++/**
++ * A registry which supports registering new objects.
++ *
++ * @param <T> registry entry type
++ * @param <B> registry entry builder type
++ */
++public interface WritableRegistry<T, B extends RegistryBuilder<T>> {
++
++ /**
++ * Register a new value with the specified key. This will
++ * fire a {@link RegistryEntryAddEvent} for the new entry.
++ *
++ * @param key the entry's key (must be unique from others)
++ * @param value a consumer for the entry's builder
++ */
++ void register(@NonNull TypedKey<T> key, @NonNull Consumer<? super B> value);
++}
+diff --git a/src/main/java/io/papermc/paper/registry/event/type/RegistryEntryAddConfiguration.java b/src/main/java/io/papermc/paper/registry/event/type/RegistryEntryAddConfiguration.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..ad54c48ff2de18fe135c29102655f39c84063792
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/event/type/RegistryEntryAddConfiguration.java
+@@ -0,0 +1,30 @@
++package io.papermc.paper.registry.event.type;
++
++import io.papermc.paper.plugin.bootstrap.BootstrapContext;
++import io.papermc.paper.plugin.lifecycle.event.handler.configuration.PrioritizedLifecycleEventHandlerConfiguration;
++import io.papermc.paper.registry.TypedKey;
++import org.checkerframework.checker.nullness.qual.NonNull;
++import org.jetbrains.annotations.Contract;
++
++/**
++ * Specific configuration for {@link io.papermc.paper.registry.event.RegistryEntryAddEvent}s.
++ *
++ * @param <T> registry entry type
++ */
++public interface RegistryEntryAddConfiguration<T> extends PrioritizedLifecycleEventHandlerConfiguration<BootstrapContext> {
++
++ /**
++ * Only call the handler if the value being added matches the specified key.
++ *
++ * @param key the key to match
++ * @return this configuration
++ */
++ @Contract(value = "_ -> this", mutates = "this")
++ @NonNull RegistryEntryAddConfiguration<T> onlyFor(@NonNull TypedKey<T> key);
++
++ @Override
++ @NonNull RegistryEntryAddConfiguration<T> priority(int priority);
++
++ @Override
++ @NonNull RegistryEntryAddConfiguration<T> monitor();
++}
+diff --git a/src/main/java/io/papermc/paper/registry/event/type/RegistryEntryAddEventType.java b/src/main/java/io/papermc/paper/registry/event/type/RegistryEntryAddEventType.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..f4d4ebf6cbed1b4a9955ceb2d0586782181d97e5
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/event/type/RegistryEntryAddEventType.java
+@@ -0,0 +1,18 @@
++package io.papermc.paper.registry.event.type;
++
++import io.papermc.paper.plugin.bootstrap.BootstrapContext;
++import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEventType;
++import io.papermc.paper.registry.RegistryBuilder;
++import io.papermc.paper.registry.event.RegistryEntryAddEvent;
++import org.jetbrains.annotations.ApiStatus;
++
++/**
++ * Lifecycle event type for {@link RegistryEntryAddEvent}s.
++ *
++ * @param <T> registry entry type
++ * @param <B> registry entry builder type
++ */
++public interface RegistryEntryAddEventType<T, B extends RegistryBuilder<T>> extends LifecycleEventType<BootstrapContext, RegistryEntryAddEvent<T, B>, RegistryEntryAddConfiguration<T>> {
++}
+diff --git a/src/main/java/org/bukkit/Registry.java b/src/main/java/org/bukkit/Registry.java
+index 802511eaf697d703cadb4b418fe51ea6d31ff3c8..85c4f231dc343c73f1678819f1ac95d5a7ffd080 100644
+--- a/src/main/java/org/bukkit/Registry.java
++++ b/src/main/java/org/bukkit/Registry.java
+@@ -353,6 +353,27 @@ public interface Registry<T extends Keyed> extends Iterable<T> {
+ */
+ @Nullable
+ T get(@NotNull NamespacedKey key);
++ // Paper start
++ /**
++ * Get the object by its key.
++ *
++ * @param key non-null key
++ * @return item or null if it does not exist
++ */
++ default @Nullable T get(final net.kyori.adventure.key.@NotNull Key key) {
++ return key instanceof final NamespacedKey nsKey ? this.get(nsKey) : this.get(new NamespacedKey(key.namespace(), key.value()));
++ }
++
++ /**
++ * Get the object by its typed key.
++ *
++ * @param typedKey non-null typed key
++ * @return item or null if it does not exist
++ */
++ default @Nullable T get(final io.papermc.paper.registry.@NotNull TypedKey<T> typedKey) {
++ return this.get(typedKey.key());
++ }
++ // Paper end
+
+ // Paper start - improve Registry
+ /**
diff --git a/patches/server/0019-Paper-Plugins.patch b/patches/server/0019-Paper-Plugins.patch
index 84a4fe0f8f..4efcc61687 100644
--- a/patches/server/0019-Paper-Plugins.patch
+++ b/patches/server/0019-Paper-Plugins.patch
@@ -760,16 +760,18 @@ index 0000000000000000000000000000000000000000..b38e1e0f3d3055086f51bb191fd4b60e
+}
diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/LaunchEntryPointHandler.java b/src/main/java/io/papermc/paper/plugin/entrypoint/LaunchEntryPointHandler.java
new file mode 100644
-index 0000000000000000000000000000000000000000..6c0f2c315387734f8dd4a7eca633aa0a9856dd17
+index 0000000000000000000000000000000000000000..48bc745ca9632fc46b5f786ff570434702eb47f2
--- /dev/null
+++ b/src/main/java/io/papermc/paper/plugin/entrypoint/LaunchEntryPointHandler.java
-@@ -0,0 +1,65 @@
+@@ -0,0 +1,74 @@
+package io.papermc.paper.plugin.entrypoint;
+
+import io.papermc.paper.plugin.provider.PluginProvider;
+import io.papermc.paper.plugin.storage.BootstrapProviderStorage;
+import io.papermc.paper.plugin.storage.ProviderStorage;
+import io.papermc.paper.plugin.storage.ServerPluginProviderStorage;
++import it.unimi.dsi.fastutil.objects.Object2BooleanMap;
++import it.unimi.dsi.fastutil.objects.Object2BooleanOpenHashMap;
+import org.jetbrains.annotations.ApiStatus;
+
+import java.util.HashMap;
@@ -782,9 +784,11 @@ index 0000000000000000000000000000000000000000..6c0f2c315387734f8dd4a7eca633aa0a
+
+ public static final LaunchEntryPointHandler INSTANCE = new LaunchEntryPointHandler();
+ private final Map<Entrypoint<?>, ProviderStorage<?>> storage = new HashMap<>();
++ private final Object2BooleanMap<Entrypoint<?>> enteredMap = new Object2BooleanOpenHashMap<>();
+
+ LaunchEntryPointHandler() {
+ this.populateProviderStorage();
++ this.enteredMap.defaultReturnValue(false);
+ }
+
+ // Utility
@@ -800,6 +804,7 @@ index 0000000000000000000000000000000000000000..6c0f2c315387734f8dd4a7eca633aa0a
+ }
+
+ storage.enter();
++ this.enteredMap.put(entrypoint, true);
+ }
+
+ @Override
@@ -823,6 +828,10 @@ index 0000000000000000000000000000000000000000..6c0f2c315387734f8dd4a7eca633aa0a
+ return storage;
+ }
+
++ public boolean hasEntered(Entrypoint<?> entrypoint) {
++ return this.enteredMap.getBoolean(entrypoint);
++ }
++
+ // Reload only
+ public void populateProviderStorage() {
+ this.storage.put(Entrypoint.BOOTSTRAPPER, new BootstrapProviderStorage());
diff --git a/patches/server/0237-Optimize-MappedRegistry.patch b/patches/server/0237-Optimize-MappedRegistry.patch
index 8f45008c5f..17c68f31d7 100644
--- a/patches/server/0237-Optimize-MappedRegistry.patch
+++ b/patches/server/0237-Optimize-MappedRegistry.patch
@@ -8,7 +8,7 @@ Use larger initial sizes to increase bucket capacity on the BiMap
BiMap.get was seen to be using a good bit of CPU time.
diff --git a/src/main/java/net/minecraft/core/MappedRegistry.java b/src/main/java/net/minecraft/core/MappedRegistry.java
-index 362e49f503f3c792fbecf41ec9f235bbc02644de..6e4d5c168acdb9aaa9fbbee090082e4dc25e89e9 100644
+index 1dcbde18bd9c462cca48887b904a9c43261e1854..edbbafd1705345282e5e6251eb71bfde5793b7d4 100644
--- a/src/main/java/net/minecraft/core/MappedRegistry.java
+++ b/src/main/java/net/minecraft/core/MappedRegistry.java
@@ -35,11 +35,11 @@ public class MappedRegistry<T> implements WritableRegistry<T> {
diff --git a/patches/server/0475-Add-RegistryAccess-for-managing-Registries.patch b/patches/server/0475-Add-RegistryAccess-for-managing-Registries.patch
index 808bc4c6c6..4f2b6f6c28 100644
--- a/patches/server/0475-Add-RegistryAccess-for-managing-Registries.patch
+++ b/patches/server/0475-Add-RegistryAccess-for-managing-Registries.patch
@@ -12,7 +12,7 @@ public net.minecraft.server.RegistryLayer STATIC_ACCESS
diff --git a/src/main/java/io/papermc/paper/registry/PaperRegistries.java b/src/main/java/io/papermc/paper/registry/PaperRegistries.java
new file mode 100644
-index 0000000000000000000000000000000000000000..55feffc03d2924e5ec0f55fc65e8aa148cb0cc62
+index 0000000000000000000000000000000000000000..40ac461d2f1906059377c77229612f540e827d75
--- /dev/null
+++ b/src/main/java/io/papermc/paper/registry/PaperRegistries.java
@@ -0,0 +1,122 @@
@@ -66,9 +66,9 @@ index 0000000000000000000000000000000000000000..55feffc03d2924e5ec0f55fc65e8aa14
+@DefaultQualifier(NonNull.class)
+public final class PaperRegistries {
+
-+ static final List<RegistryEntry<?, ?, ?>> REGISTRY_ENTRIES;
-+ private static final Map<RegistryKey<?>, RegistryEntry<?, ?, ?>> BY_REGISTRY_KEY;
-+ private static final Map<ResourceKey<?>, RegistryEntry<?, ?, ?>> BY_RESOURCE_KEY;
++ static final List<RegistryEntry<?, ?>> REGISTRY_ENTRIES;
++ private static final Map<RegistryKey<?>, RegistryEntry<?, ?>> BY_REGISTRY_KEY;
++ private static final Map<ResourceKey<?>, RegistryEntry<?, ?>> BY_RESOURCE_KEY;
+ static {
+ REGISTRY_ENTRIES = List.of(
+ // built-ins
@@ -105,9 +105,9 @@ index 0000000000000000000000000000000000000000..55feffc03d2924e5ec0f55fc65e8aa14
+ apiOnly(Registries.FROG_VARIANT, RegistryKey.FROG_VARIANT, () -> org.bukkit.Registry.FROG_VARIANT),
+ apiOnly(Registries.MAP_DECORATION_TYPE, RegistryKey.MAP_DECORATION_TYPE, () -> org.bukkit.Registry.MAP_DECORATION_TYPE)
+ );
-+ final Map<RegistryKey<?>, RegistryEntry<?, ?, ?>> byRegistryKey = new IdentityHashMap<>(REGISTRY_ENTRIES.size());
-+ final Map<ResourceKey<?>, RegistryEntry<?, ?, ?>> byResourceKey = new IdentityHashMap<>(REGISTRY_ENTRIES.size());
-+ for (final RegistryEntry<?, ?, ?> entry : REGISTRY_ENTRIES) {
++ final Map<RegistryKey<?>, RegistryEntry<?, ?>> byRegistryKey = new IdentityHashMap<>(REGISTRY_ENTRIES.size());
++ final Map<ResourceKey<?>, RegistryEntry<?, ?>> byResourceKey = new IdentityHashMap<>(REGISTRY_ENTRIES.size());
++ for (final RegistryEntry<?, ?> entry : REGISTRY_ENTRIES) {
+ byRegistryKey.put(entry.apiKey(), entry);
+ byResourceKey.put(entry.mcKey(), entry);
+ }
@@ -116,13 +116,13 @@ index 0000000000000000000000000000000000000000..55feffc03d2924e5ec0f55fc65e8aa14
+ }
+
+ @SuppressWarnings("unchecked")
-+ public static <M, T extends Keyed, R extends org.bukkit.Registry<T>> @Nullable RegistryEntry<M, T, R> getEntry(final ResourceKey<? extends Registry<M>> resourceKey) {
-+ return (RegistryEntry<M, T, R>) BY_RESOURCE_KEY.get(resourceKey);
++ public static <M, T extends Keyed> @Nullable RegistryEntry<M, T> getEntry(final ResourceKey<? extends Registry<M>> resourceKey) {
++ return (RegistryEntry<M, T>) BY_RESOURCE_KEY.get(resourceKey);
+ }
+
+ @SuppressWarnings("unchecked")
-+ public static <M, T extends Keyed, R extends org.bukkit.Registry<T>> @Nullable RegistryEntry<M, T, R> getEntry(final RegistryKey<? super T> registryKey) {
-+ return (RegistryEntry<M, T, R>) BY_REGISTRY_KEY.get(registryKey);
++ public static <M, T extends Keyed> @Nullable RegistryEntry<M, T> getEntry(final RegistryKey<? super T> registryKey) {
++ return (RegistryEntry<M, T>) BY_REGISTRY_KEY.get(registryKey);
+ }
+
+ @SuppressWarnings("unchecked")
@@ -140,7 +140,7 @@ index 0000000000000000000000000000000000000000..55feffc03d2924e5ec0f55fc65e8aa14
+}
diff --git a/src/main/java/io/papermc/paper/registry/PaperRegistryAccess.java b/src/main/java/io/papermc/paper/registry/PaperRegistryAccess.java
new file mode 100644
-index 0000000000000000000000000000000000000000..9f2bcfe0d9e479466a1e46e503071d1151310e6a
+index 0000000000000000000000000000000000000000..d591e3a2e19d5358a0d25a5a681368943622d231
--- /dev/null
+++ b/src/main/java/io/papermc/paper/registry/PaperRegistryAccess.java
@@ -0,0 +1,124 @@
@@ -190,7 +190,7 @@ index 0000000000000000000000000000000000000000..9f2bcfe0d9e479466a1e46e503071d11
+ @Override
+ public <T extends Keyed> @Nullable Registry<T> getRegistry(final Class<T> type) {
+ final RegistryKey<T> registryKey;
-+ final @Nullable RegistryEntry<?, T, ?> entry;
++ final @Nullable RegistryEntry<?, T> entry;
+ registryKey = requireNonNull(byType(type), () -> type + " is not a valid registry type");
+ entry = PaperRegistries.getEntry(registryKey);
+ final @Nullable RegistryHolder<T> registry = (RegistryHolder<T>) this.registries.get(registryKey);
@@ -198,7 +198,7 @@ index 0000000000000000000000000000000000000000..9f2bcfe0d9e479466a1e46e503071d11
+ // if the registry exists, return right away. Since this is the "legacy" method, we return DelayedRegistry
+ // for the non-builtin Registry instances stored as fields in Registry.
+ return registry.get();
-+ } else if (entry instanceof DelayedRegistryEntry<?, T, ?>) {
++ } else if (entry instanceof DelayedRegistryEntry<?, T>) {
+ // if the registry doesn't exist and the entry is marked as "delayed", we create a registry holder that is empty
+ // which will later be filled with the actual registry. This is so the fields on org.bukkit.Registry can be populated with
+ // registries that don't exist at the time org.bukkit.Registry is statically initialized.
@@ -243,7 +243,7 @@ index 0000000000000000000000000000000000000000..9f2bcfe0d9e479466a1e46e503071d11
+
+ @SuppressWarnings("unchecked") // this method should be called right after any new MappedRegistry instances are created to later be used by the server.
+ private <M, B extends Keyed, R extends Registry<B>> void registerRegistry(final ResourceKey<? extends net.minecraft.core.Registry<M>> resourceKey, final net.minecraft.core.Registry<M> registry, final boolean replace) {
-+ final @Nullable RegistryEntry<M, B, R> entry = PaperRegistries.getEntry(resourceKey);
++ final @Nullable RegistryEntry<M, B> entry = PaperRegistries.getEntry(resourceKey);
+ if (entry == null) { // skip registries that don't have API entries
+ return;
+ }
@@ -252,7 +252,7 @@ index 0000000000000000000000000000000000000000..9f2bcfe0d9e479466a1e46e503071d11
+ // if the holder doesn't exist yet, or is marked as "replaceable", put it in the map.
+ this.registries.put(entry.apiKey(), entry.createRegistryHolder(registry));
+ } else {
-+ if (registryHolder instanceof RegistryHolder.Delayed<?, ?> && entry instanceof final DelayedRegistryEntry<M, B, R> delayedEntry) {
++ if (registryHolder instanceof RegistryHolder.Delayed<?, ?> && entry instanceof final DelayedRegistryEntry<M, B> delayedEntry) {
+ // if the registry holder is delayed, and the entry is marked as "delayed", then load the holder with the CraftRegistry instance that wraps the actual nms Registry.
+ ((RegistryHolder.Delayed<B, R>) registryHolder).loadFrom(delayedEntry, registry);
+ } else {
@@ -270,7 +270,7 @@ index 0000000000000000000000000000000000000000..9f2bcfe0d9e479466a1e46e503071d11
+}
diff --git a/src/main/java/io/papermc/paper/registry/RegistryHolder.java b/src/main/java/io/papermc/paper/registry/RegistryHolder.java
new file mode 100644
-index 0000000000000000000000000000000000000000..02402ef647c3e78ed56fd6b2687bf7c67448f891
+index 0000000000000000000000000000000000000000..a31bdd9f02fe75a87fceb2ebe8c36b3232a561cc
--- /dev/null
+++ b/src/main/java/io/papermc/paper/registry/RegistryHolder.java
@@ -0,0 +1,47 @@
@@ -312,7 +312,7 @@ index 0000000000000000000000000000000000000000..02402ef647c3e78ed56fd6b2687bf7c6
+ return this.delayedRegistry;
+ }
+
-+ <M> void loadFrom(final DelayedRegistryEntry<M, B, R> delayedEntry, final net.minecraft.core.Registry<M> registry) {
++ <M> void loadFrom(final DelayedRegistryEntry<M, B> delayedEntry, final net.minecraft.core.Registry<M> registry) {
+ final RegistryHolder<B> delegateHolder = delayedEntry.delegate().createRegistryHolder(registry);
+ if (!(delegateHolder instanceof RegistryHolder.Memoized<B, ?>)) {
+ throw new IllegalArgumentException(delegateHolder + " must be a memoized holder");
@@ -323,7 +323,7 @@ index 0000000000000000000000000000000000000000..02402ef647c3e78ed56fd6b2687bf7c6
+}
diff --git a/src/main/java/io/papermc/paper/registry/entry/ApiRegistryEntry.java b/src/main/java/io/papermc/paper/registry/entry/ApiRegistryEntry.java
new file mode 100644
-index 0000000000000000000000000000000000000000..b2281a21eafd1f22f0ce261787e29af8a8637147
+index 0000000000000000000000000000000000000000..2295b0d145cbaabef5d29482c817575dcbe2ba54
--- /dev/null
+++ b/src/main/java/io/papermc/paper/registry/entry/ApiRegistryEntry.java
@@ -0,0 +1,27 @@
@@ -336,7 +336,7 @@ index 0000000000000000000000000000000000000000..b2281a21eafd1f22f0ce261787e29af8
+import net.minecraft.resources.ResourceKey;
+import org.bukkit.Keyed;
+
-+public class ApiRegistryEntry<M, B extends Keyed> extends BaseRegistryEntry<M, B, org.bukkit.Registry<B>> {
++public class ApiRegistryEntry<M, B extends Keyed> extends BaseRegistryEntry<M, B> {
+
+ private final Supplier<org.bukkit.Registry<B>> registrySupplier;
+
@@ -356,7 +356,7 @@ index 0000000000000000000000000000000000000000..b2281a21eafd1f22f0ce261787e29af8
+}
diff --git a/src/main/java/io/papermc/paper/registry/entry/BaseRegistryEntry.java b/src/main/java/io/papermc/paper/registry/entry/BaseRegistryEntry.java
new file mode 100644
-index 0000000000000000000000000000000000000000..1be8a5feccd27779fcd8ebb2c362f17d78d307da
+index 0000000000000000000000000000000000000000..ceb217dbbb84e8bd51365dd47bf91971e364d298
--- /dev/null
+++ b/src/main/java/io/papermc/paper/registry/entry/BaseRegistryEntry.java
@@ -0,0 +1,27 @@
@@ -367,7 +367,7 @@ index 0000000000000000000000000000000000000000..1be8a5feccd27779fcd8ebb2c362f17d
+import net.minecraft.resources.ResourceKey;
+import org.bukkit.Keyed;
+
-+public abstract class BaseRegistryEntry<M, B extends Keyed, R extends org.bukkit.Registry<B>> implements RegistryEntry<M, B, R> { // TODO remove Keyed
++public abstract class BaseRegistryEntry<M, B extends Keyed> implements RegistryEntry<M, B> { // TODO remove Keyed
+
+ private final ResourceKey<? extends Registry<M>> minecraftRegistryKey;
+ private final RegistryKey<B> apiRegistryKey;
@@ -389,7 +389,7 @@ index 0000000000000000000000000000000000000000..1be8a5feccd27779fcd8ebb2c362f17d
+}
diff --git a/src/main/java/io/papermc/paper/registry/entry/CraftRegistryEntry.java b/src/main/java/io/papermc/paper/registry/entry/CraftRegistryEntry.java
new file mode 100644
-index 0000000000000000000000000000000000000000..46b2560de884ef381cb7fc8669cad8f5a1fa3645
+index 0000000000000000000000000000000000000000..568984894a5463ccfa68bb6944b409ab0a2d7ad7
--- /dev/null
+++ b/src/main/java/io/papermc/paper/registry/entry/CraftRegistryEntry.java
@@ -0,0 +1,49 @@
@@ -408,13 +408,13 @@ index 0000000000000000000000000000000000000000..46b2560de884ef381cb7fc8669cad8f5
+import org.checkerframework.framework.qual.DefaultQualifier;
+
+@DefaultQualifier(NonNull.class)
-+public class CraftRegistryEntry<M, B extends Keyed> extends BaseRegistryEntry<M, B, CraftRegistry<B, M>> { // TODO remove Keyed
++public class CraftRegistryEntry<M, B extends Keyed> extends BaseRegistryEntry<M, B> { // TODO remove Keyed
+
+ private static final BiFunction<NamespacedKey, ApiVersion, NamespacedKey> EMPTY = (namespacedKey, apiVersion) -> namespacedKey;
+
+ protected final Class<?> classToPreload;
+ protected final BiFunction<NamespacedKey, M, B> minecraftToBukkit;
-+ private BiFunction<NamespacedKey, ApiVersion, NamespacedKey> updater = EMPTY;
++ protected BiFunction<NamespacedKey, ApiVersion, NamespacedKey> updater = EMPTY;
+
+ protected CraftRegistryEntry(
+ final ResourceKey<? extends Registry<M>> mcKey,
@@ -428,7 +428,7 @@ index 0000000000000000000000000000000000000000..46b2560de884ef381cb7fc8669cad8f5
+ }
+
+ @Override
-+ public RegistryEntry<M, B, CraftRegistry<B, M>> withSerializationUpdater(final BiFunction<NamespacedKey, ApiVersion, NamespacedKey> updater) {
++ public RegistryEntry<M, B> withSerializationUpdater(final BiFunction<NamespacedKey, ApiVersion, NamespacedKey> updater) {
+ this.updater = updater;
+ return this;
+ }
@@ -444,10 +444,10 @@ index 0000000000000000000000000000000000000000..46b2560de884ef381cb7fc8669cad8f5
+}
diff --git a/src/main/java/io/papermc/paper/registry/entry/RegistryEntry.java b/src/main/java/io/papermc/paper/registry/entry/RegistryEntry.java
new file mode 100644
-index 0000000000000000000000000000000000000000..c97bda87742852c921d73f4886721f1ee56b0a85
+index 0000000000000000000000000000000000000000..15991bf13894d850f360a520d1815711d25973ec
--- /dev/null
+++ b/src/main/java/io/papermc/paper/registry/entry/RegistryEntry.java
-@@ -0,0 +1,52 @@
+@@ -0,0 +1,51 @@
+package io.papermc.paper.registry.entry;
+
+import io.papermc.paper.registry.RegistryHolder;
@@ -459,17 +459,16 @@ index 0000000000000000000000000000000000000000..c97bda87742852c921d73f4886721f1e
+import net.minecraft.resources.ResourceKey;
+import org.bukkit.Keyed;
+import org.bukkit.NamespacedKey;
-+import org.bukkit.craftbukkit.CraftRegistry;
+import org.bukkit.craftbukkit.util.ApiVersion;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.framework.qual.DefaultQualifier;
+
+@DefaultQualifier(NonNull.class)
-+public interface RegistryEntry<M, B extends Keyed, R extends org.bukkit.Registry<B>> extends RegistryEntryInfo<M, B> { // TODO remove Keyed
++public interface RegistryEntry<M, B extends Keyed> extends RegistryEntryInfo<M, B> { // TODO remove Keyed
+
+ RegistryHolder<B> createRegistryHolder(Registry<M> nmsRegistry);
+
-+ default RegistryEntry<M, B, R> withSerializationUpdater(final BiFunction<NamespacedKey, ApiVersion, NamespacedKey> updater) {
++ default RegistryEntry<M, B> withSerializationUpdater(final BiFunction<NamespacedKey, ApiVersion, NamespacedKey> updater) {
+ return this;
+ }
+
@@ -479,11 +478,11 @@ index 0000000000000000000000000000000000000000..c97bda87742852c921d73f4886721f1e
+ * as fields, but instead be obtained via {@link io.papermc.paper.registry.RegistryAccess#getRegistry(RegistryKey)}
+ */
+ @Deprecated
-+ default RegistryEntry<M, B, R> delayed() {
++ default RegistryEntry<M, B> delayed() {
+ return new DelayedRegistryEntry<>(this);
+ }
+
-+ static <M, B extends Keyed> RegistryEntry<M, B, CraftRegistry<B, M>> entry(
++ static <M, B extends Keyed> RegistryEntry<M, B> entry(
+ final ResourceKey<? extends Registry<M>> mcKey,
+ final RegistryKey<B> apiKey,
+ final Class<?> classToPreload,
@@ -492,7 +491,7 @@ index 0000000000000000000000000000000000000000..c97bda87742852c921d73f4886721f1e
+ return new CraftRegistryEntry<>(mcKey, apiKey, classToPreload, minecraftToBukkit);
+ }
+
-+ static <M, B extends Keyed> RegistryEntry<M, B, org.bukkit.Registry<B>> apiOnly(
++ static <M, B extends Keyed> RegistryEntry<M, B> apiOnly(
+ final ResourceKey<? extends Registry<M>> mcKey,
+ final RegistryKey<B> apiKey,
+ final Supplier<org.bukkit.Registry<B>> apiRegistrySupplier
@@ -592,7 +591,7 @@ index 0000000000000000000000000000000000000000..5562e8da5ebaef2a3add46e88d64358b
+}
diff --git a/src/main/java/io/papermc/paper/registry/legacy/DelayedRegistryEntry.java b/src/main/java/io/papermc/paper/registry/legacy/DelayedRegistryEntry.java
new file mode 100644
-index 0000000000000000000000000000000000000000..5f615f50ac0cdbc47cf7a39b630b653e0d30cdf5
+index 0000000000000000000000000000000000000000..110b8d559f49f9e4f181b47663962a139a273a72
--- /dev/null
+++ b/src/main/java/io/papermc/paper/registry/legacy/DelayedRegistryEntry.java
@@ -0,0 +1,26 @@
@@ -605,7 +604,7 @@ index 0000000000000000000000000000000000000000..5f615f50ac0cdbc47cf7a39b630b653e
+import net.minecraft.resources.ResourceKey;
+import org.bukkit.Keyed;
+
-+public record DelayedRegistryEntry<M, T extends Keyed, R extends org.bukkit.Registry<T>>(RegistryEntry<M, T, R> delegate) implements RegistryEntry<M, T, R> {
++public record DelayedRegistryEntry<M, T extends Keyed>(RegistryEntry<M, T> delegate) implements RegistryEntry<M, T> {
+
+ @Override
+ public ResourceKey<? extends Registry<M>> mcKey() {
@@ -898,10 +897,10 @@ index 0000000000000000000000000000000000000000..b9d00e65639521eecd44bd2be3e01226
+ }
+}
diff --git a/src/test/java/io/papermc/paper/registry/RegistryKeyTest.java b/src/test/java/io/papermc/paper/registry/RegistryKeyTest.java
-index e1c14886064cde56be7fcd8f22a6ecb2d222a762..f4da2afd8977030e3200ac5d4bf51b7206a90bd7 100644
+index e1c14886064cde56be7fcd8f22a6ecb2d222a762..69cece1537bb558b80e1947fdb1fe25555e82628 100644
--- a/src/test/java/io/papermc/paper/registry/RegistryKeyTest.java
+++ b/src/test/java/io/papermc/paper/registry/RegistryKeyTest.java
-@@ -1,15 +1,18 @@
+@@ -1,15 +1,19 @@
package io.papermc.paper.registry;
+import io.papermc.paper.registry.entry.RegistryEntry;
@@ -912,6 +911,7 @@ index e1c14886064cde56be7fcd8f22a6ecb2d222a762..f4da2afd8977030e3200ac5d4bf51b72
import net.minecraft.resources.ResourceLocation;
+import org.bukkit.Keyed;
import org.bukkit.support.AbstractTestingBase;
++import org.checkerframework.checker.nullness.qual.Nullable;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
@@ -920,7 +920,7 @@ index e1c14886064cde56be7fcd8f22a6ecb2d222a762..f4da2afd8977030e3200ac5d4bf51b72
import static org.junit.jupiter.api.Assertions.assertTrue;
class RegistryKeyTest extends AbstractTestingBase {
-@@ -28,6 +31,12 @@ class RegistryKeyTest extends AbstractTestingBase {
+@@ -28,6 +32,12 @@ class RegistryKeyTest extends AbstractTestingBase {
void testApiRegistryKeysExist(final RegistryKey<?> key) {
final Optional<Registry<Object>> registry = AbstractTestingBase.REGISTRY_CUSTOM.registry(ResourceKey.createRegistryKey(ResourceLocation.parse(key.key().asString())));
assertTrue(registry.isPresent(), "Missing vanilla registry for " + key.key().asString());
@@ -929,7 +929,7 @@ index e1c14886064cde56be7fcd8f22a6ecb2d222a762..f4da2afd8977030e3200ac5d4bf51b72
+ @ParameterizedTest
+ @MethodSource("data")
+ void testRegistryEntryExists(final RegistryKey<?> key) {
-+ final RegistryEntry<?, ?, ?> entry = PaperRegistries.getEntry(key);
++ final @Nullable RegistryEntry<?, ?> entry = PaperRegistries.getEntry(key);
+ assertNotNull(entry, "Missing PaperRegistries entry for " + key);
}
}
diff --git a/patches/server/0935-Add-Lifecycle-Event-system.patch b/patches/server/0935-Add-Lifecycle-Event-system.patch
index 89149b0c71..a19cbe203a 100644
--- a/patches/server/0935-Add-Lifecycle-Event-system.patch
+++ b/patches/server/0935-Add-Lifecycle-Event-system.patch
@@ -54,7 +54,7 @@ index 30b50e6294c6eaade5e17cfaf34600d122e6251c..0bb7694188d5fb75bb756ce75d0060ea
}
diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/LifecycleEventRunner.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/LifecycleEventRunner.java
new file mode 100644
-index 0000000000000000000000000000000000000000..f84c9c80e701231e5c33ac3c5573f1093e80f38b
+index 0000000000000000000000000000000000000000..65c106fbc9ab990ed53cc5f789582c8cccc1a218
--- /dev/null
+++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/LifecycleEventRunner.java
@@ -0,0 +1,110 @@
@@ -117,8 +117,8 @@ index 0000000000000000000000000000000000000000..f84c9c80e701231e5c33ac3c5573f109
+ }
+
+ public <O extends LifecycleEventOwner, E extends PaperLifecycleEvent> void callEvent(final LifecycleEventType<O, ? super E, ?> eventType, final E event, final Predicate<? super O> ownerPredicate) {
-+ final AbstractLifecycleEventType<O, ? super E, ?, ?> lifecycleEventType = (AbstractLifecycleEventType<O, ? super E, ?, ?>) eventType;
-+ lifecycleEventType.forEachHandler(registeredHandler -> {
++ final AbstractLifecycleEventType<O, ? super E, ?> lifecycleEventType = (AbstractLifecycleEventType<O, ? super E, ?>) eventType;
++ lifecycleEventType.forEachHandler(event, registeredHandler -> {
+ try {
+ if (event instanceof final OwnerAwareLifecycleEvent<?> ownerAwareEvent) {
+ ownerAwareGenericHelper(ownerAwareEvent, registeredHandler.owner());
@@ -151,7 +151,7 @@ index 0000000000000000000000000000000000000000..f84c9c80e701231e5c33ac3c5573f109
+ }
+
+ private <O extends LifecycleEventOwner> void removeEventHandlersOwnedBy(final LifecycleEventType<O, ?, ?> eventType, final Plugin possibleOwner) {
-+ final AbstractLifecycleEventType<O, ?, ?, ?> lifecycleEventType = (AbstractLifecycleEventType<O, ?, ?, ?>) eventType;
++ final AbstractLifecycleEventType<O, ?, ?> lifecycleEventType = (AbstractLifecycleEventType<O, ?, ?>) eventType;
+ lifecycleEventType.removeMatching(registeredHandler -> registeredHandler.owner().getPluginMeta().getName().equals(possibleOwner.getPluginMeta().getName()));
+ }
+
@@ -186,7 +186,7 @@ index 0000000000000000000000000000000000000000..e941405269a773e8a77e26ffd1afd84f
+}
diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/PaperLifecycleEventManager.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/PaperLifecycleEventManager.java
new file mode 100644
-index 0000000000000000000000000000000000000000..f1be5b9a29435bae0afd2bd951bfe88d1669e7eb
+index 0000000000000000000000000000000000000000..d05334016bd01201c755dea04c0cea56b6dfcb50
--- /dev/null
+++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/PaperLifecycleEventManager.java
@@ -0,0 +1,26 @@
@@ -213,15 +213,15 @@ index 0000000000000000000000000000000000000000..f1be5b9a29435bae0afd2bd951bfe88d
+ @Override
+ public void registerEventHandler(final LifecycleEventHandlerConfiguration<? super O> handlerConfiguration) {
+ Preconditions.checkState(this.registrationCheck.getAsBoolean(), "Cannot register lifecycle event handlers");
-+ ((AbstractLifecycleEventHandlerConfiguration<? super O, ?, ?>) handlerConfiguration).registerFrom(this.owner);
++ ((AbstractLifecycleEventHandlerConfiguration<? super O, ?>) handlerConfiguration).registerFrom(this.owner);
+ }
+}
diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/AbstractLifecycleEventHandlerConfiguration.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/AbstractLifecycleEventHandlerConfiguration.java
new file mode 100644
-index 0000000000000000000000000000000000000000..6a85a4f581612efff04c1a955493aa2e32476277
+index 0000000000000000000000000000000000000000..fa216e6fd804859293385ed43c53dfca057f317f
--- /dev/null
+++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/AbstractLifecycleEventHandlerConfiguration.java
-@@ -0,0 +1,26 @@
+@@ -0,0 +1,28 @@
+package io.papermc.paper.plugin.lifecycle.event.handler.configuration;
+
+import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent;
@@ -232,28 +232,30 @@ index 0000000000000000000000000000000000000000..6a85a4f581612efff04c1a955493aa2e
+import org.checkerframework.framework.qual.DefaultQualifier;
+
+@DefaultQualifier(NonNull.class)
-+public abstract class AbstractLifecycleEventHandlerConfiguration<O extends LifecycleEventOwner, E extends LifecycleEvent, CI extends AbstractLifecycleEventHandlerConfiguration<O, E, CI>> implements LifecycleEventHandlerConfiguration<O> {
++public abstract class AbstractLifecycleEventHandlerConfiguration<O extends LifecycleEventOwner, E extends LifecycleEvent> implements LifecycleEventHandlerConfiguration<O> {
+
+ private final LifecycleEventHandler<? super E> handler;
-+ private final AbstractLifecycleEventType<O, E, ?, CI> type;
++ private final AbstractLifecycleEventType<O, E, ?> type;
+
-+ protected AbstractLifecycleEventHandlerConfiguration(final LifecycleEventHandler<? super E> handler, final AbstractLifecycleEventType<O, E, ?, CI> type) {
++ protected AbstractLifecycleEventHandlerConfiguration(final LifecycleEventHandler<? super E> handler, final AbstractLifecycleEventType<O, E, ?> type) {
+ this.handler = handler;
+ this.type = type;
+ }
+
-+ public abstract CI config();
-+
+ public final void registerFrom(final O owner) {
-+ this.type.tryRegister(owner, this.handler, this.config());
++ this.type.tryRegister(owner, this);
++ }
++
++ public LifecycleEventHandler<? super E> handler() {
++ return this.handler;
+ }
+}
diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/MonitorLifecycleEventHandlerConfigurationImpl.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/MonitorLifecycleEventHandlerConfigurationImpl.java
new file mode 100644
-index 0000000000000000000000000000000000000000..e0699fcd0a098abc5e1206e7c0fa80b96eca7884
+index 0000000000000000000000000000000000000000..ab444d60d72bd692843052df5d7b24fbb5621cf7
--- /dev/null
+++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/MonitorLifecycleEventHandlerConfigurationImpl.java
-@@ -0,0 +1,33 @@
+@@ -0,0 +1,28 @@
+package io.papermc.paper.plugin.lifecycle.event.handler.configuration;
+
+import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent;
@@ -264,19 +266,14 @@ index 0000000000000000000000000000000000000000..e0699fcd0a098abc5e1206e7c0fa80b9
+import org.checkerframework.framework.qual.DefaultQualifier;
+
+@DefaultQualifier(NonNull.class)
-+public class MonitorLifecycleEventHandlerConfigurationImpl<O extends LifecycleEventOwner, E extends LifecycleEvent> extends AbstractLifecycleEventHandlerConfiguration<O, E, MonitorLifecycleEventHandlerConfigurationImpl<O, E>> implements MonitorLifecycleEventHandlerConfiguration<O> {
++public class MonitorLifecycleEventHandlerConfigurationImpl<O extends LifecycleEventOwner, E extends LifecycleEvent> extends AbstractLifecycleEventHandlerConfiguration<O, E> implements MonitorLifecycleEventHandlerConfiguration<O> {
+
+ private boolean monitor = false;
+
-+ public MonitorLifecycleEventHandlerConfigurationImpl(final LifecycleEventHandler<? super E> handler, final AbstractLifecycleEventType<O, E, ?, MonitorLifecycleEventHandlerConfigurationImpl<O, E>> eventType) {
++ public MonitorLifecycleEventHandlerConfigurationImpl(final LifecycleEventHandler<? super E> handler, final AbstractLifecycleEventType<O, E, ?> eventType) {
+ super(handler, eventType);
+ }
+
-+ @Override
-+ public MonitorLifecycleEventHandlerConfigurationImpl<O, E> config() {
-+ return this;
-+ }
-+
+ public boolean isMonitor() {
+ return this.monitor;
+ }
@@ -289,10 +286,10 @@ index 0000000000000000000000000000000000000000..e0699fcd0a098abc5e1206e7c0fa80b9
+}
diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/PrioritizedLifecycleEventHandlerConfigurationImpl.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/PrioritizedLifecycleEventHandlerConfigurationImpl.java
new file mode 100644
-index 0000000000000000000000000000000000000000..c1d0070fc1594f7a7c29d7dc679da7b347a7140b
+index 0000000000000000000000000000000000000000..ccdad31717bf12b844cbeaf11a49247485ec77f1
--- /dev/null
+++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/PrioritizedLifecycleEventHandlerConfigurationImpl.java
-@@ -0,0 +1,43 @@
+@@ -0,0 +1,40 @@
+package io.papermc.paper.plugin.lifecycle.event.handler.configuration;
+
+import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent;
@@ -304,22 +301,19 @@ index 0000000000000000000000000000000000000000..c1d0070fc1594f7a7c29d7dc679da7b3
+import org.checkerframework.framework.qual.DefaultQualifier;
+
+@DefaultQualifier(NonNull.class)
-+public class PrioritizedLifecycleEventHandlerConfigurationImpl<O extends LifecycleEventOwner, E extends LifecycleEvent> extends AbstractLifecycleEventHandlerConfiguration<O, E, PrioritizedLifecycleEventHandlerConfigurationImpl<O, E>> implements PrioritizedLifecycleEventHandlerConfiguration<O> {
++public class PrioritizedLifecycleEventHandlerConfigurationImpl<O extends LifecycleEventOwner, E extends LifecycleEvent>
++ extends AbstractLifecycleEventHandlerConfiguration<O, E>
++ implements PrioritizedLifecycleEventHandlerConfiguration<O> {
+
+ private static final OptionalInt DEFAULT_PRIORITY = OptionalInt.of(0);
+ private static final OptionalInt MONITOR_PRIORITY = OptionalInt.empty();
+
+ private OptionalInt priority = DEFAULT_PRIORITY;
+
-+ public PrioritizedLifecycleEventHandlerConfigurationImpl(final LifecycleEventHandler<? super E> handler, final AbstractLifecycleEventType<O, E, ?, PrioritizedLifecycleEventHandlerConfigurationImpl<O, E>> eventType) {
++ public PrioritizedLifecycleEventHandlerConfigurationImpl(final LifecycleEventHandler<? super E> handler, final AbstractLifecycleEventType<O, E, ?> eventType) {
+ super(handler, eventType);
+ }
+
-+ @Override
-+ public PrioritizedLifecycleEventHandlerConfigurationImpl<O, E> config() {
-+ return this;
-+ }
-+
+ public OptionalInt priority() {
+ return this.priority;
+ }
@@ -435,10 +429,10 @@ index 0000000000000000000000000000000000000000..6d530c52aaf0dc2cdfe3bd56af557274
+}
diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/AbstractLifecycleEventType.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/AbstractLifecycleEventType.java
new file mode 100644
-index 0000000000000000000000000000000000000000..a65fb37f4a729e2fe9fb81af822db626ec7e6d7b
+index 0000000000000000000000000000000000000000..9359a36d26970742da3a7abb0050158cd6c64e8e
--- /dev/null
+++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/AbstractLifecycleEventType.java
-@@ -0,0 +1,50 @@
+@@ -0,0 +1,54 @@
+package io.papermc.paper.plugin.lifecycle.event.types;
+
+import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent;
@@ -453,7 +447,7 @@ index 0000000000000000000000000000000000000000..a65fb37f4a729e2fe9fb81af822db626
+import org.checkerframework.framework.qual.DefaultQualifier;
+
+@DefaultQualifier(NonNull.class)
-+public abstract class AbstractLifecycleEventType<O extends LifecycleEventOwner, E extends LifecycleEvent, C extends LifecycleEventHandlerConfiguration<O>, CI extends AbstractLifecycleEventHandlerConfiguration<O, E, CI>> implements LifecycleEventType<O, E, C> {
++public abstract class AbstractLifecycleEventType<O extends LifecycleEventOwner, E extends LifecycleEvent, C extends LifecycleEventHandlerConfiguration<O>> implements LifecycleEventType<O, E, C> {
+
+ private final String name;
+ private final Class<? extends O> ownerType;
@@ -474,24 +468,28 @@ index 0000000000000000000000000000000000000000..a65fb37f4a729e2fe9fb81af822db626
+ }
+ }
+
-+ public abstract void forEachHandler(Consumer<? super RegisteredHandler<O, E>> consumer, Predicate<? super RegisteredHandler<O, E>> predicate);
++ public abstract void forEachHandler(E event, Consumer<RegisteredHandler<O, E>> consumer, Predicate<RegisteredHandler<O, E>> predicate);
+
-+ public abstract void removeMatching(Predicate<? super RegisteredHandler<O, E>> predicate);
++ public abstract void removeMatching(Predicate<RegisteredHandler<O, E>> predicate);
+
-+ protected abstract void register(O owner, LifecycleEventHandler<? super E> handler, CI config);
++ protected abstract void register(O owner, AbstractLifecycleEventHandlerConfiguration<O, E> config);
+
-+ public final void tryRegister(final O owner, final LifecycleEventHandler<? super E> handler, final CI config) {
++ public final void tryRegister(final O owner, final AbstractLifecycleEventHandlerConfiguration<O, E> config) {
+ this.verifyOwner(owner);
+ LifecycleEventRunner.INSTANCE.checkRegisteredHandler(owner, this);
-+ this.register(owner, handler, config);
++ this.register(owner, config);
+ }
+
-+ public record RegisteredHandler<O, E extends LifecycleEvent>(O owner, LifecycleEventHandler<? super E> lifecycleEventHandler) {
++ public record RegisteredHandler<O extends LifecycleEventOwner, E extends LifecycleEvent>(O owner, AbstractLifecycleEventHandlerConfiguration<O, E> config) {
++
++ public LifecycleEventHandler<? super E> lifecycleEventHandler() {
++ return this.config().handler();
++ }
+ }
+}
diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/LifecycleEventTypeProviderImpl.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/LifecycleEventTypeProviderImpl.java
new file mode 100644
-index 0000000000000000000000000000000000000000..0886edad92b40276f268bd745b31bac359fd28af
+index 0000000000000000000000000000000000000000..af0cb3298d9c737417c6e54b360f8dc50a5caf04
--- /dev/null
+++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/LifecycleEventTypeProviderImpl.java
@@ -0,0 +1,25 @@
@@ -517,20 +515,21 @@ index 0000000000000000000000000000000000000000..0886edad92b40276f268bd745b31bac3
+
+ @Override
+ public <O extends LifecycleEventOwner, E extends LifecycleEvent> LifecycleEventType.Prioritizable<O, E> prioritized(final String name, final Class<? extends O> ownerType) {
-+ return LifecycleEventRunner.INSTANCE.addEventType(new PrioritizableLifecycleEventType<>(name, ownerType));
++ return LifecycleEventRunner.INSTANCE.addEventType(new PrioritizableLifecycleEventType.Simple<>(name, ownerType));
+ }
+}
diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/MonitorableLifecycleEventType.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/MonitorableLifecycleEventType.java
new file mode 100644
-index 0000000000000000000000000000000000000000..6d92c1d3adf220154dfe7cba3a3f8158356c3e3c
+index 0000000000000000000000000000000000000000..c71912f0050ce0cc6e416948a354c8a66da606a8
--- /dev/null
+++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/MonitorableLifecycleEventType.java
-@@ -0,0 +1,54 @@
+@@ -0,0 +1,58 @@
+package io.papermc.paper.plugin.lifecycle.event.types;
+
+import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent;
+import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner;
+import io.papermc.paper.plugin.lifecycle.event.handler.LifecycleEventHandler;
++import io.papermc.paper.plugin.lifecycle.event.handler.configuration.AbstractLifecycleEventHandlerConfiguration;
+import io.papermc.paper.plugin.lifecycle.event.handler.configuration.MonitorLifecycleEventHandlerConfiguration;
+import io.papermc.paper.plugin.lifecycle.event.handler.configuration.MonitorLifecycleEventHandlerConfigurationImpl;
+import java.util.ArrayList;
@@ -541,7 +540,7 @@ index 0000000000000000000000000000000000000000..6d92c1d3adf220154dfe7cba3a3f8158
+import org.checkerframework.framework.qual.DefaultQualifier;
+
+@DefaultQualifier(NonNull.class)
-+public class MonitorableLifecycleEventType<O extends LifecycleEventOwner, E extends LifecycleEvent> extends AbstractLifecycleEventType<O, E, MonitorLifecycleEventHandlerConfiguration<O>, MonitorLifecycleEventHandlerConfigurationImpl<O, E>> implements LifecycleEventType.Monitorable<O, E> {
++public class MonitorableLifecycleEventType<O extends LifecycleEventOwner, E extends LifecycleEvent> extends AbstractLifecycleEventType<O, E, MonitorLifecycleEventHandlerConfiguration<O>> implements LifecycleEventType.Monitorable<O, E> {
+
+ final List<RegisteredHandler<O, E>> handlers = new ArrayList<>();
+ int nonMonitorIdx = 0;
@@ -556,9 +555,12 @@ index 0000000000000000000000000000000000000000..6d92c1d3adf220154dfe7cba3a3f8158
+ }
+
+ @Override
-+ protected void register(final O owner, final LifecycleEventHandler<? super E> handler, final MonitorLifecycleEventHandlerConfigurationImpl<O, E> config) {
-+ final RegisteredHandler<O, E> registeredHandler = new RegisteredHandler<>(owner, handler);
-+ if (!config.isMonitor()) {
++ protected void register(final O owner, final AbstractLifecycleEventHandlerConfiguration<O, E> config) {
++ if (!(config instanceof final MonitorLifecycleEventHandlerConfigurationImpl<?,?> monitor)) {
++ throw new IllegalArgumentException("Configuration must be a MonitorLifecycleEventHandlerConfiguration");
++ }
++ final RegisteredHandler<O, E> registeredHandler = new RegisteredHandler<>(owner, config);
++ if (!monitor.isMonitor()) {
+ this.handlers.add(this.nonMonitorIdx, registeredHandler);
+ this.nonMonitorIdx++;
+ } else {
@@ -567,7 +569,7 @@ index 0000000000000000000000000000000000000000..6d92c1d3adf220154dfe7cba3a3f8158
+ }
+
+ @Override
-+ public void forEachHandler(final Consumer<? super RegisteredHandler<O, E>> consumer, final Predicate<? super RegisteredHandler<O, E>> predicate) {
++ public void forEachHandler(final E event, final Consumer<RegisteredHandler<O, E>> consumer, final Predicate<RegisteredHandler<O, E>> predicate) {
+ for (final RegisteredHandler<O, E> handler : this.handlers) {
+ if (predicate.test(handler)) {
+ consumer.accept(handler);
@@ -576,7 +578,7 @@ index 0000000000000000000000000000000000000000..6d92c1d3adf220154dfe7cba3a3f8158
+ }
+
+ @Override
-+ public void removeMatching(final Predicate<? super RegisteredHandler<O, E>> predicate) {
++ public void removeMatching(final Predicate<RegisteredHandler<O, E>> predicate) {
+ this.handlers.removeIf(predicate);
+ }
+}
@@ -603,30 +605,35 @@ index 0000000000000000000000000000000000000000..3e7e7474f301c0725fa2bcd6e19e476f
+}
diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/PrioritizableLifecycleEventType.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/PrioritizableLifecycleEventType.java
new file mode 100644
-index 0000000000000000000000000000000000000000..6629f7fabf66ce761024268043cc30076ba8a3f1
+index 0000000000000000000000000000000000000000..76f92a6fc84c0315f3973dc4e92649b66babc3d5
--- /dev/null
+++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/PrioritizableLifecycleEventType.java
-@@ -0,0 +1,64 @@
+@@ -0,0 +1,74 @@
+package io.papermc.paper.plugin.lifecycle.event.types;
+
++import com.google.common.base.Preconditions;
+import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent;
+import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner;
+import io.papermc.paper.plugin.lifecycle.event.handler.LifecycleEventHandler;
++import io.papermc.paper.plugin.lifecycle.event.handler.configuration.AbstractLifecycleEventHandlerConfiguration;
+import io.papermc.paper.plugin.lifecycle.event.handler.configuration.PrioritizedLifecycleEventHandlerConfiguration;
+import io.papermc.paper.plugin.lifecycle.event.handler.configuration.PrioritizedLifecycleEventHandlerConfigurationImpl;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
-+import java.util.OptionalInt;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.framework.qual.DefaultQualifier;
+
+@DefaultQualifier(NonNull.class)
-+public class PrioritizableLifecycleEventType<O extends LifecycleEventOwner, E extends LifecycleEvent> extends AbstractLifecycleEventType<O, E, PrioritizedLifecycleEventHandlerConfiguration<O>, PrioritizedLifecycleEventHandlerConfigurationImpl<O, E>> implements LifecycleEventType.Prioritizable<O, E> {
++public abstract class PrioritizableLifecycleEventType<
++ O extends LifecycleEventOwner,
++ E extends LifecycleEvent,
++ C extends PrioritizedLifecycleEventHandlerConfiguration<O>
++> extends AbstractLifecycleEventType<O, E, C> {
+
-+ private static final Comparator<PrioritizedHandler<?, ?>> COMPARATOR = Comparator.comparing(PrioritizedHandler::priority, (o1, o2) -> {
++ private static final Comparator<RegisteredHandler<?, ?>> COMPARATOR = Comparator.comparing(handler -> ((PrioritizedLifecycleEventHandlerConfigurationImpl<?, ?>) handler.config()).priority(), (o1, o2) -> {
+ if (o1.equals(o2)) {
+ return 0;
+ } else if (o1.isEmpty()) {
@@ -638,38 +645,43 @@ index 0000000000000000000000000000000000000000..6629f7fabf66ce761024268043cc3007
+ }
+ });
+
-+ private final List<PrioritizedHandler<O, E>> handlers = new ArrayList<>();
++ private final List<RegisteredHandler<O, E>> handlers = new ArrayList<>();
+
+ public PrioritizableLifecycleEventType(final String name, final Class<? extends O> ownerType) {
+ super(name, ownerType);
+ }
+
+ @Override
-+ public PrioritizedLifecycleEventHandlerConfiguration<O> newHandler(final LifecycleEventHandler<? super E> handler) {
-+ return new PrioritizedLifecycleEventHandlerConfigurationImpl<>(handler, this);
-+ }
-+
-+ @Override
-+ protected void register(final O owner, final LifecycleEventHandler<? super E> handler, final PrioritizedLifecycleEventHandlerConfigurationImpl<O, E> config) {
-+ this.handlers.add(new PrioritizedHandler<>(new RegisteredHandler<>(owner, handler), config.priority()));
++ protected void register(final O owner, final AbstractLifecycleEventHandlerConfiguration<O, E> config) {
++ Preconditions.checkArgument(config instanceof PrioritizedLifecycleEventHandlerConfigurationImpl<?, ?>, "Configuration must be a PrioritizedLifecycleEventHandlerConfiguration");
++ this.handlers.add(new RegisteredHandler<>(owner, config));
+ this.handlers.sort(COMPARATOR);
+ }
+
+ @Override
-+ public void forEachHandler(final Consumer<? super RegisteredHandler<O, E>> consumer, final Predicate<? super RegisteredHandler<O, E>> predicate) {
-+ for (final PrioritizedHandler<O, E> handler : this.handlers) {
-+ if (predicate.test(handler.handler())) {
-+ consumer.accept(handler.handler());
++ public void forEachHandler(final E event, final Consumer<RegisteredHandler<O, E>> consumer, final Predicate<RegisteredHandler<O, E>> predicate) {
++ for (final RegisteredHandler<O, E> handler : this.handlers) {
++ if (predicate.test(handler)) {
++ consumer.accept(handler);
+ }
+ }
+ }
+
+ @Override
-+ public void removeMatching(final Predicate<? super RegisteredHandler<O, E>> predicate) {
-+ this.handlers.removeIf(prioritizedHandler -> predicate.test(prioritizedHandler.handler()));
++ public void removeMatching(final Predicate<RegisteredHandler<O, E>> predicate) {
++ this.handlers.removeIf(predicate);
+ }
+
-+ private record PrioritizedHandler<O extends LifecycleEventOwner, E extends LifecycleEvent>(RegisteredHandler<O, E> handler, OptionalInt priority) {}
++ public static class Simple<O extends LifecycleEventOwner, E extends LifecycleEvent> extends PrioritizableLifecycleEventType<O, E, PrioritizedLifecycleEventHandlerConfiguration<O>> implements LifecycleEventType.Prioritizable<O, E> {
++ public Simple(final String name, final Class<? extends O> ownerType) {
++ super(name, ownerType);
++ }
++
++ @Override
++ public PrioritizedLifecycleEventHandlerConfiguration<O> newHandler(final LifecycleEventHandler<? super E> handler) {
++ return new PrioritizedLifecycleEventHandlerConfigurationImpl<>(handler, this);
++ }
++ }
+}
diff --git a/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java b/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java
index 834b85f24df023642f8abf7213fe578ac8c17a3e..3e82ea07ca4194844c5528446e2c4a46ff4acee5 100644
diff --git a/patches/server/0975-Brigadier-based-command-API.patch b/patches/server/0975-Brigadier-based-command-API.patch
index e551e947aa..357047a96b 100644
--- a/patches/server/0975-Brigadier-based-command-API.patch
+++ b/patches/server/0975-Brigadier-based-command-API.patch
@@ -1982,7 +1982,7 @@ index 0000000000000000000000000000000000000000..0c3c82b28e581286b798ee58ca4193ef
+
+}
diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/LifecycleEventRunner.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/LifecycleEventRunner.java
-index f84c9c80e701231e5c33ac3c5573f1093e80f38b..6c072e44a8144de6658b4eb818c996f0eac5805b 100644
+index 65c106fbc9ab990ed53cc5f789582c8cccc1a218..618e9c5e48062840e623cccc7ace4e5c3c118e78 100644
--- a/src/main/java/io/papermc/paper/plugin/lifecycle/event/LifecycleEventRunner.java
+++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/LifecycleEventRunner.java
@@ -9,6 +9,7 @@ import io.papermc.paper.plugin.lifecycle.event.registrar.RegistrarEventImpl;
diff --git a/patches/server/0991-Registry-Modification-API.patch b/patches/server/0991-Registry-Modification-API.patch
new file mode 100644
index 0000000000..c770d38c3a
--- /dev/null
+++ b/patches/server/0991-Registry-Modification-API.patch
@@ -0,0 +1,864 @@
+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
+
+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..e2605a9324b9dc91cd910e4bb974d5a911b58b1f
+--- /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, 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);
++ }
++
++ /**
++ * 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..56d8dcf2f3fd042ed1e7ed01a0ce5d90c89ee502 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 - register with listeners
+ }
+ }
+
+diff --git a/src/main/java/net/minecraft/server/ReloadableServerRegistries.java b/src/main/java/net/minecraft/server/ReloadableServerRegistries.java
+index 397bdacab9517354875ebc0bc68d35059b3c318b..705fe42b52414fbac14ec107fda5ad0989a5a115 100644
+--- a/src/main/java/net/minecraft/server/ReloadableServerRegistries.java
++++ b/src/main/java/net/minecraft/server/ReloadableServerRegistries.java
+@@ -66,7 +66,7 @@ public class ReloadableServerRegistries {
+ SimpleJsonResourceReloadListener.scanDirectory(resourceManager, string, GSON, map);
+ map.forEach(
+ (id, json) -> type.deserialize(id, ops, json)
+- .ifPresent(value -> writableRegistry.register(ResourceKey.create(type.registryKey(), id), (T)value, DEFAULT_REGISTRATION_INFO))
++ .ifPresent(value -> io.papermc.paper.registry.PaperRegistryListenerManager.INSTANCE.registerWithListeners(writableRegistry, ResourceKey.create(type.registryKey(), id), value, DEFAULT_REGISTRATION_INFO)) // Paper - register with listeners
+ );
+ return writableRegistry;
+ },
+diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java b/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java
+index d21b7e39d71c785f47f790e1ad4be33a8e8e6e51..43d686a9958cff96f5b15d93e920c8f2313aa65b 100644
+--- a/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java
++++ 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