From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Wed, 2 Mar 2022 13:36:21 -0800
Subject: [PATCH] Add RegistryAccess for managing registries


diff --git a/src/main/java/io/papermc/paper/registry/Reference.java b/src/main/java/io/papermc/paper/registry/Reference.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/io/papermc/paper/registry/Reference.java
@@ -0,0 +0,0 @@
+package io.papermc.paper.registry;
+
+import org.bukkit.Keyed;
+import org.bukkit.NamespacedKey;
+import org.bukkit.Registry;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Represents a reference to a server-backed registry value that may
+ * change.
+ *
+ * @param <T> type of the value
+ */
+@Deprecated(forRemoval = true, since = "1.20.6")
+public interface Reference<T extends Keyed> extends Keyed {
+
+    /**
+     * Gets the value from the registry with the key.
+     *
+     * @return the value
+     * @throws java.util.NoSuchElementException if there is no value with this key
+     */
+    @Deprecated(forRemoval = true, since = "1.20.6")
+    @NotNull T value();
+
+    /**
+     * Gets the value from the registry with the key.
+     *
+     * @return the value or null if it doesn't exist
+     */
+    @Deprecated(forRemoval = true, since = "1.20.6")
+    @Nullable T valueOrNull();
+
+    /**
+     * Creates a reference to a registered value.
+     *
+     * @param registry the registry the value is located in
+     * @param key the key to the value
+     * @param <T> the type of the value
+     * @return a reference
+     */
+    @Deprecated(forRemoval = true, since = "1.20.6")
+    static <T extends Keyed> @NotNull Reference<T> create(@NotNull Registry<T> registry, @NotNull NamespacedKey key) {
+        return new ReferenceImpl<>(registry, key);
+    }
+}
diff --git a/src/main/java/io/papermc/paper/registry/ReferenceImpl.java b/src/main/java/io/papermc/paper/registry/ReferenceImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/io/papermc/paper/registry/ReferenceImpl.java
@@ -0,0 +0,0 @@
+package io.papermc.paper.registry;
+
+import org.bukkit.Keyed;
+import org.bukkit.NamespacedKey;
+import org.bukkit.Registry;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.NoSuchElementException;
+
+record ReferenceImpl<T extends Keyed>(@NotNull Registry<T> registry, @NotNull NamespacedKey key) implements Reference<T> {
+
+    @Override
+    public @NotNull T value() {
+        final T value = this.registry.get(this.key);
+        if (value == null) {
+            throw new NoSuchElementException("No such value with key " + this.key);
+        }
+        return value;
+    }
+
+    @Override
+    public @Nullable T valueOrNull() {
+        return this.registry.get(this.key);
+    }
+
+    @Override
+    public @NotNull NamespacedKey getKey() {
+        return this.key;
+    }
+}
diff --git a/src/main/java/io/papermc/paper/registry/RegistryAccess.java b/src/main/java/io/papermc/paper/registry/RegistryAccess.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/io/papermc/paper/registry/RegistryAccess.java
@@ -0,0 +0,0 @@
+package io.papermc.paper.registry;
+
+import org.bukkit.Keyed;
+import org.bukkit.Registry;
+import org.jetbrains.annotations.ApiStatus;
+import org.jspecify.annotations.NullMarked;
+import org.jspecify.annotations.Nullable;
+
+/**
+ * Used for accessing different {@link Registry} instances
+ * by a {@link RegistryKey}. Get the main instance of {@link RegistryAccess}
+ * with {@link RegistryAccess#registryAccess()}.
+ */
+@NullMarked
+@ApiStatus.NonExtendable
+public interface RegistryAccess {
+
+    /**
+     * Get the {@link RegistryAccess} instance for the server.
+     *
+     * @return the RegistryAccess instance
+     */
+    static RegistryAccess registryAccess() {
+        return RegistryAccessHolder.INSTANCE.orElseThrow(() -> new IllegalStateException("No RegistryAccess implementation found"));
+    }
+
+    /**
+     * Gets the registry based on the type.
+     *
+     * @param type the type
+     * @return the registry or null if none found
+     * @param <T> the type
+     * @deprecated use {@link #getRegistry(RegistryKey)} with keys from {@link RegistryKey}
+     */
+    @Deprecated(since = "1.20.6", forRemoval = true)
+    <T extends Keyed> @Nullable Registry<T> getRegistry(Class<T> type);
+
+    /**
+     * Gets the registry with the specified key.
+     *
+     * @param registryKey the key
+     * @return the registry
+     * @param <T> the type
+     * @throws java.util.NoSuchElementException if no registry with the key is found
+     * @throws IllegalArgumentException if the registry is not available yet
+     */
+    // Future note: We should have no trouble removing this generic qualifier when
+    // registry types no longer have to be "keyed" as it shouldn't break ABI or API.
+    <T extends Keyed> Registry<T> getRegistry(RegistryKey<T> registryKey);
+}
diff --git a/src/main/java/io/papermc/paper/registry/RegistryAccessHolder.java b/src/main/java/io/papermc/paper/registry/RegistryAccessHolder.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/io/papermc/paper/registry/RegistryAccessHolder.java
@@ -0,0 +0,0 @@
+package io.papermc.paper.registry;
+
+import java.util.Optional;
+import java.util.ServiceLoader;
+
+final class RegistryAccessHolder {
+
+    static final Optional<RegistryAccess> INSTANCE = ServiceLoader.load(RegistryAccess.class).findFirst();
+
+    private RegistryAccessHolder() {
+    }
+}
diff --git a/src/main/java/io/papermc/paper/registry/RegistryKeyImpl.java b/src/main/java/io/papermc/paper/registry/RegistryKeyImpl.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/io/papermc/paper/registry/RegistryKeyImpl.java
+++ b/src/main/java/io/papermc/paper/registry/RegistryKeyImpl.java
@@ -0,0 +0,0 @@ record RegistryKeyImpl<T>(Key key) implements RegistryKey<T> {
 
     static final Set<RegistryKey<?>> REGISTRY_KEYS = Sets.newIdentityHashSet();
 
+    // override equals and hashCode to this can be used to simulate an "identity" hashmap
+    @Override
+    public boolean equals(final @Nullable Object obj) {
+        return obj == this;
+    }
+
+    @Override
+    public int hashCode() {
+        return System.identityHashCode(this);
+    }
+
     static <T> RegistryKey<T> create(@Subst("some_key") final String key) {
         final RegistryKey<T> registryKey = createInternal(key);
         REGISTRY_KEYS.add(registryKey);
diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/Bukkit.java
+++ b/src/main/java/org/bukkit/Bukkit.java
@@ -0,0 +0,0 @@ public final class Bukkit {
      * @param tClass of the registry to get
      * @param <T> type of the registry
      * @return the corresponding registry or null if not present
+     * @deprecated use {@link io.papermc.paper.registry.RegistryAccess#getRegistry(io.papermc.paper.registry.RegistryKey)}
+     * with keys from {@link io.papermc.paper.registry.RegistryKey}
      */
     @Nullable
+    @Deprecated(since = "1.20.6")
     public static <T extends Keyed> Registry<T> getRegistry(@NotNull Class<T> tClass) {
         return server.getRegistry(tClass);
     }
diff --git a/src/main/java/org/bukkit/Particle.java b/src/main/java/org/bukkit/Particle.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/Particle.java
+++ b/src/main/java/org/bukkit/Particle.java
@@ -0,0 +0,0 @@ public enum Particle implements Keyed {
 
     private final NamespacedKey key;
     private final Class<?> dataType;
-    final boolean register;
+    // Paper - all particles are registered
 
     Particle(String key) {
         this(key, Void.class);
     }
 
-    Particle(String key, boolean register) {
-        this(key, Void.class, register);
-    }
+    // Paper - all particles are registered
 
     Particle(String key, /*@NotNull*/ Class<?> data) {
-        this(key, data, true);
-    }
-
-    Particle(String key, /*@NotNull*/ Class<?> data, boolean register) {
+        // Paper - all particles are registered
         if (key != null) {
             this.key = NamespacedKey.minecraft(key);
         } else {
             this.key = null;
         }
         dataType = data;
-        this.register = register;
+        // Paper - all particles are registered
     }
 
     /**
diff --git a/src/main/java/org/bukkit/Registry.java b/src/main/java/org/bukkit/Registry.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/Registry.java
+++ b/src/main/java/org/bukkit/Registry.java
@@ -0,0 +0,0 @@ public interface Registry<T extends Keyed> extends Iterable<T> {
      * Server art.
      *
      * @see Art
+     * @deprecated use {@link io.papermc.paper.registry.RegistryAccess#getRegistry(io.papermc.paper.registry.RegistryKey)} with {@link io.papermc.paper.registry.RegistryKey#PAINTING_VARIANT}
      */
-    Registry<Art> ART = Objects.requireNonNull(Bukkit.getRegistry(Art.class), "No registry present for Art. This is a bug.");
+    @Deprecated(since = "1.21.3") // Paper
+    Registry<Art> ART = Objects.requireNonNull(io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(Art.class), "No registry present for Art. This is a bug.");
     /**
      * Attribute.
      *
      * @see Attribute
      */
-    Registry<Attribute> ATTRIBUTE = Objects.requireNonNull(Bukkit.getRegistry(Attribute.class), "No registry present for Attribute. This is a bug.");
+    Registry<Attribute> ATTRIBUTE = io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(io.papermc.paper.registry.RegistryKey.ATTRIBUTE); // Paper
     /**
      * Server banner patterns.
      *
      * @see PatternType
+     * @deprecated use {@link io.papermc.paper.registry.RegistryAccess#getRegistry(io.papermc.paper.registry.RegistryKey)} with {@link io.papermc.paper.registry.RegistryKey#BANNER_PATTERN}
      */
-    Registry<PatternType> BANNER_PATTERN = Objects.requireNonNull(Bukkit.getRegistry(PatternType.class), "No registry present for Pattern Type. This is a bug.");
+    @Deprecated(since = "1.21") // Paper
+    Registry<PatternType> BANNER_PATTERN = Objects.requireNonNull(io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(PatternType.class), "No registry present for PatternType. This is a bug."); // Paper
     /**
      * Server biomes.
      *
      * @see Biome
+     * @deprecated use {@link io.papermc.paper.registry.RegistryAccess#getRegistry(io.papermc.paper.registry.RegistryKey)} with {@link io.papermc.paper.registry.RegistryKey#BIOME}
      */
-    Registry<Biome> BIOME = Objects.requireNonNull(Bukkit.getRegistry(Biome.class), "No registry present for Biome. This is a bug.");
+    @Deprecated(since = "1.21.3") // Paper
+    Registry<Biome> BIOME = Objects.requireNonNull(io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(Biome.class), "No registry present for Biome. This is a bug.");
     /**
      * Server block types.
      *
@@ -0,0 +0,0 @@ public interface Registry<T extends Keyed> extends Iterable<T> {
      * @apiNote BlockType is not ready for public usage yet
      */
     @ApiStatus.Internal
-    Registry<BlockType> BLOCK = Objects.requireNonNull(Bukkit.getRegistry(BlockType.class), "No registry present for BlockType. This is a bug.");
+    Registry<BlockType> BLOCK = io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(io.papermc.paper.registry.RegistryKey.BLOCK); // Paper
     /**
      * Custom boss bars.
      *
@@ -0,0 +0,0 @@ public interface Registry<T extends Keyed> extends Iterable<T> {
      *
      * @see Cat.Type
      */
-    Registry<Cat.Type> CAT_VARIANT = Objects.requireNonNull(Bukkit.getRegistry(Cat.Type.class), "No registry present for Cat Type. This is a bug.");
+    Registry<Cat.Type> CAT_VARIANT = io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(io.papermc.paper.registry.RegistryKey.CAT_VARIANT); // Paper
     /**
      * Server enchantments.
      *
      * @see Enchantment
+     * @deprecated use {@link io.papermc.paper.registry.RegistryAccess#getRegistry(io.papermc.paper.registry.RegistryKey)} with {@link io.papermc.paper.registry.RegistryKey#ENCHANTMENT}
      */
-    Registry<Enchantment> ENCHANTMENT = Objects.requireNonNull(Bukkit.getRegistry(Enchantment.class), "No registry present for Enchantment. This is a bug.");
+    @Deprecated(since = "1.21")
+    Registry<Enchantment> ENCHANTMENT = Objects.requireNonNull(io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(Enchantment.class), "No registry present for Enchantment. This is a bug."); // Paper
     /**
      * Server entity types.
      *
      * @see EntityType
      */
-    Registry<EntityType> ENTITY_TYPE = new SimpleRegistry<>(EntityType.class, (entity) -> entity != EntityType.UNKNOWN);
+    Registry<EntityType> ENTITY_TYPE = io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(io.papermc.paper.registry.RegistryKey.ENTITY_TYPE); // Paper
     /**
      * Server instruments.
      *
      * @see MusicInstrument
+     * @deprecated use {@link io.papermc.paper.registry.RegistryAccess#getRegistry(io.papermc.paper.registry.RegistryKey)} with {@link io.papermc.paper.registry.RegistryKey#INSTRUMENT}
      */
-    Registry<MusicInstrument> INSTRUMENT = Objects.requireNonNull(Bukkit.getRegistry(MusicInstrument.class), "No registry present for MusicInstrument. This is a bug.");
+    @Deprecated(since = "1.21.2")
+    Registry<MusicInstrument> INSTRUMENT = Objects.requireNonNull(io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(MusicInstrument.class), "No registry present for Instruments. This is a bug."); // Paper
     /**
      * Server item types.
      *
@@ -0,0 +0,0 @@ public interface Registry<T extends Keyed> extends Iterable<T> {
      * @apiNote ItemType is not ready for public usage yet
      */
     @ApiStatus.Internal
-    Registry<ItemType> ITEM = Objects.requireNonNull(Bukkit.getRegistry(ItemType.class), "No registry present for ItemType. This is a bug.");
+    Registry<ItemType> ITEM = io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(io.papermc.paper.registry.RegistryKey.ITEM); // Paper
     /**
      * Default server loot tables.
      *
@@ -0,0 +0,0 @@ public interface Registry<T extends Keyed> extends Iterable<T> {
      * @see MenuType
      */
     @ApiStatus.Experimental
-    Registry<MenuType> MENU = Objects.requireNonNull(Bukkit.getRegistry(MenuType.class), "No registry present for MenuType. This is a bug.");
+    Registry<MenuType> MENU = io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(io.papermc.paper.registry.RegistryKey.MENU); // Paper
     /**
      * Server mob effects.
      *
      * @see PotionEffectType
      */
-    Registry<PotionEffectType> EFFECT = Objects.requireNonNull(Bukkit.getRegistry(PotionEffectType.class), "No registry present for PotionEffectType. This is a bug.");
+    Registry<PotionEffectType> EFFECT = io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(io.papermc.paper.registry.RegistryKey.MOB_EFFECT); // Paper
     /**
      * Server particles.
      *
      * @see Particle
      */
-    Registry<Particle> PARTICLE_TYPE = new SimpleRegistry<>(Particle.class, (par) -> par.register);
+    Registry<Particle> PARTICLE_TYPE = io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(io.papermc.paper.registry.RegistryKey.PARTICLE_TYPE); // Paper
     /**
      * Server potions.
      *
      * @see PotionType
      */
-    Registry<PotionType> POTION = new SimpleRegistry<>(PotionType.class);
+    Registry<PotionType> POTION = io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(io.papermc.paper.registry.RegistryKey.POTION); // Paper
     /**
      * Server statistics.
      *
@@ -0,0 +0,0 @@ public interface Registry<T extends Keyed> extends Iterable<T> {
      * Server structures.
      *
      * @see Structure
+     * @deprecated use {@link io.papermc.paper.registry.RegistryAccess#getRegistry(io.papermc.paper.registry.RegistryKey)} with {@link io.papermc.paper.registry.RegistryKey#STRUCTURE}
      */
-    Registry<Structure> STRUCTURE = Objects.requireNonNull(Bukkit.getRegistry(Structure.class), "No registry present for Structure. This is a bug.");
+    @Deprecated(since = "1.20.6") // Paper
+    Registry<Structure> STRUCTURE = Objects.requireNonNull(io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(Structure.class), "No registry present for Structure. This is a bug."); // Paper
     /**
      * Server structure types.
      *
      * @see StructureType
      */
-    Registry<StructureType> STRUCTURE_TYPE = Objects.requireNonNull(Bukkit.getRegistry(StructureType.class), "No registry present for StructureType. This is a bug.");
+    Registry<StructureType> STRUCTURE_TYPE = Objects.requireNonNull(io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(io.papermc.paper.registry.RegistryKey.STRUCTURE_TYPE), "No registry present for StructureType. This is a bug."); // Paper
     /**
      * Sound keys.
      *
      * @see Sound
      */
-    Registry<Sound> SOUNDS = Objects.requireNonNull(Bukkit.getRegistry(Sound.class), "No registry present for Sound. This is a bug.");
+    Registry<Sound> SOUNDS = io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(io.papermc.paper.registry.RegistryKey.SOUND_EVENT); // Paper
     /**
      * Trim materials.
      *
      * @see TrimMaterial
+     * @deprecated use {@link io.papermc.paper.registry.RegistryAccess#getRegistry(io.papermc.paper.registry.RegistryKey)} with {@link io.papermc.paper.registry.RegistryKey#TRIM_MATERIAL}
      */
-    Registry<TrimMaterial> TRIM_MATERIAL = Objects.requireNonNull(Bukkit.getRegistry(TrimMaterial.class), "No registry present for TrimMaterial. This is a bug.");
+    @Deprecated(since = "1.20.6") // Paper
+    Registry<TrimMaterial> TRIM_MATERIAL = Objects.requireNonNull(io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(TrimMaterial.class), "No registry present for TrimMaterial. This is a bug."); // Paper
     /**
      * Trim patterns.
      *
      * @see TrimPattern
+     * @deprecated use {@link io.papermc.paper.registry.RegistryAccess#getRegistry(io.papermc.paper.registry.RegistryKey)} with {@link io.papermc.paper.registry.RegistryKey#TRIM_PATTERN}
      */
-    Registry<TrimPattern> TRIM_PATTERN = Objects.requireNonNull(Bukkit.getRegistry(TrimPattern.class), "No registry present for TrimPattern. This is a bug.");
+    @Deprecated(since = "1.20.6")
+    Registry<TrimPattern> TRIM_PATTERN = Objects.requireNonNull(io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(TrimPattern.class), "No registry present for TrimPattern. This is a bug."); // Paper
     /**
      * Damage types.
      *
      * @see DamageType
+     * @deprecated use {@link io.papermc.paper.registry.RegistryAccess#getRegistry(io.papermc.paper.registry.RegistryKey)} with {@link io.papermc.paper.registry.RegistryKey#DAMAGE_TYPE}
      */
-    @ApiStatus.Experimental
-    Registry<DamageType> DAMAGE_TYPE = Objects.requireNonNull(Bukkit.getRegistry(DamageType.class), "No registry present for DamageType. This is a bug.");
+    @Deprecated(since = "1.20.6")
+    Registry<DamageType> DAMAGE_TYPE = Objects.requireNonNull(io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(DamageType.class), "No registry present for DamageType. This is a bug."); // Paper
     /**
      * Jukebox songs.
      *
      * @see JukeboxSong
+     * @deprecated use {@link io.papermc.paper.registry.RegistryAccess#getRegistry(io.papermc.paper.registry.RegistryKey)} with {@link io.papermc.paper.registry.RegistryKey#JUKEBOX_SONG}
      */
     @ApiStatus.Experimental
-    Registry<JukeboxSong> JUKEBOX_SONG = Objects.requireNonNull(Bukkit.getRegistry(JukeboxSong.class), "No registry present for JukeboxSong. This is a bug.");
+    @Deprecated(since = "1.21")
+    Registry<JukeboxSong> JUKEBOX_SONG = Objects.requireNonNull(io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(JukeboxSong.class), "No registry present for JukeboxSong. This is a bug."); // Paper
     /**
      * Villager profession.
      *
      * @see Villager.Profession
      */
-    Registry<Villager.Profession> VILLAGER_PROFESSION = Objects.requireNonNull(Bukkit.getRegistry(Villager.Profession.class), "No registry present for Villager Profession. This is a bug.");
+    Registry<Villager.Profession> VILLAGER_PROFESSION = io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(io.papermc.paper.registry.RegistryKey.VILLAGER_PROFESSION); // Paper
     /**
      * Villager type.
      *
      * @see Villager.Type
      */
-    Registry<Villager.Type> VILLAGER_TYPE = Objects.requireNonNull(Bukkit.getRegistry(Villager.Type.class), "No registry present for Villager Type. This is a bug.");
+    Registry<Villager.Type> VILLAGER_TYPE = io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(io.papermc.paper.registry.RegistryKey.VILLAGER_TYPE); // Paper
     /**
      * Memory Keys.
      *
@@ -0,0 +0,0 @@ public interface Registry<T extends Keyed> extends Iterable<T> {
      *
      * @see Fluid
      */
-    Registry<Fluid> FLUID = Objects.requireNonNull(Bukkit.getRegistry(Fluid.class), "No registry present for Fluid. This is a bug.");
+    Registry<Fluid> FLUID = io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(io.papermc.paper.registry.RegistryKey.FLUID); // Paper
     /**
      * Frog variants.
      *
      * @see Frog.Variant
      */
-    Registry<Frog.Variant> FROG_VARIANT = Objects.requireNonNull(Bukkit.getRegistry(Frog.Variant.class), "No registry present for Frog Variant. This is a bug.");
+    Registry<Frog.Variant> FROG_VARIANT = io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(io.papermc.paper.registry.RegistryKey.FROG_VARIANT); // Paper
     /**
      * Wolf variants.
      *
      * @see Wolf.Variant
+     * @deprecated use {@link io.papermc.paper.registry.RegistryAccess#getRegistry(io.papermc.paper.registry.RegistryKey)} with {@link io.papermc.paper.registry.RegistryKey#WOLF_VARIANT}
      */
-    Registry<Wolf.Variant> WOLF_VARIANT = Objects.requireNonNull(Bukkit.getRegistry(Wolf.Variant.class), "No registry present for Wolf Variant. This is a bug.");
+    @Deprecated(since = "1.20.6")
+    Registry<Wolf.Variant> WOLF_VARIANT = Objects.requireNonNull(io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(Wolf.Variant.class), "No registry present for Wolf$Variant. This is a bug."); // Paper
     /**
      * Map cursor types.
      *
      * @see MapCursor.Type
      */
-    Registry<MapCursor.Type> MAP_DECORATION_TYPE = Objects.requireNonNull(Bukkit.getRegistry(MapCursor.Type.class), "No registry present for MapCursor Type. This is a bug.");
+    Registry<MapCursor.Type> MAP_DECORATION_TYPE = io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(io.papermc.paper.registry.RegistryKey.MAP_DECORATION_TYPE); // Paper
     /**
      * Game events.
      *
      * @see GameEvent
      */
-    Registry<GameEvent> GAME_EVENT = Objects.requireNonNull(Bukkit.getRegistry(GameEvent.class), "No registry present for GameEvent. This is a bug.");
+    Registry<GameEvent> GAME_EVENT = io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(io.papermc.paper.registry.RegistryKey.GAME_EVENT); // Paper
     /**
      * Get the object by its key.
      *
@@ -0,0 +0,0 @@ public interface Registry<T extends Keyed> extends Iterable<T> {
         return (namespacedKey != null) ? get(namespacedKey) : null;
     }
 
-    static final class SimpleRegistry<T extends Enum<T> & Keyed> implements Registry<T> {
+    class SimpleRegistry<T extends Enum<T> & Keyed> implements Registry<T> { // Paper - remove final
 
         private final Class<T> type;
         private final Map<NamespacedKey, T> map;
diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/Server.java
+++ b/src/main/java/org/bukkit/Server.java
@@ -0,0 +0,0 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi
      * @param tClass of the registry to get
      * @param <T> type of the registry
      * @return the corresponding registry or null if not present
+     * @deprecated use {@link io.papermc.paper.registry.RegistryAccess#getRegistry(io.papermc.paper.registry.RegistryKey)}
+     * with keys from {@link io.papermc.paper.registry.RegistryKey}
      */
     @Nullable
+    @Deprecated(since = "1.20.6") // Paper
     <T extends Keyed> Registry<T> getRegistry(@NotNull Class<T> tClass);
 
     /**
diff --git a/src/test/java/io/papermc/paper/registry/TestRegistryAccess.java b/src/test/java/io/papermc/paper/registry/TestRegistryAccess.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/test/java/io/papermc/paper/registry/TestRegistryAccess.java
@@ -0,0 +0,0 @@
+package io.papermc.paper.registry;
+
+import org.bukkit.Keyed;
+import org.bukkit.Registry;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public class TestRegistryAccess implements RegistryAccess {
+
+    @Override
+    @Deprecated(since = "1.20.6", forRemoval = true)
+    public @Nullable <T extends Keyed> Registry<T> getRegistry(final @NotNull Class<T> type) {
+        throw new UnsupportedOperationException("Not supported");
+    }
+
+    @Override
+    public @NotNull <T extends Keyed> Registry<T> getRegistry(final @NotNull RegistryKey<T> registryKey) {
+        throw new UnsupportedOperationException("Not supported");
+    }
+}
diff --git a/src/test/java/org/bukkit/support/TestServer.java b/src/test/java/org/bukkit/support/TestServer.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/test/java/org/bukkit/support/TestServer.java
+++ b/src/test/java/org/bukkit/support/TestServer.java
@@ -0,0 +0,0 @@ public final class TestServer {
 
         when(instance.getBukkitVersion()).thenReturn("BukkitVersion_" + TestServer.class.getPackage().getImplementationVersion());
 
-        Map<Class<? extends Keyed>, Registry<?>> registers = new HashMap<>();
-        when(instance.getRegistry(any())).then(invocationOnMock -> registers.computeIfAbsent(invocationOnMock.getArgument(0), aClass -> new Registry<>() {
-            private final Map<NamespacedKey, Keyed> cache = new HashMap<>();
-
-            @Override
-            public Keyed get(NamespacedKey key) {
-                Class<? extends Keyed> theClass;
-                // Some registries have extra Typed classes such as BlockType and ItemType.
-                // To avoid class cast exceptions during init mock the Typed class.
-                // To get the correct class, we just use the field type.
-                try {
-                    theClass = (Class<? extends Keyed>) aClass.getField(key.getKey().toUpperCase(Locale.ROOT).replace('.', '_')).getType();
-                } catch (ClassCastException | NoSuchFieldException e) {
-                    throw new RuntimeException(e);
-                }
-
-                return cache.computeIfAbsent(key, key2 -> mock(theClass, withSettings().stubOnly()));
-            }
-
-            @NotNull
-            @Override
-            public Keyed getOrThrow(@NotNull NamespacedKey key) {
-                Keyed keyed = get(key);
-
-                Preconditions.checkArgument(keyed != null, "No %s registry entry found for key %s.", aClass, key);
-
-                return keyed;
-            }
-
-            @NotNull
-            @Override
-            public Stream<Keyed> stream() {
-                throw new UnsupportedOperationException("Not supported");
-            }
-
-            @Override
-            public Iterator<Keyed> iterator() {
-                throw new UnsupportedOperationException("Not supported");
-            }
-        }));
+        // Paper start - RegistryAccess
+        when(instance.getRegistry(any())).then(invocationOnMock -> {
+            return io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(((Class<Keyed>)invocationOnMock.getArgument(0)));
+        });
+        // Paper end - RegistryAccess
 
         UnsafeValues unsafeValues = mock(withSettings().stubOnly());
         when(instance.getUnsafe()).thenReturn(unsafeValues);
diff --git a/src/test/resources/META-INF/services/io.papermc.paper.registry.RegistryAccess b/src/test/resources/META-INF/services/io.papermc.paper.registry.RegistryAccess
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/test/resources/META-INF/services/io.papermc.paper.registry.RegistryAccess
@@ -0,0 +1 @@
+io.papermc.paper.registry.TestRegistryAccess