aboutsummaryrefslogtreecommitdiffhomepage
path: root/patches/server
diff options
context:
space:
mode:
Diffstat (limited to 'patches/server')
-rw-r--r--patches/server/0464-Remove-stale-POIs.patch22
-rw-r--r--patches/server/0465-Fix-villager-boat-exploit.patch25
-rw-r--r--patches/server/0466-Add-sendOpLevel-API.patch53
-rw-r--r--patches/server/0467-Add-RegistryAccess-for-managing-Registries.patch1338
-rw-r--r--patches/server/0468-Add-StructuresLocateEvent.patch36
-rw-r--r--patches/server/0469-Collision-option-for-requiring-a-player-participant.patch42
-rw-r--r--patches/server/0470-Return-chat-component-with-empty-text-instead-of-thr.patch25
-rw-r--r--patches/server/0471-Make-schedule-command-per-world.patch28
-rw-r--r--patches/server/0472-Configurable-max-leash-distance.patch32
-rw-r--r--patches/server/0473-Add-BlockPreDispenseEvent.patch46
-rw-r--r--patches/server/0474-Add-PlayerChangeBeaconEffectEvent.patch38
-rw-r--r--patches/server/0475-Add-toggle-for-always-placing-the-dragon-egg.patch19
-rw-r--r--patches/server/0476-Add-PlayerStonecutterRecipeSelectEvent.patch57
-rw-r--r--patches/server/0477-Expand-EntityUnleashEvent.patch157
-rw-r--r--patches/server/0478-Reset-shield-blocking-on-dimension-change.patch22
-rw-r--r--patches/server/0479-Add-DragonEggFormEvent.patch34
-rw-r--r--patches/server/0480-Add-EntityMoveEvent.patch55
-rw-r--r--patches/server/0481-added-option-to-disable-pathfinding-updates-on-block.patch26
-rw-r--r--patches/server/0482-Inline-shift-direction-fields.patch56
-rw-r--r--patches/server/0483-Allow-adding-items-to-BlockDropItemEvent.patch44
-rw-r--r--patches/server/0484-Add-getMainThreadExecutor-to-BukkitScheduler.patch26
-rw-r--r--patches/server/0485-living-entity-allow-attribute-registration.patch57
-rw-r--r--patches/server/0486-fix-dead-slime-setSize-invincibility.patch19
-rw-r--r--patches/server/0487-Merchant-getRecipes-should-return-an-immutable-list.patch19
-rw-r--r--patches/server/0488-Expose-Tracked-Players.patch32
-rw-r--r--patches/server/0489-Improve-ServerGUI.patch431
-rw-r--r--patches/server/0490-fix-converting-txt-to-json-file.patch61
-rw-r--r--patches/server/0491-Add-worldborder-events.patch72
-rw-r--r--patches/server/0492-Add-PlayerNameEntityEvent.patch26
-rw-r--r--patches/server/0493-Add-recipe-to-cook-events.patch44
-rw-r--r--patches/server/0494-Add-Block-isValidTool.patch20
-rw-r--r--patches/server/0495-Allow-using-signs-inside-spawn-protection.patch19
-rw-r--r--patches/server/0496-Expand-world-key-API.patch84
-rw-r--r--patches/server/0497-Add-fast-alternative-constructor-for-Rotations.patch29
-rw-r--r--patches/server/0498-Drop-carried-item-when-player-has-disconnected.patch27
-rw-r--r--patches/server/0499-forced-whitelist-use-configurable-kick-message.patch19
-rw-r--r--patches/server/0500-Don-t-ignore-result-of-PlayerEditBookEvent.patch19
-rw-r--r--patches/server/0501-Expose-protocol-version.patch22
-rw-r--r--patches/server/0502-Enhance-console-tab-completions-for-brigadier-comman.patch445
-rw-r--r--patches/server/0503-Fix-PlayerItemConsumeEvent-cancelling-properly.patch22
40 files changed, 3648 insertions, 0 deletions
diff --git a/patches/server/0464-Remove-stale-POIs.patch b/patches/server/0464-Remove-stale-POIs.patch
new file mode 100644
index 0000000000..70792fa6b3
--- /dev/null
+++ b/patches/server/0464-Remove-stale-POIs.patch
@@ -0,0 +1,22 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Shane Freeder <[email protected]>
+Date: Sat, 9 Jan 2021 14:17:07 +0100
+Subject: [PATCH] Remove stale POIs
+
+
+diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
+index a3ce6feff0826dcfb2a11b0f6cd01a5368c2de3a..d62d6a345837e1b63c1a1393f18e367ac0ef4c30 100644
+--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
+@@ -1805,6 +1805,11 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+ });
+ optional1.ifPresent((holder) -> {
+ this.getServer().execute(() -> {
++ // Paper start - Remove stale POIs
++ if (optional.isEmpty() && this.getPoiManager().exists(blockposition1, poiType -> true)) {
++ this.getPoiManager().remove(blockposition1);
++ }
++ // Paper end - Remove stale POIs
+ this.getPoiManager().add(blockposition1, holder);
+ DebugPackets.sendPoiAddedPacket(this, blockposition1);
+ });
diff --git a/patches/server/0465-Fix-villager-boat-exploit.patch b/patches/server/0465-Fix-villager-boat-exploit.patch
new file mode 100644
index 0000000000..f35fe8a11e
--- /dev/null
+++ b/patches/server/0465-Fix-villager-boat-exploit.patch
@@ -0,0 +1,25 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jason Penilla <[email protected]>
+Date: Mon, 11 Jan 2021 12:43:51 -0800
+Subject: [PATCH] Fix villager boat exploit
+
+
+diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
+index 4975b4222d52eddbb42e9c9cd08eef56859080c8..70b7871091ab9b64d2a5503620a71c3d5585c25d 100644
+--- a/src/main/java/net/minecraft/server/players/PlayerList.java
++++ b/src/main/java/net/minecraft/server/players/PlayerList.java
+@@ -545,6 +545,14 @@ public abstract class PlayerList {
+ PlayerList.LOGGER.debug("Removing player mount");
+ entityplayer.stopRiding();
+ entity.getPassengersAndSelf().forEach((entity1) -> {
++ // Paper start - Fix villager boat exploit
++ if (entity1 instanceof net.minecraft.world.entity.npc.AbstractVillager villager) {
++ final net.minecraft.world.entity.player.Player human = villager.getTradingPlayer();
++ if (human != null) {
++ villager.setTradingPlayer(null);
++ }
++ }
++ // Paper end - Fix villager boat exploit
+ entity1.setRemoved(Entity.RemovalReason.UNLOADED_WITH_PLAYER, EntityRemoveEvent.Cause.PLAYER_QUIT); // CraftBukkit - add Bukkit remove cause
+ });
+ }
diff --git a/patches/server/0466-Add-sendOpLevel-API.patch b/patches/server/0466-Add-sendOpLevel-API.patch
new file mode 100644
index 0000000000..e37d133769
--- /dev/null
+++ b/patches/server/0466-Add-sendOpLevel-API.patch
@@ -0,0 +1,53 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Mariell Hoversholm <[email protected]>
+Date: Tue, 29 Dec 2020 15:03:03 +0100
+Subject: [PATCH] Add sendOpLevel API
+
+
+diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
+index 70b7871091ab9b64d2a5503620a71c3d5585c25d..7676dbe55b4bf6e0472dc0190c01e6ecfcbb464e 100644
+--- a/src/main/java/net/minecraft/server/players/PlayerList.java
++++ b/src/main/java/net/minecraft/server/players/PlayerList.java
+@@ -1026,6 +1026,11 @@ public abstract class PlayerList {
+ }
+
+ private void sendPlayerPermissionLevel(ServerPlayer player, int permissionLevel) {
++ // Paper start - Add sendOpLevel API
++ this.sendPlayerPermissionLevel(player, permissionLevel, true);
++ }
++ public void sendPlayerPermissionLevel(ServerPlayer player, int permissionLevel, boolean recalculatePermissions) {
++ // Paper end - Add sendOpLevel API
+ if (player.connection != null) {
+ byte b0;
+
+@@ -1040,8 +1045,10 @@ public abstract class PlayerList {
+ player.connection.send(new ClientboundEntityEventPacket(player, b0));
+ }
+
++ if (recalculatePermissions) { // Paper - Add sendOpLevel API
+ player.getBukkitEntity().recalculatePermissions(); // CraftBukkit
+ this.server.getCommands().sendCommands(player);
++ } // Paper - Add sendOpLevel API
+ }
+
+ public boolean isWhiteListed(GameProfile profile) {
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
+index 786b8c10a647d777b951b760e4c51eef057b2edd..678099cdcf418b9e3eafed965384e6dcdd9404e3 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
+@@ -690,6 +690,15 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
+ }
+ // Paper end
+
++ // Paper start - Add sendOpLevel API
++ @Override
++ public void sendOpLevel(byte level) {
++ Preconditions.checkArgument(level >= 0 && level <= 4, "Level must be within [0, 4]");
++
++ this.getHandle().getServer().getPlayerList().sendPlayerPermissionLevel(this.getHandle(), level, false);
++ }
++ // Paper end - Add sendOpLevel API
++
+ @Override
+ public void setCompassTarget(Location loc) {
+ Preconditions.checkArgument(loc != null, "Location cannot be null");
diff --git a/patches/server/0467-Add-RegistryAccess-for-managing-Registries.patch b/patches/server/0467-Add-RegistryAccess-for-managing-Registries.patch
new file mode 100644
index 0000000000..2dfbc6b560
--- /dev/null
+++ b/patches/server/0467-Add-RegistryAccess-for-managing-Registries.patch
@@ -0,0 +1,1338 @@
+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] Add RegistryAccess for managing Registries
+
+RegistryAccess is independant from CraftServer and
+doesn't require one to be created allowing the
+org.bukkit.Registry class to be loaded earlier.
+
+== AT ==
+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..633b01431750d4b40159a57bf25fb35c6670ff1b
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/PaperRegistries.java
+@@ -0,0 +1,146 @@
++package io.papermc.paper.registry;
++
++import io.papermc.paper.adventure.PaperAdventure;
++import io.papermc.paper.registry.entry.RegistryEntry;
++import java.util.Collections;
++import java.util.IdentityHashMap;
++import java.util.List;
++import java.util.Map;
++import java.util.Objects;
++import net.minecraft.core.Registry;
++import net.minecraft.core.registries.Registries;
++import net.minecraft.resources.ResourceKey;
++import org.bukkit.GameEvent;
++import org.bukkit.JukeboxSong;
++import org.bukkit.Keyed;
++import org.bukkit.MusicInstrument;
++import org.bukkit.block.BlockType;
++import org.bukkit.block.banner.PatternType;
++import org.bukkit.craftbukkit.CraftGameEvent;
++import org.bukkit.craftbukkit.CraftJukeboxSong;
++import org.bukkit.craftbukkit.CraftMusicInstrument;
++import org.bukkit.craftbukkit.block.CraftBlockType;
++import org.bukkit.craftbukkit.block.banner.CraftPatternType;
++import org.bukkit.craftbukkit.damage.CraftDamageType;
++import org.bukkit.craftbukkit.enchantments.CraftEnchantment;
++import org.bukkit.craftbukkit.entity.CraftCat;
++import org.bukkit.craftbukkit.entity.CraftFrog;
++import org.bukkit.craftbukkit.entity.CraftVillager;
++import org.bukkit.craftbukkit.entity.CraftWolf;
++import org.bukkit.craftbukkit.generator.structure.CraftStructure;
++import org.bukkit.craftbukkit.generator.structure.CraftStructureType;
++import org.bukkit.craftbukkit.inventory.CraftItemType;
++import org.bukkit.craftbukkit.inventory.CraftMenuType;
++import org.bukkit.craftbukkit.inventory.trim.CraftTrimMaterial;
++import org.bukkit.craftbukkit.inventory.trim.CraftTrimPattern;
++import org.bukkit.craftbukkit.legacy.FieldRename;
++import org.bukkit.craftbukkit.map.CraftMapCursor;
++import org.bukkit.craftbukkit.potion.CraftPotionEffectType;
++import org.bukkit.craftbukkit.util.CraftNamespacedKey;
++import org.bukkit.damage.DamageType;
++import org.bukkit.enchantments.Enchantment;
++import org.bukkit.entity.Cat;
++import org.bukkit.entity.Frog;
++import org.bukkit.entity.Villager;
++import org.bukkit.entity.Wolf;
++import org.bukkit.entity.memory.MemoryKey;
++import org.bukkit.generator.structure.Structure;
++import org.bukkit.generator.structure.StructureType;
++import org.bukkit.inventory.ItemType;
++import org.bukkit.inventory.MenuType;
++import org.bukkit.inventory.meta.trim.TrimMaterial;
++import org.bukkit.inventory.meta.trim.TrimPattern;
++import org.bukkit.map.MapCursor;
++import org.bukkit.potion.PotionEffectType;
++import org.checkerframework.checker.nullness.qual.NonNull;
++import org.checkerframework.checker.nullness.qual.Nullable;
++import org.checkerframework.framework.qual.DefaultQualifier;
++
++import static io.papermc.paper.registry.entry.RegistryEntry.apiOnly;
++import static io.papermc.paper.registry.entry.RegistryEntry.entry;
++
++@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 {
++ REGISTRY_ENTRIES = List.of(
++ // built-ins
++ entry(Registries.GAME_EVENT, RegistryKey.GAME_EVENT, GameEvent.class, CraftGameEvent::new),
++ entry(Registries.STRUCTURE_TYPE, RegistryKey.STRUCTURE_TYPE, StructureType.class, CraftStructureType::new),
++ entry(Registries.INSTRUMENT, RegistryKey.INSTRUMENT, MusicInstrument.class, CraftMusicInstrument::new),
++ entry(Registries.MOB_EFFECT, RegistryKey.MOB_EFFECT, PotionEffectType.class, CraftPotionEffectType::new),
++ entry(Registries.BLOCK, RegistryKey.BLOCK, BlockType.class, CraftBlockType::new),
++ entry(Registries.ITEM, RegistryKey.ITEM, ItemType.class, CraftItemType::new),
++ entry(Registries.CAT_VARIANT, RegistryKey.CAT_VARIANT, Cat.Type.class, CraftCat.CraftType::new),
++ entry(Registries.FROG_VARIANT, RegistryKey.FROG_VARIANT, Frog.Variant.class, CraftFrog.CraftVariant::new),
++ entry(Registries.VILLAGER_PROFESSION, RegistryKey.VILLAGER_PROFESSION, Villager.Profession.class, CraftVillager.CraftProfession::new),
++ entry(Registries.VILLAGER_TYPE, RegistryKey.VILLAGER_TYPE, Villager.Type.class, CraftVillager.CraftType::new),
++ entry(Registries.MAP_DECORATION_TYPE, RegistryKey.MAP_DECORATION_TYPE, MapCursor.Type.class, CraftMapCursor.CraftType::new),
++ entry(Registries.MENU, RegistryKey.MENU, MenuType.class, CraftMenuType::new),
++
++ // data-drivens
++ entry(Registries.STRUCTURE, RegistryKey.STRUCTURE, Structure.class, CraftStructure::new).delayed(),
++ entry(Registries.TRIM_MATERIAL, RegistryKey.TRIM_MATERIAL, TrimMaterial.class, CraftTrimMaterial::new).delayed(),
++ entry(Registries.TRIM_PATTERN, RegistryKey.TRIM_PATTERN, TrimPattern.class, CraftTrimPattern::new).delayed(),
++ entry(Registries.DAMAGE_TYPE, RegistryKey.DAMAGE_TYPE, DamageType.class, CraftDamageType::new).delayed(),
++ entry(Registries.WOLF_VARIANT, RegistryKey.WOLF_VARIANT, Wolf.Variant.class, CraftWolf.CraftVariant::new).delayed(),
++ entry(Registries.ENCHANTMENT, RegistryKey.ENCHANTMENT, Enchantment.class, CraftEnchantment::new).withSerializationUpdater(FieldRename.ENCHANTMENT_RENAME).delayed(),
++ entry(Registries.JUKEBOX_SONG, RegistryKey.JUKEBOX_SONG, JukeboxSong.class, CraftJukeboxSong::new).delayed(),
++ entry(Registries.BANNER_PATTERN, RegistryKey.BANNER_PATTERN, PatternType.class, CraftPatternType::new).delayed(),
++
++ // api-only
++ apiOnly(Registries.BIOME, RegistryKey.BIOME, () -> org.bukkit.Registry.BIOME),
++ apiOnly(Registries.PAINTING_VARIANT, RegistryKey.PAINTING_VARIANT, () -> org.bukkit.Registry.ART),
++ apiOnly(Registries.ATTRIBUTE, RegistryKey.ATTRIBUTE, () -> org.bukkit.Registry.ATTRIBUTE),
++ apiOnly(Registries.ENTITY_TYPE, RegistryKey.ENTITY_TYPE, () -> org.bukkit.Registry.ENTITY_TYPE),
++ apiOnly(Registries.PARTICLE_TYPE, RegistryKey.PARTICLE_TYPE, () -> org.bukkit.Registry.PARTICLE_TYPE),
++ apiOnly(Registries.POTION, RegistryKey.POTION, () -> org.bukkit.Registry.POTION),
++ apiOnly(Registries.SOUND_EVENT, RegistryKey.SOUND_EVENT, () -> org.bukkit.Registry.SOUNDS),
++ apiOnly(Registries.MEMORY_MODULE_TYPE, RegistryKey.MEMORY_MODULE_TYPE, () -> (org.bukkit.Registry<MemoryKey<?>>) (org.bukkit.Registry) org.bukkit.Registry.MEMORY_MODULE_TYPE),
++ apiOnly(Registries.FLUID, RegistryKey.FLUID, () -> org.bukkit.Registry.FLUID)
++ );
++ 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);
++ }
++ BY_REGISTRY_KEY = Collections.unmodifiableMap(byRegistryKey);
++ BY_RESOURCE_KEY = Collections.unmodifiableMap(byResourceKey);
++ }
++
++ @SuppressWarnings("unchecked")
++ 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> @Nullable RegistryEntry<M, T> getEntry(final RegistryKey<? super T> registryKey) {
++ return (RegistryEntry<M, T>) BY_REGISTRY_KEY.get(registryKey);
++ }
++
++ @SuppressWarnings("unchecked")
++ public static <M, T> RegistryKey<T> registryFromNms(final ResourceKey<? extends Registry<M>> registryResourceKey) {
++ return (RegistryKey<T>) Objects.requireNonNull(BY_RESOURCE_KEY.get(registryResourceKey), registryResourceKey + " doesn't have an api RegistryKey").apiKey();
++ }
++
++ @SuppressWarnings("unchecked")
++ public static <M, T> ResourceKey<? extends Registry<M>> registryToNms(final RegistryKey<T> registryKey) {
++ return (ResourceKey<? extends Registry<M>>) Objects.requireNonNull(BY_REGISTRY_KEY.get(registryKey), registryKey + " doesn't have an mc registry ResourceKey").mcKey();
++ }
++
++ public static <M, T> TypedKey<T> fromNms(final ResourceKey<M> resourceKey) {
++ return TypedKey.create(registryFromNms(resourceKey.registryKey()), CraftNamespacedKey.fromMinecraft(resourceKey.location()));
++ }
++
++ @SuppressWarnings({"unchecked", "RedundantCast"})
++ public static <M, T> ResourceKey<M> toNms(final TypedKey<T> typedKey) {
++ return ResourceKey.create((ResourceKey<? extends Registry<M>>) PaperRegistries.registryToNms(typedKey.registryKey()), PaperAdventure.asVanilla(typedKey.key()));
++ }
++
++ private PaperRegistries() {
++ }
++}
+diff --git a/src/main/java/io/papermc/paper/registry/PaperRegistryAccess.java b/src/main/java/io/papermc/paper/registry/PaperRegistryAccess.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..35b6a7c5bac9640332a833bd3627f2bcb1bbf2f3
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/PaperRegistryAccess.java
+@@ -0,0 +1,127 @@
++package io.papermc.paper.registry;
++
++import io.papermc.paper.registry.entry.ApiRegistryEntry;
++import io.papermc.paper.registry.entry.RegistryEntry;
++import io.papermc.paper.registry.legacy.DelayedRegistry;
++import io.papermc.paper.registry.legacy.DelayedRegistryEntry;
++import io.papermc.paper.registry.legacy.LegacyRegistryIdentifiers;
++import java.util.Map;
++import java.util.NoSuchElementException;
++import java.util.Set;
++import java.util.concurrent.ConcurrentHashMap;
++import java.util.stream.Collectors;
++import net.minecraft.resources.ResourceKey;
++import org.bukkit.Keyed;
++import org.bukkit.Registry;
++import org.checkerframework.checker.nullness.qual.NonNull;
++import org.checkerframework.checker.nullness.qual.Nullable;
++import org.checkerframework.framework.qual.DefaultQualifier;
++import org.jetbrains.annotations.VisibleForTesting;
++
++import static java.util.Objects.requireNonNull;
++
++@DefaultQualifier(NonNull.class)
++public class PaperRegistryAccess implements RegistryAccess {
++
++ // We store the API registries in a memoized supplier, so they can be created on-demand.
++ // These suppliers are added to this map right after the instance of nms.Registry is created before it is loaded.
++ // We want to do registration there, so we have access to the nms.Registry instance in order to wrap it in a CraftRegistry instance.
++ // The memoized Supplier is needed because we *can't* instantiate any CraftRegistry class until **all** the BuiltInRegistries have been added
++ // to this map because that would class-load org.bukkit.Registry which would query this map.
++ private final Map<RegistryKey<?>, RegistryHolder<?>> registries = new ConcurrentHashMap<>(); // is "identity" because RegistryKey overrides equals and hashCode
++
++ public static PaperRegistryAccess instance() {
++ return (PaperRegistryAccess) RegistryAccessHolder.INSTANCE.orElseThrow(() -> new IllegalStateException("No RegistryAccess implementation found"));
++ }
++
++ @VisibleForTesting
++ public Set<RegistryKey<?>> getLoadedServerBackedRegistries() {
++ return this.registries.keySet().stream().filter(registryHolder -> !(PaperRegistries.getEntry(registryHolder) instanceof ApiRegistryEntry)).collect(Collectors.toUnmodifiableSet());
++ }
++
++ @SuppressWarnings("unchecked")
++ @Deprecated(forRemoval = true)
++ @Override
++ public <T extends Keyed> @Nullable Registry<T> getRegistry(final Class<T> type) {
++ final @Nullable RegistryKey<T> registryKey = byType(type);
++ // If our mapping from Class -> RegistryKey did not contain the passed type it was either a completely invalid type or a registry
++ // that merely exists as a SimpleRegistry in the org.bukkit.Registry type. We cannot return a registry for these, return null
++ // as per method contract in Bukkit#getRegistry.
++ if (registryKey == null) return null;
++
++ final @Nullable RegistryEntry<?, T> entry = PaperRegistries.getEntry(registryKey);
++ final @Nullable RegistryHolder<T> registry = (RegistryHolder<T>) this.registries.get(registryKey);
++ if (registry != null) {
++ // 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>) {
++ // 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.
++ final RegistryHolder<T> delayedHolder = new RegistryHolder.Delayed<>();
++ this.registries.put(registryKey, delayedHolder);
++ return delayedHolder.get();
++ } else {
++ // if the registry doesn't exist yet or doesn't have a delayed entry, just return null
++ return null;
++ }
++ }
++
++ @SuppressWarnings("unchecked")
++ @Override
++ public <T extends Keyed> Registry<T> getRegistry(final RegistryKey<T> key) {
++ if (PaperRegistries.getEntry(key) == null) {
++ throw new NoSuchElementException(key + " is not a valid registry key");
++ }
++ final @Nullable RegistryHolder<T> registryHolder = (RegistryHolder<T>) this.registries.get(key);
++ if (registryHolder == null) {
++ throw new IllegalArgumentException(key + " points to a registry that is not available yet");
++ }
++ // since this is the getRegistry method that uses the modern RegistryKey, we unwrap any DelayedRegistry instances
++ // that might be returned here. I don't think reference equality is required when doing getRegistry(RegistryKey.WOLF_VARIANT) == Registry.WOLF_VARIANT
++ return possiblyUnwrap(registryHolder.get());
++ }
++
++ 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();
++ }
++ return registry;
++ }
++
++ public <M> void registerReloadableRegistry(final ResourceKey<? extends net.minecraft.core.Registry<M>> resourceKey, final net.minecraft.core.Registry<M> registry) {
++ this.registerRegistry(resourceKey, registry, true);
++ }
++
++ public <M> void registerRegistry(final ResourceKey<? extends net.minecraft.core.Registry<M>> resourceKey, final net.minecraft.core.Registry<M> registry) {
++ this.registerRegistry(resourceKey, registry, false);
++ }
++
++ @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> entry = PaperRegistries.getEntry(resourceKey);
++ if (entry == null) { // skip registries that don't have API entries
++ return;
++ }
++ final @Nullable RegistryHolder<B> registryHolder = (RegistryHolder<B>) this.registries.get(entry.apiKey());
++ if (registryHolder == null || replace) {
++ // 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> 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 {
++ throw new IllegalArgumentException(resourceKey + " has already been created");
++ }
++ }
++ }
++
++ @SuppressWarnings("unchecked")
++ @Deprecated
++ @VisibleForTesting
++ public static <T extends Keyed> @Nullable RegistryKey<T> byType(final Class<T> type) {
++ return (RegistryKey<T>) LegacyRegistryIdentifiers.CLASS_TO_KEY_MAP.get(type);
++ }
++}
+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..a31bdd9f02fe75a87fceb2ebe8c36b3232a561cc
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/RegistryHolder.java
+@@ -0,0 +1,47 @@
++package io.papermc.paper.registry;
++
++import com.google.common.base.Suppliers;
++import io.papermc.paper.registry.legacy.DelayedRegistry;
++import io.papermc.paper.registry.legacy.DelayedRegistryEntry;
++import java.util.function.Supplier;
++import org.bukkit.Keyed;
++import org.bukkit.Registry;
++import org.checkerframework.checker.nullness.qual.NonNull;
++import org.checkerframework.framework.qual.DefaultQualifier;
++
++@DefaultQualifier(NonNull.class)
++public interface RegistryHolder<B extends Keyed> {
++
++ Registry<B> get();
++
++ final class Memoized<B extends Keyed, R extends Registry<B>> implements RegistryHolder<B> {
++
++ private final Supplier<R> memoizedSupplier;
++
++ public Memoized(final Supplier<? extends R> supplier) {
++ this.memoizedSupplier = Suppliers.memoize(supplier::get);
++ }
++
++ public Registry<B> get() {
++ return this.memoizedSupplier.get();
++ }
++ }
++
++ final class Delayed<B extends Keyed, R extends Registry<B>> implements RegistryHolder<B> {
++
++ private final DelayedRegistry<B, R> delayedRegistry = new DelayedRegistry<>();
++
++ @Override
++ public DelayedRegistry<B, R> get() {
++ return this.delayedRegistry;
++ }
++
++ <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");
++ }
++ this.delayedRegistry.load(((Memoized<B, R>) delegateHolder).memoizedSupplier);
++ }
++ }
++}
+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..2295b0d145cbaabef5d29482c817575dcbe2ba54
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/entry/ApiRegistryEntry.java
+@@ -0,0 +1,27 @@
++package io.papermc.paper.registry.entry;
++
++import io.papermc.paper.registry.RegistryHolder;
++import io.papermc.paper.registry.RegistryKey;
++import java.util.function.Supplier;
++import net.minecraft.core.Registry;
++import net.minecraft.resources.ResourceKey;
++import org.bukkit.Keyed;
++
++public class ApiRegistryEntry<M, B extends Keyed> extends BaseRegistryEntry<M, B> {
++
++ private final Supplier<org.bukkit.Registry<B>> registrySupplier;
++
++ protected ApiRegistryEntry(
++ final ResourceKey<? extends Registry<M>> mcKey,
++ final RegistryKey<B> apiKey,
++ final Supplier<org.bukkit.Registry<B>> registrySupplier
++ ) {
++ super(mcKey, apiKey);
++ this.registrySupplier = registrySupplier;
++ }
++
++ @Override
++ public RegistryHolder<B> createRegistryHolder(final Registry<M> nmsRegistry) {
++ return new RegistryHolder.Memoized<>(this.registrySupplier);
++ }
++}
+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..ceb217dbbb84e8bd51365dd47bf91971e364d298
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/entry/BaseRegistryEntry.java
+@@ -0,0 +1,27 @@
++package io.papermc.paper.registry.entry;
++
++import io.papermc.paper.registry.RegistryKey;
++import net.minecraft.core.Registry;
++import net.minecraft.resources.ResourceKey;
++import org.bukkit.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;
++
++ protected BaseRegistryEntry(final ResourceKey<? extends Registry<M>> minecraftRegistryKey, final RegistryKey<B> apiRegistryKey) {
++ this.minecraftRegistryKey = minecraftRegistryKey;
++ this.apiRegistryKey = apiRegistryKey;
++ }
++
++ @Override
++ public final ResourceKey<? extends Registry<M>> mcKey() {
++ return this.minecraftRegistryKey;
++ }
++
++ @Override
++ public final RegistryKey<B> apiKey() {
++ return this.apiRegistryKey;
++ }
++}
+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..9bb4aa926978f117901c9f99c45a6862a1d5ce30
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/entry/CraftRegistryEntry.java
+@@ -0,0 +1,51 @@
++package io.papermc.paper.registry.entry;
++
++import com.google.common.base.Preconditions;
++import io.papermc.paper.registry.RegistryHolder;
++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;
++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 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;
++ protected BiFunction<NamespacedKey, ApiVersion, NamespacedKey> updater = EMPTY;
++
++ protected CraftRegistryEntry(
++ final ResourceKey<? extends Registry<M>> mcKey,
++ final RegistryKey<B> apiKey,
++ final Class<?> classToPreload,
++ final BiFunction<NamespacedKey, M, B> minecraftToBukkit
++ ) {
++ super(mcKey, apiKey);
++ Preconditions.checkArgument(!classToPreload.getPackageName().startsWith("net.minecraft"), classToPreload + " should not be in the net.minecraft package as the class-to-preload");
++ this.classToPreload = classToPreload;
++ this.minecraftToBukkit = minecraftToBukkit;
++ }
++
++ @Override
++ public RegistryEntry<M, B> withSerializationUpdater(final BiFunction<NamespacedKey, ApiVersion, NamespacedKey> updater) {
++ this.updater = updater;
++ return this;
++ }
++
++ @Override
++ public RegistryHolder<B> createRegistryHolder(final Registry<M> nmsRegistry) {
++ return new RegistryHolder.Memoized<>(() -> this.createApiRegistry(nmsRegistry));
++ }
++
++ private CraftRegistry<B, M> createApiRegistry(final Registry<M> nmsRegistry) {
++ return new CraftRegistry<>(this.classToPreload, nmsRegistry, this.minecraftToBukkit, this.updater);
++ }
++}
+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..15991bf13894d850f360a520d1815711d25973ec
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/entry/RegistryEntry.java
+@@ -0,0 +1,51 @@
++package io.papermc.paper.registry.entry;
++
++import io.papermc.paper.registry.RegistryHolder;
++import io.papermc.paper.registry.RegistryKey;
++import io.papermc.paper.registry.legacy.DelayedRegistryEntry;
++import java.util.function.BiFunction;
++import java.util.function.Supplier;
++import net.minecraft.core.Registry;
++import net.minecraft.resources.ResourceKey;
++import org.bukkit.Keyed;
++import org.bukkit.NamespacedKey;
++import org.bukkit.craftbukkit.util.ApiVersion;
++import org.checkerframework.checker.nullness.qual.NonNull;
++import org.checkerframework.framework.qual.DefaultQualifier;
++
++@DefaultQualifier(NonNull.class)
++public interface RegistryEntry<M, B extends Keyed> extends RegistryEntryInfo<M, B> { // TODO remove Keyed
++
++ RegistryHolder<B> createRegistryHolder(Registry<M> nmsRegistry);
++
++ default RegistryEntry<M, B> withSerializationUpdater(final BiFunction<NamespacedKey, ApiVersion, NamespacedKey> updater) {
++ return this;
++ }
++
++ /**
++ * This should only be used if the registry instance needs to exist early due to the need
++ * to populate a field in {@link org.bukkit.Registry}. Data-driven registries shouldn't exist
++ * as fields, but instead be obtained via {@link io.papermc.paper.registry.RegistryAccess#getRegistry(RegistryKey)}
++ */
++ @Deprecated
++ default RegistryEntry<M, B> delayed() {
++ return new DelayedRegistryEntry<>(this);
++ }
++
++ static <M, B extends Keyed> RegistryEntry<M, B> entry(
++ final ResourceKey<? extends Registry<M>> mcKey,
++ final RegistryKey<B> apiKey,
++ final Class<?> classToPreload,
++ final BiFunction<NamespacedKey, M, B> minecraftToBukkit
++ ) {
++ return new CraftRegistryEntry<>(mcKey, apiKey, classToPreload, minecraftToBukkit);
++ }
++
++ 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
++ ) {
++ return new ApiRegistryEntry<>(mcKey, apiKey, apiRegistrySupplier);
++ }
++}
+diff --git a/src/main/java/io/papermc/paper/registry/entry/RegistryEntryInfo.java b/src/main/java/io/papermc/paper/registry/entry/RegistryEntryInfo.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0ae855e80fc9fddfc1feb33c7a9748204828b7cc
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/entry/RegistryEntryInfo.java
+@@ -0,0 +1,12 @@
++package io.papermc.paper.registry.entry;
++
++import io.papermc.paper.registry.RegistryKey;
++import net.minecraft.core.Registry;
++import net.minecraft.resources.ResourceKey;
++
++public interface RegistryEntryInfo<M, B> {
++
++ ResourceKey<? extends Registry<M>> mcKey();
++
++ RegistryKey<B> apiKey();
++}
+diff --git a/src/main/java/io/papermc/paper/registry/entry/package-info.java b/src/main/java/io/papermc/paper/registry/entry/package-info.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..e4c94d6860e0f5b643cde1ded20b5503c02a4866
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/entry/package-info.java
+@@ -0,0 +1,5 @@
++@DefaultQualifier(NonNull.class)
++package io.papermc.paper.registry.entry;
++
++import org.checkerframework.checker.nullness.qual.NonNull;
++import org.checkerframework.framework.qual.DefaultQualifier;
+diff --git a/src/main/java/io/papermc/paper/registry/legacy/DelayedRegistry.java b/src/main/java/io/papermc/paper/registry/legacy/DelayedRegistry.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..9400fed345344a0a8e4fb301cca6a1867adf625b
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/legacy/DelayedRegistry.java
+@@ -0,0 +1,61 @@
++package io.papermc.paper.registry.legacy;
++
++import java.util.Iterator;
++import java.util.function.Supplier;
++import java.util.stream.Stream;
++import net.kyori.adventure.key.Key;
++import org.bukkit.Keyed;
++import org.bukkit.NamespacedKey;
++import org.bukkit.Registry;
++import org.bukkit.craftbukkit.CraftRegistry;
++import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
++import org.checkerframework.checker.nullness.qual.Nullable;
++import org.jetbrains.annotations.NotNull;
++
++/**
++ * This is to support the now-deprecated fields in {@link Registry} for
++ * data-driven registries.
++ */
++public final class DelayedRegistry<T extends Keyed, R extends Registry<T>> implements Registry<T> {
++
++ private @MonotonicNonNull Supplier<? extends R> delegate;
++
++ public void load(final Supplier<? extends R> registry) {
++ if (this.delegate != null) {
++ throw new IllegalStateException("Registry already loaded!");
++ }
++ this.delegate = registry;
++ }
++
++ public Registry<T> delegate() {
++ if (this.delegate == null) {
++ throw new IllegalStateException("You are trying to access this registry too early!");
++ }
++ return this.delegate.get();
++ }
++
++ @Override
++ public @Nullable T get(final NamespacedKey key) {
++ return this.delegate().get(key);
++ }
++
++ @Override
++ public @NotNull T getOrThrow(@NotNull final NamespacedKey key) {
++ return this.delegate().getOrThrow(key);
++ }
++
++ @Override
++ public Iterator<T> iterator() {
++ return this.delegate().iterator();
++ }
++
++ @Override
++ public Stream<T> stream() {
++ return this.delegate().stream();
++ }
++
++ @Override
++ public NamespacedKey getKey(final T value) {
++ return this.delegate().getKey(value);
++ }
++}
+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..110b8d559f49f9e4f181b47663962a139a273a72
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/legacy/DelayedRegistryEntry.java
+@@ -0,0 +1,26 @@
++package io.papermc.paper.registry.legacy;
++
++import io.papermc.paper.registry.RegistryHolder;
++import io.papermc.paper.registry.RegistryKey;
++import io.papermc.paper.registry.entry.RegistryEntry;
++import net.minecraft.core.Registry;
++import net.minecraft.resources.ResourceKey;
++import org.bukkit.Keyed;
++
++public record DelayedRegistryEntry<M, T extends Keyed>(RegistryEntry<M, T> delegate) implements RegistryEntry<M, T> {
++
++ @Override
++ public ResourceKey<? extends Registry<M>> mcKey() {
++ return this.delegate.mcKey();
++ }
++
++ @Override
++ public RegistryKey<T> apiKey() {
++ return this.delegate.apiKey();
++ }
++
++ @Override
++ public RegistryHolder<T> createRegistryHolder(final Registry<M> nmsRegistry) {
++ return this.delegate.createRegistryHolder(nmsRegistry);
++ }
++}
+diff --git a/src/main/java/io/papermc/paper/registry/legacy/LegacyRegistryIdentifiers.java b/src/main/java/io/papermc/paper/registry/legacy/LegacyRegistryIdentifiers.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..83870816cd4c54f94a3c603ffe41c11e457f3dec
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/legacy/LegacyRegistryIdentifiers.java
+@@ -0,0 +1,33 @@
++package io.papermc.paper.registry.legacy;
++
++import com.google.common.collect.ImmutableMap;
++import io.leangen.geantyref.GenericTypeReflector;
++import io.papermc.paper.registry.RegistryKey;
++import java.lang.reflect.Field;
++import java.lang.reflect.ParameterizedType;
++import java.util.Map;
++
++@Deprecated
++public final class LegacyRegistryIdentifiers {
++
++ public static final Map<Class<?>, RegistryKey<?>> CLASS_TO_KEY_MAP;
++
++ static {
++ final ImmutableMap.Builder<Class<?>, RegistryKey<?>> builder = ImmutableMap.builder();
++ try {
++ for (final Field field : RegistryKey.class.getFields()) {
++ if (field.getType() == RegistryKey.class) {
++ // get the legacy type from the RegistryKey generic parameter on the field
++ final Class<?> legacyType = GenericTypeReflector.erase(((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0]);
++ builder.put(legacyType, (RegistryKey<?>) field.get(null));
++ }
++ }
++ } catch (final ReflectiveOperationException ex) {
++ throw new RuntimeException(ex);
++ }
++ CLASS_TO_KEY_MAP = builder.build();
++ }
++
++ private LegacyRegistryIdentifiers() {
++ }
++}
+diff --git a/src/main/java/io/papermc/paper/registry/legacy/package-info.java b/src/main/java/io/papermc/paper/registry/legacy/package-info.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..4396982af55872fafbfeaf8161ad6f392726c773
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/legacy/package-info.java
+@@ -0,0 +1,5 @@
++@DefaultQualifier(NonNull.class)
++package io.papermc.paper.registry.legacy;
++
++import org.checkerframework.checker.nullness.qual.NonNull;
++import org.checkerframework.framework.qual.DefaultQualifier;
+diff --git a/src/main/java/net/minecraft/core/registries/BuiltInRegistries.java b/src/main/java/net/minecraft/core/registries/BuiltInRegistries.java
+index 3f72e30b57fb2a4231e22a2234729408c1240af4..4638ba98dbbdb0f880337347be85a6e0fbed2191 100644
+--- a/src/main/java/net/minecraft/core/registries/BuiltInRegistries.java
++++ b/src/main/java/net/minecraft/core/registries/BuiltInRegistries.java
+@@ -323,6 +323,7 @@ public class BuiltInRegistries {
+ ResourceKey<? extends Registry<T>> key, R registry, BuiltInRegistries.RegistryBootstrap<T> initializer
+ ) {
+ Bootstrap.checkBootstrapCalled(() -> "registry " + key.location());
++ io.papermc.paper.registry.PaperRegistryAccess.instance().registerRegistry(registry.key(), registry); // Paper - initialize API registry
+ ResourceLocation resourceLocation = key.location();
+ LOADERS.put(resourceLocation, () -> initializer.run(registry));
+ WRITABLE_REGISTRY.register((ResourceKey)key, registry, RegistrationInfo.BUILT_IN); // Paper - decompile fix
+diff --git a/src/main/java/net/minecraft/resources/RegistryDataLoader.java b/src/main/java/net/minecraft/resources/RegistryDataLoader.java
+index f4f1a99d53ffb953beb2a944f54d28fa6349fa29..144a6f5b0c53110804d6d099fe857d25f107d938 100644
+--- a/src/main/java/net/minecraft/resources/RegistryDataLoader.java
++++ b/src/main/java/net/minecraft/resources/RegistryDataLoader.java
+@@ -351,6 +351,7 @@ public class RegistryDataLoader {
+
+ RegistryDataLoader.Loader<T> create(Lifecycle lifecycle, Map<ResourceKey<?>, Exception> errors) {
+ WritableRegistry<T> writableRegistry = new MappedRegistry<>(this.key, lifecycle);
++ io.papermc.paper.registry.PaperRegistryAccess.instance().registerRegistry(this.key, writableRegistry); // Paper - initialize API registry
+ return new RegistryDataLoader.Loader<>(this, writableRegistry, errors);
+ }
+
+diff --git a/src/main/java/net/minecraft/server/ReloadableServerRegistries.java b/src/main/java/net/minecraft/server/ReloadableServerRegistries.java
+index 8e8d6214adbd21a221147f0fc0d91cd9c06a080c..6fddef967b6314ca0158f5bd4b8898670ea5e9ec 100644
+--- a/src/main/java/net/minecraft/server/ReloadableServerRegistries.java
++++ b/src/main/java/net/minecraft/server/ReloadableServerRegistries.java
+@@ -64,6 +64,7 @@ public class ReloadableServerRegistries {
+ ) {
+ return CompletableFuture.supplyAsync(() -> {
+ WritableRegistry<T> writableRegistry = new MappedRegistry<>(type.registryKey(), Lifecycle.experimental());
++ io.papermc.paper.registry.PaperRegistryAccess.instance().registerReloadableRegistry(type.registryKey(), writableRegistry); // Paper - register reloadable registry
+ Map<ResourceLocation, T> map = new HashMap<>();
+ String string = Registries.elementsDirPath(type.registryKey());
+ SimpleJsonResourceReloadListener.scanDirectory(resourceManager, string, ops, type.codec(), map);
+diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java b/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java
+index b4ed857f2437759b71b75d7ab36c986a2fd71dbc..09929f580164abcd1c04061d04c6aa992767e256 100644
+--- a/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java
++++ b/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java
+@@ -122,81 +122,12 @@ public class CraftRegistry<B extends Keyed, M> implements Registry<B> {
+ + ", this can happen if a plugin creates its own registry entry with out properly registering it.");
+ }
+
+- /**
+- * Note: Newly added registries should also be added to RegistriesArgumentProvider in the test package
+- *
+- * @param bukkitClass the bukkit class of the registry
+- * @param registryHolder the minecraft registry holder
+- * @return the bukkit registry of the provided class
+- */
+- public static <B extends Keyed> Registry<?> createRegistry(Class<? super B> bukkitClass, RegistryAccess registryHolder) {
+- if (bukkitClass == Enchantment.class) {
+- return new CraftRegistry<>(Enchantment.class, registryHolder.lookupOrThrow(Registries.ENCHANTMENT), CraftEnchantment::new, FieldRename.ENCHANTMENT_RENAME);
+- }
+- if (bukkitClass == GameEvent.class) {
+- return new CraftRegistry<>(GameEvent.class, registryHolder.lookupOrThrow(Registries.GAME_EVENT), CraftGameEvent::new, FieldRename.NONE);
+- }
+- if (bukkitClass == MusicInstrument.class) {
+- return new CraftRegistry<>(MusicInstrument.class, registryHolder.lookupOrThrow(Registries.INSTRUMENT), CraftMusicInstrument::new, FieldRename.NONE);
+- }
+- if (bukkitClass == MenuType.class) {
+- return new CraftRegistry<>(MenuType.class, registryHolder.lookupOrThrow(Registries.MENU), CraftMenuType::new, FieldRename.NONE);
+- }
+- if (bukkitClass == PotionEffectType.class) {
+- return new CraftRegistry<>(PotionEffectType.class, registryHolder.lookupOrThrow(Registries.MOB_EFFECT), CraftPotionEffectType::new, FieldRename.NONE);
+- }
+- if (bukkitClass == Structure.class) {
+- return new CraftRegistry<>(Structure.class, registryHolder.lookupOrThrow(Registries.STRUCTURE), CraftStructure::new, FieldRename.NONE);
+- }
+- if (bukkitClass == StructureType.class) {
+- return new CraftRegistry<>(StructureType.class, registryHolder.lookupOrThrow(Registries.STRUCTURE_TYPE), CraftStructureType::new, FieldRename.NONE);
+- }
+- if (bukkitClass == Villager.Type.class) {
+- return new CraftRegistry<>(Villager.Type.class, registryHolder.lookupOrThrow(Registries.VILLAGER_TYPE), CraftVillager.CraftType::new, FieldRename.NONE);
+- }
+- if (bukkitClass == Villager.Profession.class) {
+- return new CraftRegistry<>(Villager.Profession.class, registryHolder.lookupOrThrow(Registries.VILLAGER_PROFESSION), CraftVillager.CraftProfession::new, FieldRename.NONE);
+- }
+- if (bukkitClass == TrimMaterial.class) {
+- return new CraftRegistry<>(TrimMaterial.class, registryHolder.lookupOrThrow(Registries.TRIM_MATERIAL), CraftTrimMaterial::new, FieldRename.NONE);
+- }
+- if (bukkitClass == TrimPattern.class) {
+- return new CraftRegistry<>(TrimPattern.class, registryHolder.lookupOrThrow(Registries.TRIM_PATTERN), CraftTrimPattern::new, FieldRename.NONE);
+- }
+- if (bukkitClass == DamageType.class) {
+- return new CraftRegistry<>(DamageType.class, registryHolder.lookupOrThrow(Registries.DAMAGE_TYPE), CraftDamageType::new, FieldRename.NONE);
+- }
+- if (bukkitClass == JukeboxSong.class) {
+- return new CraftRegistry<>(JukeboxSong.class, registryHolder.lookupOrThrow(Registries.JUKEBOX_SONG), CraftJukeboxSong::new, FieldRename.NONE);
+- }
+- if (bukkitClass == Wolf.Variant.class) {
+- return new CraftRegistry<>(Wolf.Variant.class, registryHolder.lookupOrThrow(Registries.WOLF_VARIANT), CraftWolf.CraftVariant::new, FieldRename.NONE);
+- }
+- if (bukkitClass == BlockType.class) {
+- return new CraftRegistry<>(BlockType.class, registryHolder.lookupOrThrow(Registries.BLOCK), CraftBlockType::new, FieldRename.NONE);
+- }
+- if (bukkitClass == ItemType.class) {
+- return new CraftRegistry<>(ItemType.class, registryHolder.lookupOrThrow(Registries.ITEM), CraftItemType::new, FieldRename.NONE);
+- }
+- if (bukkitClass == Frog.Variant.class) {
+- return new CraftRegistry<>(Frog.Variant.class, registryHolder.lookupOrThrow(Registries.FROG_VARIANT), CraftFrog.CraftVariant::new, FieldRename.NONE);
+- }
+- if (bukkitClass == Cat.Type.class) {
+- return new CraftRegistry<>(Cat.Type.class, registryHolder.lookupOrThrow(Registries.CAT_VARIANT), CraftCat.CraftType::new, FieldRename.NONE);
+- }
+- if (bukkitClass == MapCursor.Type.class) {
+- return new CraftRegistry<>(MapCursor.Type.class, registryHolder.lookupOrThrow(Registries.MAP_DECORATION_TYPE), CraftMapCursor.CraftType::new, FieldRename.NONE);
+- }
+- if (bukkitClass == PatternType.class) {
+- return new CraftRegistry<>(PatternType.class, registryHolder.lookupOrThrow(Registries.BANNER_PATTERN), CraftPatternType::new, FieldRename.NONE);
+- }
+-
+- return null;
+- }
++ // Paper - move to PaperRegistries
+
++ // Paper - NOTE: As long as all uses of the method below relate to *serialization* via ConfigurationSerializable, it's fine
+ public static <B extends Keyed> B get(Registry<B> bukkit, NamespacedKey namespacedKey, ApiVersion apiVersion) {
+ if (bukkit instanceof CraftRegistry<B, ?> craft) {
+- return craft.get(namespacedKey, apiVersion);
++ return craft.get(craft.serializationUpdater.apply(namespacedKey, apiVersion)); // Paper
+ }
+
+ if (bukkit instanceof Registry.SimpleRegistry<?> simple) {
+@@ -222,23 +153,21 @@ public class CraftRegistry<B extends Keyed, M> implements Registry<B> {
+ return bukkit.get(namespacedKey);
+ }
+
+- private final Class<? super B> bukkitClass;
++ private final Class<?> bukkitClass; // Paper - relax preload class
+ private final Map<NamespacedKey, B> cache = new HashMap<>();
+ private final net.minecraft.core.Registry<M> minecraftRegistry;
+ private final BiFunction<NamespacedKey, M, B> minecraftToBukkit;
+- private final BiFunction<NamespacedKey, ApiVersion, NamespacedKey> updater;
++ private final BiFunction<NamespacedKey, ApiVersion, NamespacedKey> serializationUpdater; // Paper - rename to make it *clear* what it is *only* for
+ private boolean init;
+
+- public CraftRegistry(Class<? super B> bukkitClass, net.minecraft.core.Registry<M> minecraftRegistry, BiFunction<NamespacedKey, M, B> minecraftToBukkit, BiFunction<NamespacedKey, ApiVersion, NamespacedKey> updater) {
++ public CraftRegistry(Class<?> bukkitClass, net.minecraft.core.Registry<M> minecraftRegistry, BiFunction<NamespacedKey, M, B> minecraftToBukkit, BiFunction<NamespacedKey, ApiVersion, NamespacedKey> serializationUpdater) { // Paper - relax preload class
+ this.bukkitClass = bukkitClass;
+ this.minecraftRegistry = minecraftRegistry;
+ this.minecraftToBukkit = minecraftToBukkit;
+- this.updater = updater;
++ this.serializationUpdater = serializationUpdater;
+ }
+
+- public B get(NamespacedKey namespacedKey, ApiVersion apiVersion) {
+- return this.get(this.updater.apply(namespacedKey, apiVersion));
+- }
++ // Paper - inline into CraftRegistry#get(Registry, NamespacedKey, ApiVersion) above
+
+ @Override
+ public B get(NamespacedKey namespacedKey) {
+diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+index dd3940c2aacfa835b528a882f3ec5dd4d98bd59f..ee231d93d216571a45b11b49663b2ea91c47a1c7 100644
+--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+@@ -284,7 +284,7 @@ public final class CraftServer implements Server {
+ protected final DedicatedServer console;
+ protected final DedicatedPlayerList playerList;
+ private final Map<String, World> worlds = new LinkedHashMap<String, World>();
+- private final Map<Class<?>, Registry<?>> registries = new HashMap<>();
++ // private final Map<Class<?>, Registry<?>> registries = new HashMap<>(); // Paper - replace with RegistryAccess
+ private YamlConfiguration configuration;
+ private YamlConfiguration commandsConfiguration;
+ private final Yaml yaml = new Yaml(new SafeConstructor(new LoaderOptions()));
+@@ -432,6 +432,7 @@ public final class CraftServer implements Server {
+ }
+
+ private void loadCompatibilities() {
++ if (true) return; // Paper - Big nope
+ ConfigurationSection compatibilities = this.configuration.getConfigurationSection("settings.compatibility");
+ if (compatibilities == null) {
+ this.activeCompatibilities = Collections.emptySet();
+@@ -2745,7 +2746,7 @@ public final class CraftServer implements Server {
+
+ @Override
+ public <T extends Keyed> Registry<T> getRegistry(Class<T> aClass) {
+- return (Registry<T>) this.registries.computeIfAbsent(aClass, key -> CraftRegistry.createRegistry(aClass, this.console.registryAccess()));
++ return io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(aClass); // Paper - replace with RegistryAccess
+ }
+
+ @Deprecated
+diff --git a/src/main/java/org/bukkit/craftbukkit/legacy/FieldRename.java b/src/main/java/org/bukkit/craftbukkit/legacy/FieldRename.java
+index c1023eff9f391c07b57e57450b756fe16299f723..b5e0023e431f9fb43c93a3f977144b03545322bb 100644
+--- a/src/main/java/org/bukkit/craftbukkit/legacy/FieldRename.java
++++ b/src/main/java/org/bukkit/craftbukkit/legacy/FieldRename.java
+@@ -51,11 +51,14 @@ public class FieldRename {
+ };
+ }
+
+- @RequireCompatibility("allow-old-keys-in-registry")
+- public static <T extends Keyed> T get(Registry<T> registry, NamespacedKey namespacedKey) {
+- // We don't have version-specific changes, so just use current, and don't inject a version
+- return CraftRegistry.get(registry, namespacedKey, ApiVersion.CURRENT);
+- }
++ // Paper start - absolutely not, having this as an expectation for plugin developers opens a huge
++ // can of worms in the future, especially if mojang comes back and reuses some old key
++ //@RequireCompatibility("allow-old-keys-in-registry")
++ //public static <T extends Keyed> T get(Registry<T> registry, NamespacedKey namespacedKey) {
++ // // We don't have version-specific changes, so just use current, and don't inject a version
++ // return CraftRegistry.get(registry, namespacedKey, ApiVersion.CURRENT);
++ //}
++ // Paper end
+
+ // PatternType
+ private static final FieldRenameData PATTERN_TYPE_DATA = FieldRenameData.Builder.newBuilder()
+diff --git a/src/main/java/org/bukkit/craftbukkit/util/Commodore.java b/src/main/java/org/bukkit/craftbukkit/util/Commodore.java
+index b85223ebff4dbb8aa74b501663afc87ef11e2a96..760f56d36f0e4a74b58628408a286a499d6664ec 100644
+--- a/src/main/java/org/bukkit/craftbukkit/util/Commodore.java
++++ b/src/main/java/org/bukkit/craftbukkit/util/Commodore.java
+@@ -215,20 +215,10 @@ public class Commodore {
+
+ public byte[] convert(byte[] b, final String pluginName, final ApiVersion pluginVersion, final Set<String> activeCompatibilities) {
+ final boolean modern = pluginVersion.isNewerThanOrSameAs(ApiVersion.FLATTENING);
+- final boolean enumCompatibility = pluginVersion.isOlderThanOrSameAs(ApiVersion.getOrCreateVersion("1.20.6")) && activeCompatibilities.contains("enum-compatibility-mode");
+ ClassReader cr = new ClassReader(b);
+ ClassWriter cw = new ClassWriter(cr, 0);
+
+- List<String> methodEnumSignatures = Commodore.getMethodSignatures(b);
+- Multimap<String, String> enumLessToEnum = HashMultimap.create();
+- for (String method : methodEnumSignatures) {
+- enumLessToEnum.put(method.replace("Ljava/lang/Enum;", "Ljava/lang/Object;"), method);
+- }
+-
+ ClassVisitor visitor = cw;
+- if (enumCompatibility) {
+- visitor = new LimitedClassRemapper(cw, new SimpleRemapper(Commodore.ENUM_RENAMES));
+- }
+
+ visitor = io.papermc.paper.pluginremap.reflect.ReflectionRemapper.visitor(visitor); // Paper
+ cr.accept(new ClassRemapper(new ClassVisitor(Opcodes.ASM9, visitor) {
+@@ -295,15 +285,6 @@ public class Commodore {
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
+- if (enumCompatibility && (access & Opcodes.ACC_SYNTHETIC) != 0 && (access & Opcodes.ACC_BRIDGE) != 0 && desc.contains("Ljava/lang/Object;")) {
+- // SPIGOT-7820: Do not use object method if enum method is present
+- // The object method does only redirect to the enum method
+- Collection<String> result = enumLessToEnum.get(desc.replace("Ljava/lang/Enum;", "Ljava/lang/Object;") + " " + name);
+- if (result.size() == 2) {
+- name = name + "_BUKKIT_UNUSED";
+- }
+- }
+-
+ return new MethodVisitor(this.api, super.visitMethod(access, name, desc, signature, exceptions)) {
+ // Paper start - Plugin rewrites
+ @Override
+diff --git a/src/main/resources/META-INF/services/io.papermc.paper.registry.RegistryAccess b/src/main/resources/META-INF/services/io.papermc.paper.registry.RegistryAccess
+new file mode 100644
+index 0000000000000000000000000000000000000000..8a083d45004f82fc9c51c219fb20f34624adb501
+--- /dev/null
++++ b/src/main/resources/META-INF/services/io.papermc.paper.registry.RegistryAccess
+@@ -0,0 +1 @@
++io.papermc.paper.registry.PaperRegistryAccess
+diff --git a/src/main/resources/configurations/bukkit.yml b/src/main/resources/configurations/bukkit.yml
+index 543e37737bc6fdca23ed9ed0606805d345515a5a..eef7c125b2689f29cae5464659eacdf33f5695b2 100644
+--- a/src/main/resources/configurations/bukkit.yml
++++ b/src/main/resources/configurations/bukkit.yml
+@@ -23,9 +23,6 @@ settings:
+ shutdown-message: Server closed
+ minimum-api: none
+ use-map-color-cache: true
+- compatibility:
+- allow-old-keys-in-registry: false
+- enum-compatibility-mode: false
+ spawn-limits:
+ monsters: 70
+ animals: 10
+diff --git a/src/test/java/io/papermc/paper/registry/LegacyRegistryIdentifierTest.java b/src/test/java/io/papermc/paper/registry/LegacyRegistryIdentifierTest.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..a80b0ded74c0be657e734de61cbf5e32e16c26a8
+--- /dev/null
++++ b/src/test/java/io/papermc/paper/registry/LegacyRegistryIdentifierTest.java
+@@ -0,0 +1,21 @@
++package io.papermc.paper.registry;
++
++import org.bukkit.GameEvent;
++import org.bukkit.MusicInstrument;
++import org.bukkit.inventory.meta.trim.TrimPattern;
++import org.bukkit.support.environment.VanillaFeature;
++import org.junit.jupiter.api.Test;
++
++import static org.junit.jupiter.api.Assertions.assertSame;
++
++@Deprecated
++@VanillaFeature
++class LegacyRegistryIdentifierTest {
++
++ @Test
++ void testSeveralConversions() {
++ assertSame(RegistryKey.GAME_EVENT, PaperRegistryAccess.byType(GameEvent.class));
++ assertSame(RegistryKey.TRIM_PATTERN, PaperRegistryAccess.byType(TrimPattern.class));
++ assertSame(RegistryKey.INSTRUMENT, PaperRegistryAccess.byType(MusicInstrument.class));
++ }
++}
+diff --git a/src/test/java/io/papermc/paper/registry/RegistryKeyTest.java b/src/test/java/io/papermc/paper/registry/RegistryKeyTest.java
+index fe52d229c31526cc32f6422328efe92edf75a7ff..67dadb1765a5ef9a391a459224e233f38201f5d5 100644
+--- a/src/test/java/io/papermc/paper/registry/RegistryKeyTest.java
++++ b/src/test/java/io/papermc/paper/registry/RegistryKeyTest.java
+@@ -1,15 +1,19 @@
+ package io.papermc.paper.registry;
+
++import io.papermc.paper.registry.entry.RegistryEntry;
+ import java.util.Optional;
+ import java.util.stream.Stream;
+ import net.minecraft.core.Registry;
+ import net.minecraft.resources.ResourceKey;
+ import net.minecraft.resources.ResourceLocation;
+-import org.bukkit.support.AbstractTestingBase;
++import org.bukkit.support.RegistryHelper;
++import org.bukkit.support.environment.AllFeatures;
++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;
+
++import static org.junit.jupiter.api.Assertions.assertNotNull;
+ import static org.junit.jupiter.api.Assertions.assertTrue;
+
+ @AllFeatures
+@@ -29,6 +33,12 @@ class RegistryKeyTest {
+ void testApiRegistryKeysExist(final RegistryKey<?> key) {
+ final Optional<Registry<Object>> registry = RegistryHelper.getRegistry().registry(ResourceKey.createRegistryKey(ResourceLocation.parse(key.key().asString())));
+ assertTrue(registry.isPresent(), "Missing vanilla registry for " + key.key().asString());
++ }
+
++ @ParameterizedTest
++ @MethodSource("data")
++ void testRegistryEntryExists(final RegistryKey<?> key) {
++ final @Nullable RegistryEntry<?, ?> entry = PaperRegistries.getEntry(key);
++ assertNotNull(entry, "Missing PaperRegistries entry for " + key);
+ }
+ }
+diff --git a/src/test/java/org/bukkit/registry/RegistryArgumentAddedTest.java b/src/test/java/org/bukkit/registry/RegistryArgumentAddedTest.java
+index b65a3ee68c177da7ef5a57608187dc1672257c7f..c1016e0eb00e952551370c874e8d678fef8ba3dc 100644
+--- a/src/test/java/org/bukkit/registry/RegistryArgumentAddedTest.java
++++ b/src/test/java/org/bukkit/registry/RegistryArgumentAddedTest.java
+@@ -22,14 +22,17 @@ public class RegistryArgumentAddedTest {
+ // Make sure every registry is created
+ Class.forName(Registry.class.getName());
+
+- Set<Class<?>> loadedRegistries = new HashSet<>(AllFeaturesExtension.getRealRegistries().keySet());
+- Set<Class<?>> notFound = new HashSet<>();
++ // Paper start
++ Set<io.papermc.paper.registry.RegistryKey<?>> loadedRegistries = java.util.Collections.newSetFromMap(new java.util.IdentityHashMap<>());
++ loadedRegistries.addAll(io.papermc.paper.registry.PaperRegistryAccess.instance().getLoadedServerBackedRegistries());
++ // Paper end
++ Set<io.papermc.paper.registry.RegistryKey<?>> notFound = new HashSet<>(); // Paper
+
+ RegistriesArgumentProvider
+ .getData()
+ .map(Arguments::get)
+ .map(array -> array[0])
+- .map(clazz -> (Class<?>) clazz)
++ .map(clazz -> (io.papermc.paper.registry.RegistryKey<?>) clazz) // Paper
+ .forEach(clazz -> {
+ if (!loadedRegistries.remove(clazz)) {
+ notFound.add(clazz);
+diff --git a/src/test/java/org/bukkit/registry/RegistryConversionTest.java b/src/test/java/org/bukkit/registry/RegistryConversionTest.java
+index e97328b95973db52d44bc4d0aefd8eb69f2ebdea..01e351f4e292efe78fc1a1db0f31b2c0a313b101 100644
+--- a/src/test/java/org/bukkit/registry/RegistryConversionTest.java
++++ b/src/test/java/org/bukkit/registry/RegistryConversionTest.java
+@@ -41,9 +41,9 @@ public class RegistryConversionTest {
+
+ @Order(1)
+ @RegistriesTest
+- public void testHandleableImplementation(Class<? extends Keyed> clazz) {
++ public void testHandleableImplementation(io.papermc.paper.registry.RegistryKey<? extends Keyed> type, Class<? extends Keyed> clazz) { // Paper
+ Set<Class<? extends Keyed>> notImplemented = new HashSet<>();
+- Registry<? extends Keyed> registry = Bukkit.getRegistry(clazz);
++ Registry<? extends Keyed> registry = io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(type); // Paper
+
+ for (Keyed item : registry) {
+ if (!(item instanceof Handleable<?>)) {
+@@ -63,7 +63,7 @@ public class RegistryConversionTest {
+
+ @Order(2)
+ @RegistriesTest
+- public void testMinecraftToBukkitPresent(Class<? extends Keyed> clazz, ResourceKey<net.minecraft.core.Registry<?>> registryKey,
++ public void testMinecraftToBukkitPresent(io.papermc.paper.registry.RegistryKey<? extends Keyed> type, Class<? extends Keyed> clazz, ResourceKey<net.minecraft.core.Registry<?>> registryKey,
+ Class<? extends Keyed> craftClazz, Class<?> minecraftClazz, boolean newMethod) {
+ String methodName = (newMethod) ? RegistryConversionTest.MINECRAFT_TO_BUKKIT_NEW : RegistryConversionTest.MINECRAFT_TO_BUKKIT;
+ Method method = null;
+@@ -112,7 +112,7 @@ public class RegistryConversionTest {
+
+ @Order(2)
+ @RegistriesTest
+- public void testBukkitToMinecraftPresent(Class<? extends Keyed> clazz, ResourceKey<net.minecraft.core.Registry<?>> registryKey,
++ public void testBukkitToMinecraftPresent(io.papermc.paper.registry.RegistryKey<? extends Keyed> type, Class<? extends Keyed> clazz, ResourceKey<net.minecraft.core.Registry<?>> registryKey,
+ Class<? extends Keyed> craftClazz, Class<?> minecraftClazz, boolean newMethod) {
+ String methodName = (newMethod) ? RegistryConversionTest.BUKKIT_TO_MINECRAFT_NEW : RegistryConversionTest.BUKKIT_TO_MINECRAFT;
+ Method method = null;
+@@ -159,9 +159,9 @@ public class RegistryConversionTest {
+ """, minecraftClazz.getName(), methodName, clazz.getSimpleName());
+ }
+
+- @Order(2)
++ @Order(3)
+ @RegistriesTest
+- public void testMinecraftToBukkitNullValue(Class<? extends Keyed> clazz) throws IllegalAccessException {
++ public void testMinecraftToBukkitNullValue(io.papermc.paper.registry.RegistryKey<? extends Keyed> type, Class<? extends Keyed> clazz) throws IllegalAccessException { // Paper
+ this.checkValidMinecraftToBukkit(clazz);
+
+ try {
+@@ -180,7 +180,7 @@ public class RegistryConversionTest {
+
+ @Order(3)
+ @RegistriesTest
+- public void testBukkitToMinecraftNullValue(Class<? extends Keyed> clazz) throws IllegalAccessException {
++ public void testBukkitToMinecraftNullValue(io.papermc.paper.registry.RegistryKey<? extends Keyed> type, Class<? extends Keyed> clazz) throws IllegalAccessException { // Paper
+ this.checkValidBukkitToMinecraft(clazz);
+
+ try {
+@@ -199,14 +199,14 @@ public class RegistryConversionTest {
+
+ @Order(3)
+ @RegistriesTest
+- public void testMinecraftToBukkit(Class<? extends Keyed> clazz) {
++ public void testMinecraftToBukkit(io.papermc.paper.registry.RegistryKey<? extends Keyed> type, Class<? extends Keyed> clazz) { // Paper
+ this.checkValidMinecraftToBukkit(clazz);
+ this.checkValidHandle(clazz);
+
+ Map<Object, Object> notMatching = new HashMap<>();
+ Method method = RegistryConversionTest.MINECRAFT_TO_BUKKIT_METHODS.get(clazz);
+
+- RegistryArgumentProvider.getValues(clazz).map(Arguments::get).forEach(arguments -> {
++ RegistryArgumentProvider.getValues(type).map(Arguments::get).forEach(arguments -> { // Paper
+ Keyed bukkit = (Keyed) arguments[0];
+ Object minecraft = arguments[1];
+
+@@ -230,14 +230,14 @@ public class RegistryConversionTest {
+
+ @Order(3)
+ @RegistriesTest
+- public void testBukkitToMinecraft(Class<? extends Keyed> clazz) {
++ public void testBukkitToMinecraft(io.papermc.paper.registry.RegistryKey<? extends Keyed> type, Class<? extends Keyed> clazz) { // Paper
+ this.checkValidBukkitToMinecraft(clazz);
+ this.checkValidHandle(clazz);
+
+ Map<Object, Object> notMatching = new HashMap<>();
+ Method method = RegistryConversionTest.BUKKIT_TO_MINECRAFT_METHODS.get(clazz);
+
+- RegistryArgumentProvider.getValues(clazz).map(Arguments::get).forEach(arguments -> {
++ RegistryArgumentProvider.getValues(type).map(Arguments::get).forEach(arguments -> { // Paper
+ Keyed bukkit = (Keyed) arguments[0];
+ Object minecraft = arguments[1];
+
+@@ -265,7 +265,7 @@ public class RegistryConversionTest {
+ */
+ @Order(3)
+ @RegistriesTest
+- public void testMinecraftToBukkitNoValidMinecraft(Class<? extends Keyed> clazz, ResourceKey<net.minecraft.core.Registry<?>> registryKey,
++ public void testMinecraftToBukkitNoValidMinecraft(io.papermc.paper.registry.RegistryKey<? extends Keyed> type, Class<? extends Keyed> clazz, ResourceKey<net.minecraft.core.Registry<?>> registryKey, // Paper
+ Class<? extends Keyed> craftClazz, Class<?> minecraftClazz) throws IllegalAccessException {
+ this.checkValidMinecraftToBukkit(clazz);
+
+diff --git a/src/test/java/org/bukkit/support/extension/AllFeaturesExtension.java b/src/test/java/org/bukkit/support/extension/AllFeaturesExtension.java
+index e9eb521419bbacb03d7000ace355f2a9f5a6a9c5..8fef8421e3cf87913746a314a477634bd3e99300 100644
+--- a/src/test/java/org/bukkit/support/extension/AllFeaturesExtension.java
++++ b/src/test/java/org/bukkit/support/extension/AllFeaturesExtension.java
+@@ -39,22 +39,7 @@ public class AllFeaturesExtension extends BaseExtension {
+
+ Bukkit.setServer(server);
+
+- when(server.getRegistry(any()))
+- .then(invocation -> {
+- Class<? extends Keyed> keyed = invocation.getArgument(0);
+- if (spyRegistries.containsKey(keyed)) {
+- return spyRegistries.get(keyed);
+- }
+-
+- Registry<?> registry = CraftRegistry.createRegistry(keyed, RegistryHelper.getRegistry());
+- realRegistries.put(keyed, registry);
+-
+- Registry<?> spy = mock(registry.getClass(), withSettings().stubOnly().spiedInstance(registry).defaultAnswer(CALLS_REAL_METHODS));
+-
+- spyRegistries.put(keyed, spy);
+-
+- return spy;
+- });
++ // Paper - Add RegistryAccess for managing registries - replaced with registry access
+
+ CraftRegistry.setMinecraftRegistry(RegistryHelper.getRegistry());
+ }
+diff --git a/src/test/java/org/bukkit/support/extension/LegacyExtension.java b/src/test/java/org/bukkit/support/extension/LegacyExtension.java
+index 94cf52cf7603e6814682c92b26fcf03a8b927838..c9c3227c3b7fa36ed80f2dc828885a0128e1e3d0 100644
+--- a/src/test/java/org/bukkit/support/extension/LegacyExtension.java
++++ b/src/test/java/org/bukkit/support/extension/LegacyExtension.java
+@@ -30,11 +30,7 @@ public class LegacyExtension extends BaseExtension {
+
+ Bukkit.setServer(server);
+
+- when(server.getRegistry(any()))
+- .then(invocation -> {
+- Class<? extends Keyed> keyed = invocation.getArgument(0);
+- return registries.computeIfAbsent(keyed, k -> CraftRegistry.createRegistry(keyed, RegistryHelper.getRegistry()));
+- });
++ // Paper - Add RegistryAccess for managing registries - replaced with registry access
+
+ CraftRegistry.setMinecraftRegistry(RegistryHelper.getRegistry());
+ }
+diff --git a/src/test/java/org/bukkit/support/extension/SlowExtension.java b/src/test/java/org/bukkit/support/extension/SlowExtension.java
+index e0ce6836d4365c36303f6c675a75ef6a9b047b92..87364f223fbd6185b041138550fcb6e3ed07d1ae 100644
+--- a/src/test/java/org/bukkit/support/extension/SlowExtension.java
++++ b/src/test/java/org/bukkit/support/extension/SlowExtension.java
+@@ -30,11 +30,7 @@ public class SlowExtension extends BaseExtension {
+
+ Bukkit.setServer(server);
+
+- when(server.getRegistry(any()))
+- .then(invocation -> {
+- Class<? extends Keyed> keyed = invocation.getArgument(0);
+- return registries.computeIfAbsent(keyed, k -> CraftRegistry.createRegistry(keyed, RegistryHelper.getRegistry()));
+- });
++ // Paper - Add RegistryAccess for managing registries - replaced with registry access
+
+ CraftRegistry.setMinecraftRegistry(RegistryHelper.getRegistry());
+ }
+diff --git a/src/test/java/org/bukkit/support/extension/VanillaFeatureExtension.java b/src/test/java/org/bukkit/support/extension/VanillaFeatureExtension.java
+index bbd5dd5b27937ddc3d8c57f2b604331495b0f311..626c3033e36897846fe84a77d05e2e91a15598e5 100644
+--- a/src/test/java/org/bukkit/support/extension/VanillaFeatureExtension.java
++++ b/src/test/java/org/bukkit/support/extension/VanillaFeatureExtension.java
+@@ -30,11 +30,7 @@ public class VanillaFeatureExtension extends BaseExtension {
+
+ Bukkit.setServer(server);
+
+- when(server.getRegistry(any()))
+- .then(invocation -> {
+- Class<? extends Keyed> keyed = invocation.getArgument(0);
+- return registries.computeIfAbsent(keyed, k -> CraftRegistry.createRegistry(keyed, RegistryHelper.getRegistry()));
+- });
++ // Paper - Add RegistryAccess for managing registries - replaced with registry access
+
+ CraftRegistry.setMinecraftRegistry(RegistryHelper.getRegistry());
+ }
+diff --git a/src/test/java/org/bukkit/support/provider/RegistriesArgumentProvider.java b/src/test/java/org/bukkit/support/provider/RegistriesArgumentProvider.java
+index 47f3b79d76399ff2185ea753260a702441ecadf5..eb3974690fb12ffe678522ed47e0f730712db016 100644
+--- a/src/test/java/org/bukkit/support/provider/RegistriesArgumentProvider.java
++++ b/src/test/java/org/bukkit/support/provider/RegistriesArgumentProvider.java
+@@ -1,6 +1,7 @@
+ package org.bukkit.support.provider;
+
+ import com.google.common.collect.Lists;
++import io.papermc.paper.registry.RegistryKey;
+ import java.util.List;
+ import java.util.stream.Stream;
+ import net.minecraft.core.registries.Registries;
+@@ -61,36 +62,35 @@ public class RegistriesArgumentProvider implements ArgumentsProvider {
+ private static final List<Arguments> DATA = Lists.newArrayList();
+
+ static {
+- // Order: Bukkit class, Minecraft Registry key, CraftBukkit class, Minecraft class
+- register(Enchantment.class, Registries.ENCHANTMENT, CraftEnchantment.class, net.minecraft.world.item.enchantment.Enchantment.class);
+- register(GameEvent.class, Registries.GAME_EVENT, CraftGameEvent.class, net.minecraft.world.level.gameevent.GameEvent.class);
+- register(MusicInstrument.class, Registries.INSTRUMENT, CraftMusicInstrument.class, Instrument.class);
+- register(MenuType.class, Registries.MENU, CraftMenuType.class, net.minecraft.world.inventory.MenuType.class);
+- register(PotionEffectType.class, Registries.MOB_EFFECT, CraftPotionEffectType.class, MobEffect.class);
+- register(Structure.class, Registries.STRUCTURE, CraftStructure.class, net.minecraft.world.level.levelgen.structure.Structure.class);
+- register(StructureType.class, Registries.STRUCTURE_TYPE, CraftStructureType.class, net.minecraft.world.level.levelgen.structure.StructureType.class);
+- register(Villager.Type.class, Registries.VILLAGER_TYPE, CraftVillager.CraftType.class, VillagerType.class);
+- register(Villager.Profession.class, Registries.VILLAGER_PROFESSION, CraftVillager.CraftProfession.class, VillagerProfession.class);
+- register(TrimMaterial.class, Registries.TRIM_MATERIAL, CraftTrimMaterial.class, net.minecraft.world.item.equipment.trim.TrimMaterial.class);
+- register(TrimPattern.class, Registries.TRIM_PATTERN, CraftTrimPattern.class, net.minecraft.world.item.equipment.trim.TrimPattern.class);
+- register(DamageType.class, Registries.DAMAGE_TYPE, CraftDamageType.class, net.minecraft.world.damagesource.DamageType.class);
+- register(JukeboxSong.class, Registries.JUKEBOX_SONG, CraftJukeboxSong.class, net.minecraft.world.item.JukeboxSong.class);
+- register(Wolf.Variant.class, Registries.WOLF_VARIANT, CraftWolf.CraftVariant.class, WolfVariant.class);
+- register(ItemType.class, Registries.ITEM, CraftItemType.class, net.minecraft.world.item.Item.class, true);
+- register(BlockType.class, Registries.BLOCK, CraftBlockType.class, net.minecraft.world.level.block.Block.class, true);
+- register(Frog.Variant.class, Registries.FROG_VARIANT, CraftFrog.CraftVariant.class, FrogVariant.class);
+- register(Cat.Type.class, Registries.CAT_VARIANT, CraftCat.CraftType.class, CatVariant.class);
+- register(MapCursor.Type.class, Registries.MAP_DECORATION_TYPE, CraftMapCursor.CraftType.class, MapDecorationType.class);
+- register(PatternType.class, Registries.BANNER_PATTERN, CraftPatternType.class, BannerPattern.class);
+-
++ // Order: RegistryKey, Bukkit class, Minecraft Registry key, CraftBukkit class, Minecraft class
++ register(RegistryKey.ENCHANTMENT, Enchantment.class, Registries.ENCHANTMENT, CraftEnchantment.class, net.minecraft.world.item.enchantment.Enchantment.class);
++ register(RegistryKey.GAME_EVENT, GameEvent.class, Registries.GAME_EVENT, CraftGameEvent.class, net.minecraft.world.level.gameevent.GameEvent.class);
++ register(RegistryKey.INSTRUMENT, MusicInstrument.class, Registries.INSTRUMENT, CraftMusicInstrument.class, Instrument.class);
++ register(RegistryKey.MOB_EFFECT, PotionEffectType.class, Registries.MOB_EFFECT, CraftPotionEffectType.class, MobEffect.class);
++ register(RegistryKey.STRUCTURE, Structure.class, Registries.STRUCTURE, CraftStructure.class, net.minecraft.world.level.levelgen.structure.Structure.class);
++ register(RegistryKey.STRUCTURE_TYPE, StructureType.class, Registries.STRUCTURE_TYPE, CraftStructureType.class, net.minecraft.world.level.levelgen.structure.StructureType.class);
++ register(RegistryKey.VILLAGER_TYPE, Villager.Type.class, Registries.VILLAGER_TYPE, CraftVillager.CraftType.class, VillagerType.class);
++ register(RegistryKey.VILLAGER_PROFESSION, Villager.Profession.class, Registries.VILLAGER_PROFESSION, CraftVillager.CraftProfession.class, VillagerProfession.class);
++ register(RegistryKey.TRIM_MATERIAL, TrimMaterial.class, Registries.TRIM_MATERIAL, CraftTrimMaterial.class, net.minecraft.world.item.equipment.trim.TrimMaterial.class);
++ register(RegistryKey.TRIM_PATTERN, TrimPattern.class, Registries.TRIM_PATTERN, CraftTrimPattern.class, net.minecraft.world.item.equipment.trim.TrimPattern.class);
++ register(RegistryKey.DAMAGE_TYPE, DamageType.class, Registries.DAMAGE_TYPE, CraftDamageType.class, net.minecraft.world.damagesource.DamageType.class);
++ register(RegistryKey.JUKEBOX_SONG, JukeboxSong.class, Registries.JUKEBOX_SONG, CraftJukeboxSong.class, net.minecraft.world.item.JukeboxSong.class);
++ register(RegistryKey.WOLF_VARIANT, Wolf.Variant.class, Registries.WOLF_VARIANT, CraftWolf.CraftVariant.class, WolfVariant.class);
++ register(RegistryKey.ITEM, ItemType.class, Registries.ITEM, CraftItemType.class, net.minecraft.world.item.Item.class, true);
++ register(RegistryKey.BLOCK, BlockType.class, Registries.BLOCK, CraftBlockType.class, net.minecraft.world.level.block.Block.class, true);
++ register(RegistryKey.FROG_VARIANT, Frog.Variant.class, Registries.FROG_VARIANT, CraftFrog.CraftVariant.class, FrogVariant.class);
++ register(RegistryKey.CAT_VARIANT, Cat.Type.class, Registries.CAT_VARIANT, CraftCat.CraftType.class, CatVariant.class);
++ register(RegistryKey.MAP_DECORATION_TYPE, MapCursor.Type.class, Registries.MAP_DECORATION_TYPE, CraftMapCursor.CraftType.class, MapDecorationType.class);
++ register(RegistryKey.BANNER_PATTERN, PatternType.class, Registries.BANNER_PATTERN, CraftPatternType.class, BannerPattern.class);
++ register(RegistryKey.MENU, MenuType.class, Registries.MENU, CraftMenuType.class, net.minecraft.world.inventory.MenuType.class);
+ }
+
+- private static void register(Class bukkit, ResourceKey registry, Class craft, Class minecraft) {
+- RegistriesArgumentProvider.register(bukkit, registry, craft, minecraft, false);
++ private static void register(RegistryKey registryKey, Class bukkit, ResourceKey registry, Class craft, Class minecraft) { // Paper
++ RegistriesArgumentProvider.register(registryKey, bukkit, registry, craft, minecraft, false);
+ }
+
+- private static void register(Class bukkit, ResourceKey registry, Class craft, Class minecraft, boolean newClass) {
+- RegistriesArgumentProvider.DATA.add(Arguments.of(bukkit, registry, craft, minecraft, newClass));
++ private static void register(RegistryKey registryKey, Class bukkit, ResourceKey registry, Class craft, Class minecraft, boolean newClass) { // Paper
++ RegistriesArgumentProvider.DATA.add(Arguments.of(registryKey, bukkit, registry, craft, minecraft, newClass));
+ }
+
+ @Override
+diff --git a/src/test/java/org/bukkit/support/provider/RegistryArgumentProvider.java b/src/test/java/org/bukkit/support/provider/RegistryArgumentProvider.java
+index f2ceb5e67536dfa5de792dc64a7898fd2e8aa810..beb5fc9e721f5de54064c3d241df9ca9f4cd4f65 100644
+--- a/src/test/java/org/bukkit/support/provider/RegistryArgumentProvider.java
++++ b/src/test/java/org/bukkit/support/provider/RegistryArgumentProvider.java
+@@ -22,11 +22,11 @@ public class RegistryArgumentProvider implements ArgumentsProvider, AnnotationCo
+
+ @Override
+ public Stream<? extends Arguments> provideArguments(ExtensionContext extensionContext) throws Exception {
+- return RegistryArgumentProvider.getValues(this.registryType);
++ return RegistryArgumentProvider.getValues(io.papermc.paper.registry.PaperRegistryAccess.byType(this.registryType)); // Paper
+ }
+
+- public static Stream<? extends Arguments> getValues(Class<? extends Keyed> registryType) {
+- Registry<?> registry = Bukkit.getRegistry(registryType);
++ public static Stream<? extends Arguments> getValues(io.papermc.paper.registry.RegistryKey<? extends Keyed> registryType) { // Paper
++ Registry<?> registry = io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(registryType); // Paper
+ return registry.stream().map(keyed -> (Handleable<?>) keyed)
+ .map(handleAble -> Arguments.of(handleAble, handleAble.getHandle()));
+ }
diff --git a/patches/server/0468-Add-StructuresLocateEvent.patch b/patches/server/0468-Add-StructuresLocateEvent.patch
new file mode 100644
index 0000000000..5312adefad
--- /dev/null
+++ b/patches/server/0468-Add-StructuresLocateEvent.patch
@@ -0,0 +1,36 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: dfsek <[email protected]>
+Date: Wed, 16 Sep 2020 01:12:29 -0700
+Subject: [PATCH] Add StructuresLocateEvent
+
+Co-authored-by: Jake Potrebic <[email protected]>
+
+diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java
+index 115deba41ec48143570489e8494785a3a48cd789..fd2dd6d25b8d6f3066c60a7f30a58a72cb418b85 100644
+--- a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java
++++ b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java
+@@ -126,6 +126,24 @@ public abstract class ChunkGenerator {
+
+ @Nullable
+ public Pair<BlockPos, Holder<Structure>> findNearestMapStructure(ServerLevel world, HolderSet<Structure> structures, BlockPos center, int radius, boolean skipReferencedStructures) {
++ // Paper start - StructuresLocateEvent
++ final org.bukkit.World bukkitWorld = world.getWorld();
++ final org.bukkit.Location origin = io.papermc.paper.util.MCUtil.toLocation(world, center);
++ final List<org.bukkit.generator.structure.Structure> apiStructures = structures.stream().map(Holder::value).map(nms -> org.bukkit.craftbukkit.generator.structure.CraftStructure.minecraftToBukkit(nms)).toList();
++ if (!apiStructures.isEmpty()) {
++ final io.papermc.paper.event.world.StructuresLocateEvent event = new io.papermc.paper.event.world.StructuresLocateEvent(bukkitWorld, origin, apiStructures, radius, skipReferencedStructures);
++ if (!event.callEvent()) {
++ return null;
++ }
++ if (event.getResult() != null) {
++ return Pair.of(io.papermc.paper.util.MCUtil.toBlockPos(event.getResult().pos()), world.registryAccess().registryOrThrow(Registries.STRUCTURE).wrapAsHolder(org.bukkit.craftbukkit.generator.structure.CraftStructure.bukkitToMinecraft(event.getResult().structure())));
++ }
++ center = io.papermc.paper.util.MCUtil.toBlockPosition(event.getOrigin());
++ radius = event.getRadius();
++ skipReferencedStructures = event.shouldFindUnexplored();
++ structures = HolderSet.direct(api -> world.registryAccess().registryOrThrow(Registries.STRUCTURE).wrapAsHolder(org.bukkit.craftbukkit.generator.structure.CraftStructure.bukkitToMinecraft(api)), event.getStructures());
++ }
++ // Paper end
+ ChunkGeneratorStructureState chunkgeneratorstructurestate = world.getChunkSource().getGeneratorState();
+ Map<StructurePlacement, Set<Holder<Structure>>> map = new Object2ObjectArrayMap();
+ Iterator iterator = structures.iterator();
diff --git a/patches/server/0469-Collision-option-for-requiring-a-player-participant.patch b/patches/server/0469-Collision-option-for-requiring-a-player-participant.patch
new file mode 100644
index 0000000000..a1b916f2ad
--- /dev/null
+++ b/patches/server/0469-Collision-option-for-requiring-a-player-participant.patch
@@ -0,0 +1,42 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Mariell Hoversholm <[email protected]>
+Date: Sat, 14 Nov 2020 16:48:37 +0100
+Subject: [PATCH] Collision option for requiring a player participant
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
+index ad34a525f54a157140323a26752a420a8e348a55..1d2f0f8756addf0db7356b47ea8a1eddd2c4503d 100644
+--- a/src/main/java/net/minecraft/world/entity/Entity.java
++++ b/src/main/java/net/minecraft/world/entity/Entity.java
+@@ -2024,6 +2024,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+ public void push(Entity entity) {
+ if (!this.isPassengerOfSameVehicle(entity)) {
+ if (!entity.noPhysics && !this.noPhysics) {
++ if (this.level.paperConfig().collisions.onlyPlayersCollide && !(entity instanceof ServerPlayer || this instanceof ServerPlayer)) return; // Paper - Collision option for requiring a player participant
+ double d0 = entity.getX() - this.getX();
+ double d1 = entity.getZ() - this.getZ();
+ double d2 = Mth.absMax(d0, d1);
+diff --git a/src/main/java/net/minecraft/world/entity/vehicle/AbstractBoat.java b/src/main/java/net/minecraft/world/entity/vehicle/AbstractBoat.java
+index a4eab65c280e493889621e62d8fc94158b930c96..57ea01469ddd180a0c2121cce2807bcccf93bf48 100644
+--- a/src/main/java/net/minecraft/world/entity/vehicle/AbstractBoat.java
++++ b/src/main/java/net/minecraft/world/entity/vehicle/AbstractBoat.java
+@@ -196,6 +196,7 @@ public abstract class AbstractBoat extends VehicleEntity implements Leashable {
+
+ @Override
+ public void push(Entity entity) {
++ if (!this.level().paperConfig().collisions.allowVehicleCollisions && this.level().paperConfig().collisions.onlyPlayersCollide && !(entity instanceof Player)) return; // Paper - Collision option for requiring a player participant
+ if (entity instanceof AbstractBoat) {
+ if (entity.getBoundingBox().minY < this.getBoundingBox().maxY) {
+ // CraftBukkit start
+diff --git a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java
+index 50c0055d80735313c280821991bd2a76e427f082..ee7350e19a86ffa115e4bce6b186a2422951e89b 100644
+--- a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java
++++ b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java
+@@ -562,6 +562,7 @@ public abstract class AbstractMinecart extends VehicleEntity {
+ public void push(Entity entity) {
+ if (!this.level().isClientSide) {
+ if (!entity.noPhysics && !this.noPhysics) {
++ if (!this.level().paperConfig().collisions.allowVehicleCollisions && this.level().paperConfig().collisions.onlyPlayersCollide && !(entity instanceof Player)) return; // Paper - Collision option for requiring a player participant
+ if (!this.hasPassenger(entity)) {
+ // CraftBukkit start
+ VehicleEntityCollisionEvent collisionEvent = new VehicleEntityCollisionEvent((Vehicle) this.getBukkitEntity(), entity.getBukkitEntity());
diff --git a/patches/server/0470-Return-chat-component-with-empty-text-instead-of-thr.patch b/patches/server/0470-Return-chat-component-with-empty-text-instead-of-thr.patch
new file mode 100644
index 0000000000..20c17b39c0
--- /dev/null
+++ b/patches/server/0470-Return-chat-component-with-empty-text-instead-of-thr.patch
@@ -0,0 +1,25 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: CDFN <[email protected]>
+Date: Tue, 7 Jul 2020 17:53:23 +0200
+Subject: [PATCH] Return chat component with empty text instead of throwing
+ exception
+
+
+diff --git a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java
+index 8a03f31d07e467288dd518c99f3f29bc77eac058..3b92fb173f623a05ae99c86d98f2ecdf907f58c4 100644
+--- a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java
++++ b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java
+@@ -93,7 +93,12 @@ public abstract class AbstractContainerMenu {
+ }
+ private Component title;
+ public final Component getTitle() {
+- Preconditions.checkState(this.title != null, "Title not set");
++ // Paper start - return chat component with empty text instead of throwing error
++ // Preconditions.checkState(this.title != null, "Title not set");
++ if (this.title == null){
++ return Component.literal("");
++ }
++ // Paper end - return chat component with empty text instead of throwing error
+ return this.title;
+ }
+ public final void setTitle(Component title) {
diff --git a/patches/server/0471-Make-schedule-command-per-world.patch b/patches/server/0471-Make-schedule-command-per-world.patch
new file mode 100644
index 0000000000..3ac2f8e3f4
--- /dev/null
+++ b/patches/server/0471-Make-schedule-command-per-world.patch
@@ -0,0 +1,28 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Mon, 4 Jan 2021 19:52:44 -0800
+Subject: [PATCH] Make schedule command per-world
+
+
+diff --git a/src/main/java/net/minecraft/server/commands/ScheduleCommand.java b/src/main/java/net/minecraft/server/commands/ScheduleCommand.java
+index 10b059a93762d9711fb4aedff572edfaebcb8264..1090759025969c04267de8b96ad045b1da0cb246 100644
+--- a/src/main/java/net/minecraft/server/commands/ScheduleCommand.java
++++ b/src/main/java/net/minecraft/server/commands/ScheduleCommand.java
+@@ -33,7 +33,7 @@ public class ScheduleCommand {
+ });
+ private static final SimpleCommandExceptionType ERROR_MACRO = new SimpleCommandExceptionType(Component.translatableEscape("commands.schedule.macro"));
+ private static final SuggestionProvider<CommandSourceStack> SUGGEST_SCHEDULE = (commandcontext, suggestionsbuilder) -> {
+- return SharedSuggestionProvider.suggest((Iterable) ((CommandSourceStack) commandcontext.getSource()).getServer().getWorldData().overworldData().getScheduledEvents().getEventsIds(), suggestionsbuilder);
++ return SharedSuggestionProvider.suggest((Iterable) ((net.minecraft.commands.CommandSourceStack) commandcontext.getSource()).getLevel().serverLevelData.getScheduledEvents().getEventsIds(), suggestionsbuilder); // Paper - Make schedule command per-world
+ };
+
+ public ScheduleCommand() {}
+@@ -93,7 +93,7 @@ public class ScheduleCommand {
+ }
+
+ private static int remove(CommandSourceStack source, String eventName) throws CommandSyntaxException {
+- int i = source.getServer().getWorldData().overworldData().getScheduledEvents().remove(eventName);
++ int i = source.getLevel().serverLevelData.getScheduledEvents().remove(eventName); // Paper - Make schedule command per-world
+
+ if (i == 0) {
+ throw ScheduleCommand.ERROR_CANT_REMOVE.create(eventName);
diff --git a/patches/server/0472-Configurable-max-leash-distance.patch b/patches/server/0472-Configurable-max-leash-distance.patch
new file mode 100644
index 0000000000..625e1c156b
--- /dev/null
+++ b/patches/server/0472-Configurable-max-leash-distance.patch
@@ -0,0 +1,32 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Sun, 3 Jan 2021 21:04:03 -0800
+Subject: [PATCH] Configurable max leash distance
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/Leashable.java b/src/main/java/net/minecraft/world/entity/Leashable.java
+index e53c3b53ea9c0932f0f049bf76f1f6de0432506a..dc39ecc3e1aada638337d31bfe68b400c6454af7 100644
+--- a/src/main/java/net/minecraft/world/entity/Leashable.java
++++ b/src/main/java/net/minecraft/world/entity/Leashable.java
+@@ -180,7 +180,7 @@ public interface Leashable {
+ return;
+ }
+
+- if ((double) f > 10.0D) {
++ if ((double) f > entity.level().paperConfig().misc.maxLeashDistance.or(LEASH_TOO_FAR_DIST)) { // Paper - Configurable max leash distance
+ ((Leashable) entity).leashTooFarBehaviour();
+ } else if ((double) f > 6.0D) {
+ ((Leashable) entity).elasticRangeLeashBehaviour(entity1, f);
+diff --git a/src/main/java/net/minecraft/world/entity/TamableAnimal.java b/src/main/java/net/minecraft/world/entity/TamableAnimal.java
+index f239ab65d914ee9ec819e112f0a0466a06a77929..d2785628368b65854b6e1f35005c478d490a2f8c 100644
+--- a/src/main/java/net/minecraft/world/entity/TamableAnimal.java
++++ b/src/main/java/net/minecraft/world/entity/TamableAnimal.java
+@@ -98,7 +98,7 @@ public abstract class TamableAnimal extends Animal implements OwnableEntity {
+ @Override
+ public boolean handleLeashAtDistance(Entity leashHolder, float distance) {
+ if (this.isInSittingPose()) {
+- if (distance > 10.0F) {
++ if (distance > (float) this.level().paperConfig().misc.maxLeashDistance.or(Leashable.LEASH_TOO_FAR_DIST)) { // Paper - Configurable max leash distance
+ this.dropLeash(true, true);
+ }
+
diff --git a/patches/server/0473-Add-BlockPreDispenseEvent.patch b/patches/server/0473-Add-BlockPreDispenseEvent.patch
new file mode 100644
index 0000000000..ae0c60a3b4
--- /dev/null
+++ b/patches/server/0473-Add-BlockPreDispenseEvent.patch
@@ -0,0 +1,46 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Madeline Miller <[email protected]>
+Date: Sun, 17 Jan 2021 13:16:09 +1000
+Subject: [PATCH] Add BlockPreDispenseEvent
+
+
+diff --git a/src/main/java/net/minecraft/world/level/block/DispenserBlock.java b/src/main/java/net/minecraft/world/level/block/DispenserBlock.java
+index f4853a5ff8a45efcda2d7781c1fa897c47d8ea46..a02f24448b002824b068278fa427003008c0d0f1 100644
+--- a/src/main/java/net/minecraft/world/level/block/DispenserBlock.java
++++ b/src/main/java/net/minecraft/world/level/block/DispenserBlock.java
+@@ -107,6 +107,7 @@ public class DispenserBlock extends BaseEntityBlock {
+ DispenseItemBehavior idispensebehavior = this.getDispenseMethod(world, itemstack);
+
+ if (idispensebehavior != DispenseItemBehavior.NOOP) {
++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockPreDispenseEvent(world, pos, itemstack, i)) return; // Paper - Add BlockPreDispenseEvent
+ DispenserBlock.eventFired = false; // CraftBukkit - reset event status
+ tileentitydispenser.setItem(i, idispensebehavior.dispense(sourceblock, itemstack));
+ }
+diff --git a/src/main/java/net/minecraft/world/level/block/DropperBlock.java b/src/main/java/net/minecraft/world/level/block/DropperBlock.java
+index 91b514967405115f22edf4255775361a672e5c2f..ddecf443df3679e3098eb54edd19585a0512e342 100644
+--- a/src/main/java/net/minecraft/world/level/block/DropperBlock.java
++++ b/src/main/java/net/minecraft/world/level/block/DropperBlock.java
+@@ -71,6 +71,7 @@ public class DropperBlock extends DispenserBlock {
+ ItemStack itemstack1;
+
+ if (iinventory == null) {
++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockPreDispenseEvent(world, pos, itemstack, i)) return; // Paper - Add BlockPreDispenseEvent
+ itemstack1 = DropperBlock.DISPENSE_BEHAVIOUR.dispense(sourceblock, itemstack);
+ } else {
+ // CraftBukkit start - Fire event when pushing items into other inventories
+diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
+index 4d7ac961b35b747ba5369ec1bdb6a9d78281f5d7..12aeb922c8a53eff9ba8e2c765d87df497611362 100644
+--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
+@@ -2132,5 +2132,11 @@ populateFields(victim, event); // Paper - make cancellable
+ io.papermc.paper.event.block.BlockFailedDispenseEvent event = new io.papermc.paper.event.block.BlockFailedDispenseEvent(block);
+ return event.callEvent();
+ }
++
++ public static boolean handleBlockPreDispenseEvent(ServerLevel serverLevel, BlockPos pos, ItemStack itemStack, int slot) {
++ org.bukkit.block.Block block = CraftBlock.at(serverLevel, pos);
++ io.papermc.paper.event.block.BlockPreDispenseEvent event = new io.papermc.paper.event.block.BlockPreDispenseEvent(block, org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack), slot);
++ return event.callEvent();
++ }
+ // Paper end
+ }
diff --git a/patches/server/0474-Add-PlayerChangeBeaconEffectEvent.patch b/patches/server/0474-Add-PlayerChangeBeaconEffectEvent.patch
new file mode 100644
index 0000000000..0b8263f96e
--- /dev/null
+++ b/patches/server/0474-Add-PlayerChangeBeaconEffectEvent.patch
@@ -0,0 +1,38 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Wed, 24 Jun 2020 15:14:51 -0600
+Subject: [PATCH] Add PlayerChangeBeaconEffectEvent
+
+
+diff --git a/src/main/java/net/minecraft/world/inventory/BeaconMenu.java b/src/main/java/net/minecraft/world/inventory/BeaconMenu.java
+index a191cef8037d94c7e93f79b86fc26ebefae95d8f..cad0b581c992edc5cd312a727695a443e26e96d8 100644
+--- a/src/main/java/net/minecraft/world/inventory/BeaconMenu.java
++++ b/src/main/java/net/minecraft/world/inventory/BeaconMenu.java
+@@ -157,12 +157,25 @@ public class BeaconMenu extends AbstractContainerMenu {
+ return BeaconMenu.decodeEffect(this.beaconData.get(2));
+ }
+
++ // Paper start - Add PlayerChangeBeaconEffectEvent
++ private static @Nullable org.bukkit.potion.PotionEffectType convert(Optional<Holder<MobEffect>> optionalEffect) {
++ return optionalEffect.map(org.bukkit.craftbukkit.potion.CraftPotionEffectType::minecraftHolderToBukkit).orElse(null);
++ }
++ // Paper end - Add PlayerChangeBeaconEffectEvent
++
+ public void updateEffects(Optional<Holder<MobEffect>> primary, Optional<Holder<MobEffect>> secondary) {
+ if (this.paymentSlot.hasItem()) {
+- this.beaconData.set(1, BeaconMenu.encodeEffect((Holder) primary.orElse(null)));// CraftBukkit - decompile error
+- this.beaconData.set(2, BeaconMenu.encodeEffect((Holder) secondary.orElse(null)));// CraftBukkit - decompile error
++ // Paper start - Add PlayerChangeBeaconEffectEvent
++ io.papermc.paper.event.player.PlayerChangeBeaconEffectEvent event = new io.papermc.paper.event.player.PlayerChangeBeaconEffectEvent((org.bukkit.entity.Player) this.player.player.getBukkitEntity(), convert(primary), convert(secondary), this.access.getLocation().getBlock());
++ if (event.callEvent()) {
++ // Paper end - Add PlayerChangeBeaconEffectEvent
++ this.beaconData.set(1, BeaconMenu.encodeEffect(event.getPrimary() == null ? null : org.bukkit.craftbukkit.potion.CraftPotionEffectType.bukkitToMinecraftHolder(event.getPrimary())));// CraftBukkit - decompile error
++ this.beaconData.set(2, BeaconMenu.encodeEffect(event.getSecondary() == null ? null : org.bukkit.craftbukkit.potion.CraftPotionEffectType.bukkitToMinecraftHolder(event.getSecondary())));// CraftBukkit - decompile error
++ if (event.willConsumeItem()) { // Paper
+ this.paymentSlot.remove(1);
++ } // Paper
+ this.access.execute(Level::blockEntityChanged);
++ } // Paper end - Add PlayerChangeBeaconEffectEvent
+ }
+
+ }
diff --git a/patches/server/0475-Add-toggle-for-always-placing-the-dragon-egg.patch b/patches/server/0475-Add-toggle-for-always-placing-the-dragon-egg.patch
new file mode 100644
index 0000000000..0b28271dd1
--- /dev/null
+++ b/patches/server/0475-Add-toggle-for-always-placing-the-dragon-egg.patch
@@ -0,0 +1,19 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath <[email protected]>
+Date: Thu, 26 Nov 2020 11:47:24 +0000
+Subject: [PATCH] Add toggle for always placing the dragon egg
+
+
+diff --git a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java
+index 323fbf684b7062c1b9084f1718538a3b3414d5bf..0e1eceb2b83aaafccbb5d58cf5098cfbc6f25a54 100644
+--- a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java
++++ b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java
+@@ -410,7 +410,7 @@ public class EndDragonFight {
+ this.dragonEvent.setVisible(false);
+ this.spawnExitPortal(true);
+ this.spawnNewGateway();
+- if (!this.previouslyKilled) {
++ if (this.level.paperConfig().entities.behavior.enderDragonsDeathAlwaysPlacesDragonEgg || !this.previouslyKilled) { // Paper - Add toggle for always placing the dragon egg
+ this.level.setBlockAndUpdate(this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, EndPodiumFeature.getLocation(this.origin)), Blocks.DRAGON_EGG.defaultBlockState());
+ }
+
diff --git a/patches/server/0476-Add-PlayerStonecutterRecipeSelectEvent.patch b/patches/server/0476-Add-PlayerStonecutterRecipeSelectEvent.patch
new file mode 100644
index 0000000000..8a5e50313f
--- /dev/null
+++ b/patches/server/0476-Add-PlayerStonecutterRecipeSelectEvent.patch
@@ -0,0 +1,57 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Fri, 27 Nov 2020 17:14:27 -0800
+Subject: [PATCH] Add PlayerStonecutterRecipeSelectEvent
+
+Co-Authored-By: MiniDigger <[email protected]>
+
+diff --git a/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java b/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java
+index 97837d1baf6b929a50e5562ef466050e70c2c8b1..a93870952e2ef674028b8a20aa52a685c743e7ea 100644
+--- a/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java
++++ b/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java
+@@ -64,7 +64,7 @@ public class StonecutterMenu extends AbstractContainerMenu {
+
+ public StonecutterMenu(int syncId, Inventory playerInventory, final ContainerLevelAccess context) {
+ super(MenuType.STONECUTTER, syncId);
+- this.selectedRecipeIndex = DataSlot.standalone();
++ this.selectedRecipeIndex = DataSlot.shared(new int[1], 0); // Paper - Add PlayerStonecutterRecipeSelectEvent
+ this.recipesForInput = SelectableRecipe.SingleInputSet.empty();
+ this.input = ItemStack.EMPTY;
+ this.slotUpdateListener = () -> {
+@@ -150,8 +150,34 @@ public class StonecutterMenu extends AbstractContainerMenu {
+ @Override
+ public boolean clickMenuButton(net.minecraft.world.entity.player.Player player, int id) {
+ if (this.isValidRecipeIndex(id)) {
+- this.selectedRecipeIndex.set(id);
+- this.setupResultSlot(id);
++ // Paper start - Add PlayerStonecutterRecipeSelectEvent
++ int recipeIndex = id;
++ this.selectedRecipeIndex.set(recipeIndex);
++ this.selectedRecipeIndex.checkAndClearUpdateFlag(); // mark as changed
++ paperEventBlock: if (this.isValidRecipeIndex(id)) {
++ final Optional<RecipeHolder<StonecutterRecipe>> recipe = this.recipesForInput.entries().get(id).recipe().recipe();
++ if (recipe.isEmpty()) break paperEventBlock; // The recipe selected does not have an actual server recipe (presumably its the empty one). Cannot call the event, just break.
++
++ io.papermc.paper.event.player.PlayerStonecutterRecipeSelectEvent event = new io.papermc.paper.event.player.PlayerStonecutterRecipeSelectEvent((Player) player.getBukkitEntity(), getBukkitView().getTopInventory(), (org.bukkit.inventory.StonecuttingRecipe) recipe.get().toBukkitRecipe());
++ if (!event.callEvent()) {
++ player.containerMenu.sendAllDataToRemote();
++ return false;
++ }
++
++ net.minecraft.resources.ResourceLocation key = org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(event.getStonecuttingRecipe().getKey());
++ if (!recipe.get().id().location().equals(key)) { // If the recipe did NOT stay the same
++ for (int newRecipeIndex = 0; newRecipeIndex < this.recipesForInput.entries().size(); newRecipeIndex++) {
++ if (this.recipesForInput.entries().get(newRecipeIndex).recipe().recipe().filter(r -> r.id().location().equals(key)).isPresent()) {
++ recipeIndex = newRecipeIndex;
++ break;
++ }
++ }
++ }
++ }
++ player.containerMenu.sendAllDataToRemote();
++ this.selectedRecipeIndex.set(recipeIndex); // set new index, so that listeners can read it
++ this.setupResultSlot(recipeIndex);
++ // Paper end - Add PlayerStonecutterRecipeSelectEvent
+ }
+
+ return true;
diff --git a/patches/server/0477-Expand-EntityUnleashEvent.patch b/patches/server/0477-Expand-EntityUnleashEvent.patch
new file mode 100644
index 0000000000..fceff6d94a
--- /dev/null
+++ b/patches/server/0477-Expand-EntityUnleashEvent.patch
@@ -0,0 +1,157 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Nassim Jahnke <[email protected]>
+Date: Fri, 29 Jan 2021 15:13:11 +0100
+Subject: [PATCH] Expand EntityUnleashEvent
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
+index 1d2f0f8756addf0db7356b47ea8a1eddd2c4503d..42004784a5421bd27d0a4a2bf1c17d14341fc5a4 100644
+--- a/src/main/java/net/minecraft/world/entity/Entity.java
++++ b/src/main/java/net/minecraft/world/entity/Entity.java
+@@ -2689,12 +2689,15 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+ if (leashable.getLeashHolder() == player) {
+ if (!this.level().isClientSide()) {
+ // CraftBukkit start - fire PlayerUnleashEntityEvent
+- if (CraftEventFactory.callPlayerUnleashEntityEvent(this, player, hand).isCancelled()) {
++ // Paper start - Expand EntityUnleashEvent
++ org.bukkit.event.player.PlayerUnleashEntityEvent event = CraftEventFactory.callPlayerUnleashEntityEvent(this, player, hand, !player.hasInfiniteMaterials());
++ if (event.isCancelled()) {
++ // Paper end - Expand EntityUnleashEvent
+ ((ServerPlayer) player).connection.send(new ClientboundSetEntityLinkPacket(this, leashable.getLeashHolder()));
+ return InteractionResult.PASS;
+ }
+ // CraftBukkit end
+- leashable.dropLeash(true, !player.hasInfiniteMaterials());
++ leashable.dropLeash(true, event.isDropLeash()); // Paper - Expand EntityUnleashEvent
+ this.gameEvent(GameEvent.ENTITY_INTERACT, player);
+ }
+
+@@ -3659,9 +3662,12 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+
+ protected void removeAfterChangingDimensions() {
+ this.setRemoved(Entity.RemovalReason.CHANGED_DIMENSION, null); // CraftBukkit - add Bukkit remove cause
+- if (this instanceof Leashable leashable) {
+- this.level().getCraftServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), UnleashReason.UNKNOWN)); // CraftBukkit
+- leashable.dropLeash(true, false);
++ if (this instanceof Leashable leashable && leashable.isLeashed()) { // Paper - only call if it is leashed
++ // Paper start - Expand EntityUnleashEvent
++ final EntityUnleashEvent event = new EntityUnleashEvent(this.getBukkitEntity(), UnleashReason.UNKNOWN, false); // CraftBukkit
++ event.callEvent();
++ leashable.dropLeash(true, event.isDropLeash());
++ // Paper end - Expand EntityUnleashEvent
+ }
+
+ }
+diff --git a/src/main/java/net/minecraft/world/entity/Leashable.java b/src/main/java/net/minecraft/world/entity/Leashable.java
+index dc39ecc3e1aada638337d31bfe68b400c6454af7..1a6448cccf79a94013f9f44c3067d91da3da1f7e 100644
+--- a/src/main/java/net/minecraft/world/entity/Leashable.java
++++ b/src/main/java/net/minecraft/world/entity/Leashable.java
+@@ -167,8 +167,11 @@ public interface Leashable {
+
+ if (leashable_a != null && leashable_a.leashHolder != null) {
+ if (!entity.isAlive() || !leashable_a.leashHolder.isAlive()) {
+- world.getCraftServer().getPluginManager().callEvent(new EntityUnleashEvent(entity.getBukkitEntity(), (!entity.isAlive()) ? UnleashReason.PLAYER_UNLEASH : UnleashReason.HOLDER_GONE)); // CraftBukkit
++ // Paper start - Expand EntityUnleashEvent
++ final EntityUnleashEvent event = new EntityUnleashEvent(entity.getBukkitEntity(), (!entity.isAlive()) ? UnleashReason.PLAYER_UNLEASH : UnleashReason.HOLDER_GONE, world.getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS) && !entity.pluginRemoved);
++ event.callEvent();
+ Leashable.dropLeash(entity, true, world.getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS) && !entity.pluginRemoved); // CraftBukkit - SPIGOT-7487: Don't drop leash, when the holder was removed by a plugin
++ // Paper end - Expand EntityUnleashEvent
+ }
+
+ Entity entity1 = ((Leashable) entity).getLeashHolder();
+@@ -199,11 +202,16 @@ public interface Leashable {
+
+ default void leashTooFarBehaviour() {
+ // CraftBukkit start
++ boolean dropLeash = true; // Paper
+ if (this instanceof Entity entity) {
+- entity.level().getCraftServer().getPluginManager().callEvent(new EntityUnleashEvent(entity.getBukkitEntity(), EntityUnleashEvent.UnleashReason.DISTANCE));
++ // Paper start - Expand EntityUnleashEvent
++ final EntityUnleashEvent event = new EntityUnleashEvent(entity.getBukkitEntity(), EntityUnleashEvent.UnleashReason.DISTANCE, true);
++ if (!event.callEvent()) return;
++ dropLeash = event.isDropLeash();
++ // Paper end - Expand EntityUnleashEvent
+ }
+ // CraftBukkit end
+- this.dropLeash(true, true);
++ this.dropLeash(true, dropLeash); // Paper
+ }
+
+ default void closeRangeLeashBehaviour(Entity entity) {}
+diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java
+index a8ab3c03a6f96658ce2a3f5758225954a36de6a9..ac7a52e77fd2fcbe9f95709b95ba54f8c2a6514b 100644
+--- a/src/main/java/net/minecraft/world/entity/Mob.java
++++ b/src/main/java/net/minecraft/world/entity/Mob.java
+@@ -1613,8 +1613,11 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab
+ boolean flag1 = super.startRiding(entity, force);
+
+ if (flag1 && this.isLeashed()) {
+- this.level().getCraftServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), UnleashReason.UNKNOWN)); // CraftBukkit
+- this.dropLeash(true, true);
++ // Paper start - Expand EntityUnleashEvent
++ EntityUnleashEvent event = new EntityUnleashEvent(this.getBukkitEntity(), EntityUnleashEvent.UnleashReason.UNKNOWN, true);
++ if (!event.callEvent()) { return flag1; }
++ this.dropLeash(true, event.isDropLeash());
++ // Paper end - Expand EntityUnleashEvent
+ }
+
+ return flag1;
+diff --git a/src/main/java/net/minecraft/world/entity/TamableAnimal.java b/src/main/java/net/minecraft/world/entity/TamableAnimal.java
+index d2785628368b65854b6e1f35005c478d490a2f8c..cd565d1a8dab8d45196e4d29cab3d93a3ca619eb 100644
+--- a/src/main/java/net/minecraft/world/entity/TamableAnimal.java
++++ b/src/main/java/net/minecraft/world/entity/TamableAnimal.java
+@@ -99,7 +99,11 @@ public abstract class TamableAnimal extends Animal implements OwnableEntity {
+ public boolean handleLeashAtDistance(Entity leashHolder, float distance) {
+ if (this.isInSittingPose()) {
+ if (distance > (float) this.level().paperConfig().misc.maxLeashDistance.or(Leashable.LEASH_TOO_FAR_DIST)) { // Paper - Configurable max leash distance
+- this.dropLeash(true, true);
++ // Paper start - Expand EntityUnleashEvent
++ org.bukkit.event.entity.EntityUnleashEvent event = new org.bukkit.event.entity.EntityUnleashEvent(this.getBukkitEntity(), org.bukkit.event.entity.EntityUnleashEvent.UnleashReason.DISTANCE, true);
++ if (!event.callEvent()) return false;
++ this.dropLeash(true, event.isDropLeash());
++ // Paper end - Expand EntityUnleashEvent
+ }
+
+ return false;
+diff --git a/src/main/java/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java b/src/main/java/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java
+index 4b44830ef5d08887274ebb39e2780606fe3a76b4..d3a7953a3f42a0020342845e9107c6991637b050 100644
+--- a/src/main/java/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java
++++ b/src/main/java/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java
+@@ -119,13 +119,18 @@ public class LeashFenceKnotEntity extends BlockAttachedEntity {
+
+ if (leashable1.isLeashed() && leashable1.getLeashHolder() == this) {
+ // CraftBukkit start
++ boolean dropLeash = !player.hasInfiniteMaterials();
+ if (leashable1 instanceof Entity leashed) {
+- if (CraftEventFactory.callPlayerUnleashEntityEvent(leashed, player, hand).isCancelled()) {
++ // Paper start - Expand EntityUnleashEvent
++ org.bukkit.event.player.PlayerUnleashEntityEvent event = CraftEventFactory.callPlayerUnleashEntityEvent(leashed, player, hand, dropLeash);
++ dropLeash = event.isDropLeash();
++ if (event.isCancelled()) {
++ // Paper end - Expand EntityUnleashEvent
+ die = false;
+ continue;
+ }
+ }
+- leashable1.dropLeash(true, !player.getAbilities().instabuild); // false -> survival mode boolean
++ leashable1.dropLeash(true, dropLeash); // false -> survival mode boolean // Paper - Expand EntityUnleashEvent
+ // CraftBukkit end
+ flag1 = true;
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
+index 12aeb922c8a53eff9ba8e2c765d87df497611362..cee72eb02ebba4f50a117876351b8f516ba12057 100644
+--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
+@@ -1596,8 +1596,10 @@ populateFields(victim, event); // Paper - make cancellable
+ Bukkit.getPluginManager().callEvent(new PlayerRecipeBookSettingsChangeEvent(player.getBukkitEntity(), bukkitType, open, filter));
+ }
+
+- public static PlayerUnleashEntityEvent callPlayerUnleashEntityEvent(Entity entity, net.minecraft.world.entity.player.Player player, InteractionHand enumhand) {
+- PlayerUnleashEntityEvent event = new PlayerUnleashEntityEvent(entity.getBukkitEntity(), (Player) player.getBukkitEntity(), CraftEquipmentSlot.getHand(enumhand));
++ // Paper start - Expand EntityUnleashEvent
++ public static PlayerUnleashEntityEvent callPlayerUnleashEntityEvent(Entity entity, net.minecraft.world.entity.player.Player player, InteractionHand enumhand, boolean dropLeash) {
++ PlayerUnleashEntityEvent event = new PlayerUnleashEntityEvent(entity.getBukkitEntity(), (Player) player.getBukkitEntity(), CraftEquipmentSlot.getHand(enumhand), dropLeash);
++ // Paper end - Expand EntityUnleashEvent
+ entity.level().getCraftServer().getPluginManager().callEvent(event);
+ return event;
+ }
diff --git a/patches/server/0478-Reset-shield-blocking-on-dimension-change.patch b/patches/server/0478-Reset-shield-blocking-on-dimension-change.patch
new file mode 100644
index 0000000000..c1bc7de4e1
--- /dev/null
+++ b/patches/server/0478-Reset-shield-blocking-on-dimension-change.patch
@@ -0,0 +1,22 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Yive <[email protected]>
+Date: Sun, 24 Jan 2021 08:55:19 -0800
+Subject: [PATCH] Reset shield blocking on dimension change
+
+
+diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
+index 0ae490bb9b4cd781876054d0824c5392060208eb..3a2a4f6bc1de45c0dc2020357ee308064666f8d5 100644
+--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
+@@ -1613,6 +1613,11 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player {
+ PlayerChangedWorldEvent changeEvent = new PlayerChangedWorldEvent(this.getBukkitEntity(), worldserver1.getWorld());
+ this.level().getCraftServer().getPluginManager().callEvent(changeEvent);
+ // CraftBukkit end
++ // Paper start - Reset shield blocking on dimension change
++ if (this.isBlocking()) {
++ this.stopUsingItem();
++ }
++ // Paper end - Reset shield blocking on dimension change
+ return this;
+ }
+ }
diff --git a/patches/server/0479-Add-DragonEggFormEvent.patch b/patches/server/0479-Add-DragonEggFormEvent.patch
new file mode 100644
index 0000000000..34506b3c79
--- /dev/null
+++ b/patches/server/0479-Add-DragonEggFormEvent.patch
@@ -0,0 +1,34 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Trigary <[email protected]>
+Date: Mon, 25 Jan 2021 14:53:57 +0100
+Subject: [PATCH] Add DragonEggFormEvent
+
+
+diff --git a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java
+index 0e1eceb2b83aaafccbb5d58cf5098cfbc6f25a54..3d1a49d865e17a61ff74c6fe0efbd908447ee730 100644
+--- a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java
++++ b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java
+@@ -410,8 +410,22 @@ public class EndDragonFight {
+ this.dragonEvent.setVisible(false);
+ this.spawnExitPortal(true);
+ this.spawnNewGateway();
++ // Paper start - Add DragonEggFormEvent
++ BlockPos eggPosition = this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, EndPodiumFeature.getLocation(this.origin));
++ org.bukkit.craftbukkit.block.CraftBlockState eggState = org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(this.level, eggPosition);
++ eggState.setData(Blocks.DRAGON_EGG.defaultBlockState());
++ io.papermc.paper.event.block.DragonEggFormEvent eggEvent = new io.papermc.paper.event.block.DragonEggFormEvent(org.bukkit.craftbukkit.block.CraftBlock.at(this.level, eggPosition), eggState,
++ new org.bukkit.craftbukkit.boss.CraftDragonBattle(this));
++ // Paper end - Add DragonEggFormEvent
+ if (this.level.paperConfig().entities.behavior.enderDragonsDeathAlwaysPlacesDragonEgg || !this.previouslyKilled) { // Paper - Add toggle for always placing the dragon egg
+- this.level.setBlockAndUpdate(this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, EndPodiumFeature.getLocation(this.origin)), Blocks.DRAGON_EGG.defaultBlockState());
++ // Paper start - Add DragonEggFormEvent
++ // this.level.setBlockAndUpdate(this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, EndPodiumFeature.getLocation(this.origin)), Blocks.DRAGON_EGG.defaultBlockState());
++ } else {
++ eggEvent.setCancelled(true);
++ }
++ if (eggEvent.callEvent()) {
++ eggEvent.getNewState().update(true);
++ // Paper end - Add DragonEggFormEvent
+ }
+
+ this.previouslyKilled = true;
diff --git a/patches/server/0480-Add-EntityMoveEvent.patch b/patches/server/0480-Add-EntityMoveEvent.patch
new file mode 100644
index 0000000000..7eb0eacb2e
--- /dev/null
+++ b/patches/server/0480-Add-EntityMoveEvent.patch
@@ -0,0 +1,55 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath <[email protected]>
+Date: Tue, 11 Feb 2020 21:56:48 -0600
+Subject: [PATCH] Add EntityMoveEvent
+
+
+diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
+index 058719f8b768b5a1227a19927d0f632815190a91..985f345cdf9f69140df9210be6eca477d911f8a9 100644
+--- a/src/main/java/net/minecraft/server/MinecraftServer.java
++++ b/src/main/java/net/minecraft/server/MinecraftServer.java
+@@ -1683,6 +1683,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+ while (iterator.hasNext()) {
+ ServerLevel worldserver = (ServerLevel) iterator.next();
+ worldserver.hasPhysicsEvent = org.bukkit.event.block.BlockPhysicsEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - BlockPhysicsEvent
++ worldserver.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent
+
+ gameprofilerfiller.push(() -> {
+ String s = String.valueOf(worldserver);
+diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
+index d62d6a345837e1b63c1a1393f18e367ac0ef4c30..b91ed08e8d9fbd399834d19ef01ebe5692d1ee4a 100644
+--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
+@@ -231,6 +231,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+ public final LevelStorageSource.LevelStorageAccess convertable;
+ public final UUID uuid;
+ public boolean hasPhysicsEvent = true; // Paper - BlockPhysicsEvent
++ public boolean hasEntityMoveEvent; // Paper - Add EntityMoveEvent
+
+ public LevelChunk getChunkIfLoaded(int x, int z) {
+ return this.chunkSource.getChunk(x, z, false);
+diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java
+index 7ea58478d3cde175a056f41cf945636a04128828..293490d124bc06c4a06b9f4a7f77a9c25a8d7d39 100644
+--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java
++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java
+@@ -3628,6 +3628,20 @@ public abstract class LivingEntity extends Entity implements Attackable {
+
+ this.pushEntities();
+ gameprofilerfiller.pop();
++ // Paper start - Add EntityMoveEvent
++ if (((ServerLevel) this.level()).hasEntityMoveEvent && !(this instanceof net.minecraft.world.entity.player.Player)) {
++ if (this.xo != this.getX() || this.yo != this.getY() || this.zo != this.getZ() || this.yRotO != this.getYRot() || this.xRotO != this.getXRot()) {
++ Location from = new Location(this.level().getWorld(), this.xo, this.yo, this.zo, this.yRotO, this.xRotO);
++ Location to = new Location(this.level().getWorld(), this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot());
++ io.papermc.paper.event.entity.EntityMoveEvent event = new io.papermc.paper.event.entity.EntityMoveEvent(this.getBukkitLivingEntity(), from, to.clone());
++ if (!event.callEvent()) {
++ this.absMoveTo(from.getX(), from.getY(), from.getZ(), from.getYaw(), from.getPitch());
++ } else if (!to.equals(event.getTo())) {
++ this.absMoveTo(event.getTo().getX(), event.getTo().getY(), event.getTo().getZ(), event.getTo().getYaw(), event.getTo().getPitch());
++ }
++ }
++ }
++ // Paper end - Add EntityMoveEvent
+ world = this.level();
+ if (world instanceof ServerLevel worldserver) {
+ if (this.isSensitiveToWater() && this.isInWaterRainOrBubble()) {
diff --git a/patches/server/0481-added-option-to-disable-pathfinding-updates-on-block.patch b/patches/server/0481-added-option-to-disable-pathfinding-updates-on-block.patch
new file mode 100644
index 0000000000..3850327834
--- /dev/null
+++ b/patches/server/0481-added-option-to-disable-pathfinding-updates-on-block.patch
@@ -0,0 +1,26 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: lukas81298 <[email protected]>
+Date: Mon, 25 Jan 2021 14:37:57 +0100
+Subject: [PATCH] added option to disable pathfinding updates on block changes
+
+
+diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
+index b91ed08e8d9fbd399834d19ef01ebe5692d1ee4a..d696ec118367f64fa7151189a4ace58287329851 100644
+--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
+@@ -1387,6 +1387,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+
+ this.getChunkSource().blockChanged(pos);
+ this.pathTypesByPosCache.invalidate(pos);
++ if (this.paperConfig().misc.updatePathfindingOnBlockUpdate) { // Paper - option to disable pathfinding updates
+ VoxelShape voxelshape = oldState.getCollisionShape(this, pos);
+ VoxelShape voxelshape1 = newState.getCollisionShape(this, pos);
+
+@@ -1428,6 +1429,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+ }
+
+ }
++ } // Paper - option to disable pathfinding updates
+ }
+
+ @Override
diff --git a/patches/server/0482-Inline-shift-direction-fields.patch b/patches/server/0482-Inline-shift-direction-fields.patch
new file mode 100644
index 0000000000..22234bc09a
--- /dev/null
+++ b/patches/server/0482-Inline-shift-direction-fields.patch
@@ -0,0 +1,56 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Andrew Steinborn <[email protected]>
+Date: Mon, 18 Jan 2021 20:45:25 -0500
+Subject: [PATCH] Inline shift direction fields
+
+Removes a layer of indirection for EnumDirection.getAdjacent(X|Y|Z)(), which is in the
+critical section for much of the server, including the lighting engine.
+
+diff --git a/src/main/java/net/minecraft/core/Direction.java b/src/main/java/net/minecraft/core/Direction.java
+index 69ccada8ea78c05d3fb886222698c07dee421506..3fde5abde736b2c19d8819d9aec0397a0245ccd1 100644
+--- a/src/main/java/net/minecraft/core/Direction.java
++++ b/src/main/java/net/minecraft/core/Direction.java
+@@ -57,6 +57,12 @@ public enum Direction implements StringRepresentable {
+ .sorted(Comparator.comparingInt(direction -> direction.data2d))
+ .toArray(Direction[]::new);
+
++ // Paper start - Perf: Inline shift direction fields
++ private final int adjX;
++ private final int adjY;
++ private final int adjZ;
++ // Paper end - Perf: Inline shift direction fields
++
+ private Direction(
+ final int id,
+ final int idOpposite,
+@@ -74,6 +80,11 @@ public enum Direction implements StringRepresentable {
+ this.axisDirection = direction;
+ this.normal = vector;
+ this.normalVec3 = Vec3.atLowerCornerOf(vector);
++ // Paper start - Perf: Inline shift direction fields
++ this.adjX = vector.getX();
++ this.adjY = vector.getY();
++ this.adjZ = vector.getZ();
++ // Paper end - Perf: Inline shift direction fields
+ }
+
+ public static Direction[] orderedByNearest(Entity entity) {
+@@ -247,15 +258,15 @@ public enum Direction implements StringRepresentable {
+ }
+
+ public int getStepX() {
+- return this.normal.getX();
++ return this.adjX; // Paper - Perf: Inline shift direction fields
+ }
+
+ public int getStepY() {
+- return this.normal.getY();
++ return this.adjY; // Paper - Perf: Inline shift direction fields
+ }
+
+ public int getStepZ() {
+- return this.normal.getZ();
++ return this.adjZ; // Paper - Perf: Inline shift direction fields
+ }
+
+ public Vector3f step() {
diff --git a/patches/server/0483-Allow-adding-items-to-BlockDropItemEvent.patch b/patches/server/0483-Allow-adding-items-to-BlockDropItemEvent.patch
new file mode 100644
index 0000000000..4f9534949b
--- /dev/null
+++ b/patches/server/0483-Allow-adding-items-to-BlockDropItemEvent.patch
@@ -0,0 +1,44 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: BillyGalbreath <[email protected]>
+Date: Wed, 20 Jan 2021 14:23:37 -0600
+Subject: [PATCH] Allow adding items to BlockDropItemEvent
+
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
+index cee72eb02ebba4f50a117876351b8f516ba12057..1ce637a2b80dfc5c3da20f7bd95b97f4718a07d4 100644
+--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
+@@ -461,13 +461,30 @@ public class CraftEventFactory {
+ }
+
+ public static void handleBlockDropItemEvent(Block block, BlockState state, ServerPlayer player, List<ItemEntity> items) {
+- BlockDropItemEvent event = new BlockDropItemEvent(block, state, player.getBukkitEntity(), Lists.transform(items, (item) -> (org.bukkit.entity.Item) item.getBukkitEntity()));
++ // Paper start - Allow adding items to BlockDropItemEvent
++ List<Item> list = new ArrayList<>();
++ for (ItemEntity item : items) {
++ list.add((Item) item.getBukkitEntity());
++ }
++ BlockDropItemEvent event = new BlockDropItemEvent(block, state, player.getBukkitEntity(), list);
++ // Paper end - Allow adding items to BlockDropItemEvent
+ Bukkit.getPluginManager().callEvent(event);
+
+ if (!event.isCancelled()) {
+- for (ItemEntity item : items) {
+- item.level().addFreshEntity(item);
++ // Paper start - Allow adding items to BlockDropItemEvent
++ for (Item bukkit : list) {
++ if (!bukkit.isValid()) {
++ Entity item = ((org.bukkit.craftbukkit.entity.CraftItem) bukkit).getHandle();
++ item.level().addFreshEntity(item);
++ }
++ }
++ } else {
++ for (Item bukkit : list) {
++ if (bukkit.isValid()) {
++ bukkit.remove();
++ }
+ }
++ // Paper end - Allow adding items to BlockDropItemEvent
+ }
+ }
+
diff --git a/patches/server/0484-Add-getMainThreadExecutor-to-BukkitScheduler.patch b/patches/server/0484-Add-getMainThreadExecutor-to-BukkitScheduler.patch
new file mode 100644
index 0000000000..e2b8629ca8
--- /dev/null
+++ b/patches/server/0484-Add-getMainThreadExecutor-to-BukkitScheduler.patch
@@ -0,0 +1,26 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Aleksander Jagiello <[email protected]>
+Date: Sun, 24 Jan 2021 22:17:54 +0100
+Subject: [PATCH] Add getMainThreadExecutor to BukkitScheduler
+
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java
+index fdfdcac6644e5343fb1f1cbe5d9aa76a79627046..5fc88196b2c873427c817e9802ad3b12009f265f 100644
+--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java
++++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java
+@@ -642,4 +642,15 @@ public class CraftScheduler implements BukkitScheduler {
+ public BukkitTask runTaskTimerAsynchronously(Plugin plugin, BukkitRunnable task, long delay, long period) throws IllegalArgumentException {
+ throw new UnsupportedOperationException("Use BukkitRunnable#runTaskTimerAsynchronously(Plugin, long, long)");
+ }
++
++ // Paper start - add getMainThreadExecutor
++ @Override
++ public Executor getMainThreadExecutor(Plugin plugin) {
++ Preconditions.checkArgument(plugin != null, "Plugin cannot be null");
++ return command -> {
++ Preconditions.checkArgument(command != null, "Command cannot be null");
++ this.runTask(plugin, command);
++ };
++ }
++ // Paper end
+ }
diff --git a/patches/server/0485-living-entity-allow-attribute-registration.patch b/patches/server/0485-living-entity-allow-attribute-registration.patch
new file mode 100644
index 0000000000..e9e4ab2f48
--- /dev/null
+++ b/patches/server/0485-living-entity-allow-attribute-registration.patch
@@ -0,0 +1,57 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: ysl3000 <[email protected]>
+Date: Sat, 24 Oct 2020 16:37:44 +0200
+Subject: [PATCH] living entity allow attribute registration
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java b/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java
+index 3a6c55a7f07e8871a77c91679732dd63db604004..94d04a20f97405e02d7cccaabadc7a7e86e336f7 100644
+--- a/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java
++++ b/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java
+@@ -149,4 +149,12 @@ public class AttributeMap {
+ }
+ }
+ }
++
++ // Paper - start - living entity allow attribute registration
++ public void registerAttribute(Holder<Attribute> attributeBase) {
++ AttributeInstance attributeModifiable = new AttributeInstance(attributeBase, AttributeInstance::getAttribute);
++ attributes.put(attributeBase, attributeModifiable);
++ }
++ // Paper - end - living entity allow attribute registration
++
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java b/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java
+index 5678d2007d5adf45dec0638c5dd848b601801814..0a7ed5a4f1644a70d8f98ad7a6962b814ad6daf4 100644
+--- a/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java
++++ b/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java
+@@ -35,4 +35,11 @@ public class CraftAttributeMap implements Attributable {
+
+ return (nms == null) ? null : new CraftAttributeInstance(nms, attribute);
+ }
++ // Paper start - living entity allow attribute registration
++ @Override
++ public void registerAttribute(Attribute attribute) {
++ Preconditions.checkArgument(attribute != null, "attribute");
++ handle.registerAttribute(CraftAttribute.bukkitToMinecraftHolder(attribute));
++ }
++ // Paper end - living entity allow attribute registration
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java
+index ff3b53eff8f5fc1e02e7b30d59ff27dfe8f5d431..5749e2b5174be23633c8a811baec8c05da12e3e2 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java
+@@ -775,6 +775,13 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity {
+ return this.getHandle().craftAttributes.getAttribute(attribute);
+ }
+
++ // Paper start - living entity allow attribute registration
++ @Override
++ public void registerAttribute(Attribute attribute) {
++ getHandle().craftAttributes.registerAttribute(attribute);
++ }
++ // Paper end - living entity allow attribute registration
++
+ @Override
+ public void setAI(boolean ai) {
+ if (this.getHandle() instanceof Mob) {
diff --git a/patches/server/0486-fix-dead-slime-setSize-invincibility.patch b/patches/server/0486-fix-dead-slime-setSize-invincibility.patch
new file mode 100644
index 0000000000..494e56127e
--- /dev/null
+++ b/patches/server/0486-fix-dead-slime-setSize-invincibility.patch
@@ -0,0 +1,19 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Trigary <[email protected]>
+Date: Fri, 5 Feb 2021 22:12:13 +0100
+Subject: [PATCH] fix dead slime setSize invincibility
+
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSlime.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSlime.java
+index 3d9b7c0e128ea05bec5600c774e9685998b71cac..e48f7d1cbec4a2319745ba48a5d44ab9925214e2 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSlime.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSlime.java
+@@ -16,7 +16,7 @@ public class CraftSlime extends CraftMob implements Slime, CraftEnemy {
+
+ @Override
+ public void setSize(int size) {
+- this.getHandle().setSize(size, true);
++ this.getHandle().setSize(size, /* true */ getHandle().isAlive()); // Paper - fix dead slime setSize invincibility
+ }
+
+ @Override
diff --git a/patches/server/0487-Merchant-getRecipes-should-return-an-immutable-list.patch b/patches/server/0487-Merchant-getRecipes-should-return-an-immutable-list.patch
new file mode 100644
index 0000000000..d40a7cf8d7
--- /dev/null
+++ b/patches/server/0487-Merchant-getRecipes-should-return-an-immutable-list.patch
@@ -0,0 +1,19 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jason Penilla <[email protected]>
+Date: Wed, 10 Feb 2021 14:53:36 -0800
+Subject: [PATCH] Merchant#getRecipes should return an immutable list
+
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchant.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchant.java
+index 770686757a87d923ecb0361ba0939b4f7d184d76..72b5f6724278ec78605d7f435ef7ca340f195f5b 100644
+--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchant.java
++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchant.java
+@@ -16,7 +16,7 @@ public interface CraftMerchant extends Merchant {
+
+ @Override
+ default List<MerchantRecipe> getRecipes() {
+- return Collections.unmodifiableList(Lists.transform(this.getMerchant().getOffers(), new Function<net.minecraft.world.item.trading.MerchantOffer, MerchantRecipe>() {
++ return List.copyOf(Lists.transform(this.getMerchant().getOffers(), new Function<net.minecraft.world.item.trading.MerchantOffer, MerchantRecipe>() { // Paper - javadoc says 'an immutable list of trades' - not 'an unmodifiable view of a list of trades'. fixes issue with setRecipes(getRecipes())
+ @Override
+ public MerchantRecipe apply(net.minecraft.world.item.trading.MerchantOffer recipe) {
+ return recipe.asBukkit();
diff --git a/patches/server/0488-Expose-Tracked-Players.patch b/patches/server/0488-Expose-Tracked-Players.patch
new file mode 100644
index 0000000000..2835731302
--- /dev/null
+++ b/patches/server/0488-Expose-Tracked-Players.patch
@@ -0,0 +1,32 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Tom <[email protected]>
+Date: Fri, 26 Feb 2021 16:24:25 -0600
+Subject: [PATCH] Expose Tracked Players
+
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
+index 925626b74005ec9c031c3773171f7684ecc9ccd3..06fda053874e7ef0c8995b1a55e5f518b28eebc9 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
+@@ -1070,4 +1070,21 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity {
+ return getHandle().isTicking();
+ }
+ // Paper end - isTicking API
++
++ // Paper start - tracked players API
++ @Override
++ public Set<org.bukkit.entity.Player> getTrackedPlayers() {
++ ServerLevel world = (net.minecraft.server.level.ServerLevel)this.entity.level();
++ ChunkMap.TrackedEntity tracker = world == null ? null : world.getChunkSource().chunkMap.entityMap.get(this.entity.getId());
++ if (tracker == null) {
++ return java.util.Collections.emptySet();
++ }
++
++ Set<org.bukkit.entity.Player> set = new java.util.HashSet<>(tracker.seenBy.size());
++ for (net.minecraft.server.network.ServerPlayerConnection connection : tracker.seenBy) {
++ set.add(connection.getPlayer().getBukkitEntity().getPlayer());
++ }
++ return set;
++ }
++ // Paper end - tracked players API
+ }
diff --git a/patches/server/0489-Improve-ServerGUI.patch b/patches/server/0489-Improve-ServerGUI.patch
new file mode 100644
index 0000000000..8dde1d1a9b
--- /dev/null
+++ b/patches/server/0489-Improve-ServerGUI.patch
@@ -0,0 +1,431 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: AlexProgrammerDE <[email protected]>
+Date: Sat, 3 Oct 2020 08:27:40 +0200
+Subject: [PATCH] Improve ServerGUI
+
+- Added logo to server frame
+- Show tps in the server stats
+
+diff --git a/src/main/java/com/destroystokyo/paper/gui/RAMDetails.java b/src/main/java/com/destroystokyo/paper/gui/RAMDetails.java
+index f93373d28d741e1f8a53e07b4e328ce9c4e1657f..12b327eea95e0de9e9c39b7d039badee8ec46508 100644
+--- a/src/main/java/com/destroystokyo/paper/gui/RAMDetails.java
++++ b/src/main/java/com/destroystokyo/paper/gui/RAMDetails.java
+@@ -58,9 +58,22 @@ public class RAMDetails extends JList<String> {
+ public void update() {
+ GraphData data = RAMGraph.DATA.peekLast();
+ Vector<String> vector = new Vector<>();
++
++ // Follows CraftServer#getTPS
++ double[] tps = new double[] {
++ server.tps1.getAverage(),
++ server.tps5.getAverage(),
++ server.tps15.getAverage()
++ };
++ String[] tpsAvg = new String[tps.length];
++
++ for ( int g = 0; g < tps.length; g++) {
++ tpsAvg[g] = format( tps[g] );
++ }
+ vector.add("Memory use: " + (data.getUsedMem() / 1024L / 1024L) + " mb (" + (data.getFree() * 100L / data.getMax()) + "% free)");
+ vector.add("Heap: " + (data.getTotal() / 1024L / 1024L) + " / " + (data.getMax() / 1024L / 1024L) + " mb");
+ vector.add("Avg tick: " + DECIMAL_FORMAT.format((double)this.server.getAverageTickTimeNanos() / (double) TimeUtil.NANOSECONDS_PER_MILLISECOND) + " ms");
++ vector.add("TPS from last 1m, 5m, 15m: " + String.join(", ", tpsAvg));
+ setListData(vector);
+ }
+
+@@ -71,4 +84,8 @@ public class RAMDetails extends JList<String> {
+ }
+ return ((double) total / (double) tickTimes.length) * 1.0E-6D;
+ }
++
++ private static String format(double tps) {
++ return ( ( tps > 21.0 ) ? "*" : "" ) + Math.min( Math.round( tps * 100.0 ) / 100.0, 20.0 );
++ }
+ }
+diff --git a/src/main/java/net/minecraft/server/gui/MinecraftServerGui.java b/src/main/java/net/minecraft/server/gui/MinecraftServerGui.java
+index 8b570b0c3967a22c085f390110cb29cbd9c8feff..4d3fe4f56e0b264fa030409919caf52d5f421d46 100644
+--- a/src/main/java/net/minecraft/server/gui/MinecraftServerGui.java
++++ b/src/main/java/net/minecraft/server/gui/MinecraftServerGui.java
+@@ -59,6 +59,15 @@ public class MinecraftServerGui extends JComponent {
+ jframe.pack();
+ jframe.setLocationRelativeTo((Component) null);
+ jframe.setVisible(true);
++ jframe.setName("Minecraft server"); // Paper - Improve ServerGUI
++
++ // Paper start - Improve ServerGUI
++ try {
++ jframe.setIconImage(javax.imageio.ImageIO.read(Objects.requireNonNull(MinecraftServerGui.class.getClassLoader().getResourceAsStream("logo.png"))));
++ } catch (java.io.IOException ignore) {
++ }
++ // Paper end - Improve ServerGUI
++
+ jframe.addWindowListener(new WindowAdapter() {
+ public void windowClosing(WindowEvent windowevent) {
+ if (!servergui.isClosing.getAndSet(true)) {
+diff --git a/src/main/java/net/minecraft/server/gui/StatsComponent.java b/src/main/java/net/minecraft/server/gui/StatsComponent.java
+index 6e9c6d556ed55325e36d191fc9d1508c00879671..096c89bd01cec2abd151bf6fffc4847d1bcd548f 100644
+--- a/src/main/java/net/minecraft/server/gui/StatsComponent.java
++++ b/src/main/java/net/minecraft/server/gui/StatsComponent.java
+@@ -34,10 +34,19 @@ public class StatsComponent extends JComponent {
+
+ private void tick() {
+ long l = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
++ // Paper start - Improve ServerGUI
++ double[] tps = org.bukkit.Bukkit.getTPS();
++ String[] tpsAvg = new String[tps.length];
++
++ for ( int g = 0; g < tps.length; g++) {
++ tpsAvg[g] = format( tps[g] );
++ }
+ this.msgs[0] = "Memory use: " + l / 1024L / 1024L + " mb (" + Runtime.getRuntime().freeMemory() * 100L / Runtime.getRuntime().maxMemory() + "% free)";
+ this.msgs[1] = "Avg tick: "
+ + DECIMAL_FORMAT.format((double)this.server.getAverageTickTimeNanos() / (double)TimeUtil.NANOSECONDS_PER_MILLISECOND)
+ + " ms";
++ this.msgs[2] = "TPS from last 1m, 5m, 15m: " + String.join(", ", tpsAvg);
++ // Paper end - Improve ServerGUI
+ this.values[this.vp++ & 0xFF] = (int)(l * 100L / Runtime.getRuntime().maxMemory());
+ this.repaint();
+ }
+@@ -66,4 +75,10 @@ public class StatsComponent extends JComponent {
+ public void close() {
+ this.timer.stop();
+ }
++
++ // Paper start - Improve ServerGUI
++ private static String format(double tps) {
++ return (( tps > 21.0 ) ? "*" : "") + Math.min(Math.round(tps * 100.0) / 100.0, 20.0); // only print * at 21, we commonly peak to 20.02 as the tick sleep is not accurate enough, stop the noise
++ }
++ // Paper end - Improve ServerGUI
+ }
+diff --git a/src/main/resources/logo.png b/src/main/resources/logo.png
+new file mode 100644
+index 0000000000000000000000000000000000000000..8b924977b7886df9ab8790b1e4ff9b1c04a2af45
+GIT binary patch
+literal 16900
+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
+LFIp>X81%mY^Bg|U
+
+literal 0
+HcmV?d00001
+
diff --git a/patches/server/0490-fix-converting-txt-to-json-file.patch b/patches/server/0490-fix-converting-txt-to-json-file.patch
new file mode 100644
index 0000000000..19ad568347
--- /dev/null
+++ b/patches/server/0490-fix-converting-txt-to-json-file.patch
@@ -0,0 +1,61 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Mon, 4 Jan 2021 19:49:15 -0800
+Subject: [PATCH] fix converting txt to json file
+
+
+diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedPlayerList.java b/src/main/java/net/minecraft/server/dedicated/DedicatedPlayerList.java
+index 929f59bce01c8e6ed4b0b551744d42e131b8fc80..22c4f8dea99f92a1eb3da2baf0a15bf9d2ca0462 100644
+--- a/src/main/java/net/minecraft/server/dedicated/DedicatedPlayerList.java
++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedPlayerList.java
+@@ -18,6 +18,11 @@ public class DedicatedPlayerList extends PlayerList {
+ this.setViewDistance(dedicatedServerProperties.viewDistance);
+ this.setSimulationDistance(dedicatedServerProperties.simulationDistance);
+ super.setUsingWhiteList(dedicatedServerProperties.whiteList.get());
++ // Paper start - fix converting txt to json file; moved from constructor
++ }
++ @Override
++ public void loadAndSaveFiles() {
++ // Paper end - fix converting txt to json file
+ this.loadUserBanList();
+ this.saveUserBanList();
+ this.loadIpBanList();
+diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
+index 20a3138c6c2a6c8ada8b6008913abae0eea76f2d..e95d592b6001dd4320c58133d841359f99c2d9c4 100644
+--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
+@@ -215,6 +215,12 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
+ this.paperConfigurations.initializeGlobalConfiguration(this.registryAccess());
+ this.paperConfigurations.initializeWorldDefaultsConfiguration(this.registryAccess());
+ // Paper end - initialize global and world-defaults configuration
++ // Paper start - fix converting txt to json file; convert old users earlier after PlayerList creation but before file load/save
++ if (this.convertOldUsers()) {
++ this.getProfileCache().save(false); // Paper
++ }
++ this.getPlayerList().loadAndSaveFiles(); // Must be after convertNames
++ // Paper end - fix converting txt to json file
+ org.spigotmc.WatchdogThread.doStart(org.spigotmc.SpigotConfig.timeoutTime, org.spigotmc.SpigotConfig.restartOnCrash); // Paper - start watchdog thread
+ io.papermc.paper.command.PaperCommands.registerCommands(this); // Paper - setup /paper command
+ com.destroystokyo.paper.Metrics.PaperMetrics.startMetrics(); // Paper - start metrics
+@@ -269,9 +275,6 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
+ DedicatedServer.LOGGER.warn("To change this, set \"online-mode\" to \"true\" in the server.properties file.");
+ }
+
+- if (this.convertOldUsers()) {
+- this.getProfileCache().save(false); // Paper - Perf: Async GameProfileCache saving
+- }
+
+ if (!OldUsersConverter.serverReadyAfterUserconversion(this)) {
+ return false;
+diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
+index 7676dbe55b4bf6e0472dc0190c01e6ecfcbb464e..26e0414645f7ab11ca3e77c7c5e458612625aee9 100644
+--- a/src/main/java/net/minecraft/server/players/PlayerList.java
++++ b/src/main/java/net/minecraft/server/players/PlayerList.java
+@@ -180,6 +180,7 @@ public abstract class PlayerList {
+ this.maxPlayers = maxPlayers;
+ this.playerIo = saveHandler;
+ }
++ abstract public void loadAndSaveFiles(); // Paper - fix converting txt to json file; moved from DedicatedPlayerList constructor
+
+ public void placeNewPlayer(Connection connection, ServerPlayer player, CommonListenerCookie clientData) {
+ player.isRealPlayer = true; // Paper
diff --git a/patches/server/0491-Add-worldborder-events.patch b/patches/server/0491-Add-worldborder-events.patch
new file mode 100644
index 0000000000..014dba4c88
--- /dev/null
+++ b/patches/server/0491-Add-worldborder-events.patch
@@ -0,0 +1,72 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Mon, 4 Jan 2021 22:40:34 -0800
+Subject: [PATCH] Add worldborder events
+
+
+diff --git a/src/main/java/net/minecraft/world/level/border/WorldBorder.java b/src/main/java/net/minecraft/world/level/border/WorldBorder.java
+index b50090df116697a12f5498d65dd2e5d6d5297fb5..807a097a7b6399f24ede741f94ce98eb67e55add 100644
+--- a/src/main/java/net/minecraft/world/level/border/WorldBorder.java
++++ b/src/main/java/net/minecraft/world/level/border/WorldBorder.java
+@@ -148,6 +148,14 @@ public class WorldBorder {
+ }
+
+ public void setCenter(double x, double z) {
++ // Paper start - Add worldborder events
++ if (this.world != null) {
++ io.papermc.paper.event.world.border.WorldBorderCenterChangeEvent event = new io.papermc.paper.event.world.border.WorldBorderCenterChangeEvent(world.getWorld(), world.getWorld().getWorldBorder(), new org.bukkit.Location(world.getWorld(), this.getCenterX(), 0, this.getCenterZ()), new org.bukkit.Location(world.getWorld(), x, 0, z));
++ if (!event.callEvent()) return;
++ x = event.getNewCenter().getX();
++ z = event.getNewCenter().getZ();
++ }
++ // Paper end - Add worldborder events
+ this.centerX = x;
+ this.centerZ = z;
+ this.extent.onCenterChange();
+@@ -174,6 +182,17 @@ public class WorldBorder {
+ }
+
+ public void setSize(double size) {
++ // Paper start - Add worldborder events
++ if (this.world != null) {
++ io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent event = new io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent(world.getWorld(), world.getWorld().getWorldBorder(), io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent.Type.INSTANT_MOVE, getSize(), size, 0);
++ if (!event.callEvent()) return;
++ if (event.getType() == io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent.Type.STARTED_MOVE && event.getDuration() > 0) { // If changed to a timed transition
++ lerpSizeBetween(event.getOldSize(), event.getNewSize(), event.getDuration());
++ return;
++ }
++ size = event.getNewSize();
++ }
++ // Paper end - Add worldborder events
+ this.extent = new WorldBorder.StaticBorderExtent(size);
+ Iterator iterator = this.getListeners().iterator();
+
+@@ -186,6 +205,20 @@ public class WorldBorder {
+ }
+
+ public void lerpSizeBetween(double fromSize, double toSize, long time) {
++ // Paper start - Add worldborder events
++ if (this.world != null) {
++ io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent.Type type;
++ if (fromSize == toSize) { // new size = old size
++ type = io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent.Type.INSTANT_MOVE; // Use INSTANT_MOVE because below it creates a Static border if they are equal.
++ } else {
++ type = io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent.Type.STARTED_MOVE;
++ }
++ io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent event = new io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent(world.getWorld(), world.getWorld().getWorldBorder(), type, fromSize, toSize, time);
++ if (!event.callEvent()) return;
++ toSize = event.getNewSize();
++ time = event.getDuration();
++ }
++ // Paper end - Add worldborder events
+ this.extent = (WorldBorder.BorderExtent) (fromSize == toSize ? new WorldBorder.StaticBorderExtent(toSize) : new WorldBorder.MovingBorderExtent(fromSize, toSize, time));
+ Iterator iterator = this.getListeners().iterator();
+
+@@ -497,6 +530,7 @@ public class WorldBorder {
+
+ @Override
+ public WorldBorder.BorderExtent update() {
++ if (world != null && this.getLerpRemainingTime() <= 0L) new io.papermc.paper.event.world.border.WorldBorderBoundsChangeFinishEvent(world.getWorld(), world.getWorld().getWorldBorder(), this.from, this.to, this.lerpDuration).callEvent(); // Paper - Add worldborder events
+ return (WorldBorder.BorderExtent) (this.getLerpRemainingTime() <= 0L ? WorldBorder.this.new StaticBorderExtent(this.to) : this);
+ }
+
diff --git a/patches/server/0492-Add-PlayerNameEntityEvent.patch b/patches/server/0492-Add-PlayerNameEntityEvent.patch
new file mode 100644
index 0000000000..4215e4291d
--- /dev/null
+++ b/patches/server/0492-Add-PlayerNameEntityEvent.patch
@@ -0,0 +1,26 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Sun, 5 Jul 2020 00:33:54 -0700
+Subject: [PATCH] Add PlayerNameEntityEvent
+
+
+diff --git a/src/main/java/net/minecraft/world/item/NameTagItem.java b/src/main/java/net/minecraft/world/item/NameTagItem.java
+index dd5aa4bbf2e7bab3be1c3582b090fdf0fa3fb0df..000d1863bfba98b5132dfc6743362d687b2f54f3 100644
+--- a/src/main/java/net/minecraft/world/item/NameTagItem.java
++++ b/src/main/java/net/minecraft/world/item/NameTagItem.java
+@@ -18,8 +18,13 @@ public class NameTagItem extends Item {
+ Component component = stack.get(DataComponents.CUSTOM_NAME);
+ if (component != null && entity.getType().canSerialize()) {
+ if (!user.level().isClientSide && entity.isAlive()) {
+- entity.setCustomName(component);
+- if (entity instanceof Mob mob) {
++ // Paper start - Add PlayerNameEntityEvent
++ io.papermc.paper.event.player.PlayerNameEntityEvent event = new io.papermc.paper.event.player.PlayerNameEntityEvent(((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity(), entity.getBukkitLivingEntity(), io.papermc.paper.adventure.PaperAdventure.asAdventure(stack.getHoverName()), true);
++ if (!event.callEvent()) return InteractionResult.PASS;
++ LivingEntity newEntity = ((org.bukkit.craftbukkit.entity.CraftLivingEntity) event.getEntity()).getHandle();
++ newEntity.setCustomName(event.getName() != null ? io.papermc.paper.adventure.PaperAdventure.asVanilla(event.getName()) : null);
++ if (event.isPersistent() && newEntity instanceof Mob mob) {
++ // Paper end - Add PlayerNameEntityEvent
+ mob.setPersistenceRequired();
+ }
+
diff --git a/patches/server/0493-Add-recipe-to-cook-events.patch b/patches/server/0493-Add-recipe-to-cook-events.patch
new file mode 100644
index 0000000000..737ce3caf8
--- /dev/null
+++ b/patches/server/0493-Add-recipe-to-cook-events.patch
@@ -0,0 +1,44 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Thonk <[email protected]>
+Date: Wed, 6 Jan 2021 12:04:03 -0800
+Subject: [PATCH] Add recipe to cook events
+
+
+diff --git a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java
+index b9dd5f710533b156311cac2c020fd0d5f64b6265..2ec1c00eb77051c622fedec1ebeba2953886ace4 100644
+--- a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java
++++ b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java
+@@ -332,7 +332,7 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit
+ CraftItemStack source = CraftItemStack.asCraftMirror(itemstack);
+ org.bukkit.inventory.ItemStack result = CraftItemStack.asBukkitCopy(itemstack1);
+
+- FurnaceSmeltEvent furnaceSmeltEvent = new FurnaceSmeltEvent(CraftBlock.at(world, blockposition), source, result);
++ FurnaceSmeltEvent furnaceSmeltEvent = new FurnaceSmeltEvent(CraftBlock.at(world, blockposition), source, result, (org.bukkit.inventory.CookingRecipe<?>) recipeholder.toBukkitRecipe()); // Paper - Add recipe to cook events
+ world.getCraftServer().getPluginManager().callEvent(furnaceSmeltEvent);
+
+ if (furnaceSmeltEvent.isCancelled()) {
+diff --git a/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java
+index 30035d534e144bf31f94073c57b0195be7e62772..7fa1aea7942a1bc4d9779a9f8ab020ccd5566923 100644
+--- a/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java
++++ b/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java
+@@ -66,7 +66,10 @@ public class CampfireBlockEntity extends BlockEntity implements Clearable {
+
+ if (blockEntity.cookingProgress[i] >= blockEntity.cookingTime[i]) {
+ SingleRecipeInput singlerecipeinput = new SingleRecipeInput(itemstack);
+- ItemStack itemstack1 = (ItemStack) recipeMatchGetter.getRecipeFor(singlerecipeinput, world).map((recipeholder) -> {
++ // Paper start - add recipe to cook events
++ final Optional<RecipeHolder<CampfireCookingRecipe>> recipeHolderOptional = recipeMatchGetter.getRecipeFor(singlerecipeinput, world);
++ ItemStack itemstack1 = (ItemStack) recipeHolderOptional.map((recipeholder) -> {
++ // Paper end - add recipe to cook events
+ return ((CampfireCookingRecipe) recipeholder.value()).assemble(singlerecipeinput, world.registryAccess());
+ }).orElse(itemstack);
+
+@@ -75,7 +78,7 @@ public class CampfireBlockEntity extends BlockEntity implements Clearable {
+ CraftItemStack source = CraftItemStack.asCraftMirror(itemstack);
+ org.bukkit.inventory.ItemStack result = CraftItemStack.asBukkitCopy(itemstack1);
+
+- BlockCookEvent blockCookEvent = new BlockCookEvent(CraftBlock.at(world, pos), source, result);
++ BlockCookEvent blockCookEvent = new BlockCookEvent(CraftBlock.at(world, pos), source, result, (org.bukkit.inventory.CookingRecipe<?>) recipeHolderOptional.map(RecipeHolder::toBukkitRecipe).orElse(null)); // Paper - Add recipe to cook events
+ world.getCraftServer().getPluginManager().callEvent(blockCookEvent);
+
+ if (blockCookEvent.isCancelled()) {
diff --git a/patches/server/0494-Add-Block-isValidTool.patch b/patches/server/0494-Add-Block-isValidTool.patch
new file mode 100644
index 0000000000..e0c42e8e37
--- /dev/null
+++ b/patches/server/0494-Add-Block-isValidTool.patch
@@ -0,0 +1,20 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Mon, 6 Jul 2020 12:44:31 -0700
+Subject: [PATCH] Add Block#isValidTool
+
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java
+index 1595e877b02b447b36f5c316ae70d4023b78a862..54fb380a6896731a18c0100722d12099e590cbc9 100644
+--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java
++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java
+@@ -693,5 +693,9 @@ public class CraftBlock implements Block {
+ public String translationKey() {
+ return this.getNMS().getBlock().getDescriptionId();
+ }
++
++ public boolean isValidTool(ItemStack itemStack) {
++ return getDrops(itemStack).size() != 0;
++ }
+ // Paper end
+ }
diff --git a/patches/server/0495-Allow-using-signs-inside-spawn-protection.patch b/patches/server/0495-Allow-using-signs-inside-spawn-protection.patch
new file mode 100644
index 0000000000..84ca07a4a1
--- /dev/null
+++ b/patches/server/0495-Allow-using-signs-inside-spawn-protection.patch
@@ -0,0 +1,19 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Anton Lindroth <[email protected]>
+Date: Wed, 15 Apr 2020 01:54:02 +0200
+Subject: [PATCH] Allow using signs inside spawn protection
+
+
+diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+index 0c413f4981038c5c3f12ba17309ce354c3f13ec6..628260aa644539a966e4cc5acf8da34144fcabd0 100644
+--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+@@ -1762,7 +1762,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
+ int i = this.player.level().getMaxY();
+
+ if (blockposition.getY() <= i) {
+- if (this.awaitingPositionFromClient == null && worldserver.mayInteract(this.player, blockposition)) {
++ if (this.awaitingPositionFromClient == null && (worldserver.mayInteract(this.player, blockposition) || (worldserver.paperConfig().spawn.allowUsingSignsInsideSpawnProtection && worldserver.getBlockState(blockposition).getBlock() instanceof net.minecraft.world.level.block.SignBlock))) { // Paper - Allow using signs inside spawn protection
+ this.player.stopUsingItem(); // CraftBukkit - SPIGOT-4706
+ InteractionResult enuminteractionresult = this.player.gameMode.useItemOn(this.player, worldserver, itemstack, enumhand, movingobjectpositionblock);
+
diff --git a/patches/server/0496-Expand-world-key-API.patch b/patches/server/0496-Expand-world-key-API.patch
new file mode 100644
index 0000000000..beef586467
--- /dev/null
+++ b/patches/server/0496-Expand-world-key-API.patch
@@ -0,0 +1,84 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Wed, 6 Jan 2021 00:34:04 -0800
+Subject: [PATCH] Expand world key API
+
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java
+index 93dadbf659e41c923268d8ec782fcbdc8aaeff68..21e5dd6624e50dec35859b7d1be4a304e4427883 100644
+--- a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java
++++ b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java
+@@ -515,5 +515,10 @@ public abstract class CraftRegionAccessor implements RegionAccessor {
+ public io.papermc.paper.world.MoonPhase getMoonPhase() {
+ return io.papermc.paper.world.MoonPhase.getPhase(this.getHandle().dayTime() / 24000L);
+ }
++
++ @Override
++ public org.bukkit.NamespacedKey getKey() {
++ return org.bukkit.craftbukkit.util.CraftNamespacedKey.fromMinecraft(this.getHandle().getLevel().dimension().location());
++ }
+ // Paper end
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+index ee231d93d216571a45b11b49663b2ea91c47a1c7..dc20a383950a72aba5d056912d257912d3c0e808 100644
+--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+@@ -1186,9 +1186,15 @@ public final class CraftServer implements Server {
+ File folder = new File(this.getWorldContainer(), name);
+ World world = this.getWorld(name);
+
+- if (world != null) {
+- return world;
++ // Paper start
++ World worldByKey = this.getWorld(creator.key());
++ if (world != null || worldByKey != null) {
++ if (world == worldByKey) {
++ return world;
++ }
++ throw new IllegalArgumentException("Cannot create a world with key " + creator.key() + " and name " + name + " one (or both) already match a world that exists");
+ }
++ // Paper end
+
+ if (folder.exists()) {
+ Preconditions.checkArgument(folder.isDirectory(), "File (%s) exists and isn't a folder", name);
+@@ -1314,7 +1320,7 @@ public final class CraftServer implements Server {
+ } else if (name.equals(levelName + "_the_end")) {
+ worldKey = net.minecraft.world.level.Level.END;
+ } else {
+- worldKey = ResourceKey.create(Registries.DIMENSION, ResourceLocation.withDefaultNamespace(name.toLowerCase(Locale.ROOT)));
++ worldKey = ResourceKey.create(Registries.DIMENSION, ResourceLocation.fromNamespaceAndPath(creator.key().namespace(), creator.key().value()));
+ }
+
+ // If set to not keep spawn in memory (changed from default) then adjust rule accordingly
+@@ -1410,6 +1416,15 @@ public final class CraftServer implements Server {
+ return null;
+ }
+
++ // Paper start
++ @Override
++ public World getWorld(net.kyori.adventure.key.Key worldKey) {
++ ServerLevel worldServer = console.getLevel(ResourceKey.create(net.minecraft.core.registries.Registries.DIMENSION, io.papermc.paper.adventure.PaperAdventure.asVanilla(worldKey)));
++ if (worldServer == null) return null;
++ return worldServer.getWorld();
++ }
++ // Paper end
++
+ public void addWorld(World world) {
+ // Check if a World already exists with the UID.
+ if (this.getWorld(world.getUID()) != null) {
+diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
+index ea9c9ae832c4044a2eccbf901e20ff042df68bf3..407fa9f1f3d418597eb490e2cf41aee39c715d3a 100644
+--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
+@@ -522,6 +522,11 @@ public final class CraftMagicNumbers implements UnsafeValues {
+ public int nextEntityId() {
+ return net.minecraft.world.entity.Entity.nextEntityId();
+ }
++
++ @Override
++ public String getMainLevelName() {
++ return ((net.minecraft.server.dedicated.DedicatedServer) net.minecraft.server.MinecraftServer.getServer()).getProperties().levelName;
++ }
+ // Paper end
+
+ @Override
diff --git a/patches/server/0497-Add-fast-alternative-constructor-for-Rotations.patch b/patches/server/0497-Add-fast-alternative-constructor-for-Rotations.patch
new file mode 100644
index 0000000000..e28775fc9a
--- /dev/null
+++ b/patches/server/0497-Add-fast-alternative-constructor-for-Rotations.patch
@@ -0,0 +1,29 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Irmo van den Berge <[email protected]>
+Date: Wed, 10 Mar 2021 21:26:31 +0100
+Subject: [PATCH] Add fast alternative constructor for Rotations
+
+
+diff --git a/src/main/java/net/minecraft/core/Rotations.java b/src/main/java/net/minecraft/core/Rotations.java
+index e6c97efb3aa89646149d11bb0ae4420b3977d214..27007280dba9e6d19a50dc2a4b160e96b20c67f7 100644
+--- a/src/main/java/net/minecraft/core/Rotations.java
++++ b/src/main/java/net/minecraft/core/Rotations.java
+@@ -34,6 +34,18 @@ public class Rotations {
+ this(serialized.getFloat(0), serialized.getFloat(1), serialized.getFloat(2));
+ }
+
++ // Paper start - faster alternative constructor
++ private Rotations(float x, float y, float z, Void dummy_var) {
++ this.x = x;
++ this.y = y;
++ this.z = z;
++ }
++
++ public static Rotations createWithoutValidityChecks(float x, float y, float z) {
++ return new Rotations(x, y, z, null);
++ }
++ // Paper end - faster alternative constructor
++
+ public ListTag save() {
+ ListTag listTag = new ListTag();
+ listTag.add(FloatTag.valueOf(this.x));
diff --git a/patches/server/0498-Drop-carried-item-when-player-has-disconnected.patch b/patches/server/0498-Drop-carried-item-when-player-has-disconnected.patch
new file mode 100644
index 0000000000..a13f458eea
--- /dev/null
+++ b/patches/server/0498-Drop-carried-item-when-player-has-disconnected.patch
@@ -0,0 +1,27 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Dmitry Sidorov <[email protected]>
+Date: Thu, 4 Feb 2021 20:32:01 +0300
+Subject: [PATCH] Drop carried item when player has disconnected
+
+Fixes disappearance of held items, when a player gets disconnected and PlayerDropItemEvent is cancelled.
+Closes #5036
+
+diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
+index 26e0414645f7ab11ca3e77c7c5e458612625aee9..6f7807cc0da427485037b3a72a9c60c81a858294 100644
+--- a/src/main/java/net/minecraft/server/players/PlayerList.java
++++ b/src/main/java/net/minecraft/server/players/PlayerList.java
+@@ -538,6 +538,14 @@ public abstract class PlayerList {
+ }
+ // Paper end - Configurable player collision
+
++ // Paper - Drop carried item when player has disconnected
++ if (!entityplayer.containerMenu.getCarried().isEmpty()) {
++ net.minecraft.world.item.ItemStack carried = entityplayer.containerMenu.getCarried();
++ entityplayer.containerMenu.setCarried(net.minecraft.world.item.ItemStack.EMPTY);
++ entityplayer.drop(carried, false);
++ }
++ // Paper end - Drop carried item when player has disconnected
++
+ this.save(entityplayer);
+ if (entityplayer.isPassenger()) {
+ Entity entity = entityplayer.getRootVehicle();
diff --git a/patches/server/0499-forced-whitelist-use-configurable-kick-message.patch b/patches/server/0499-forced-whitelist-use-configurable-kick-message.patch
new file mode 100644
index 0000000000..aed5bbf27d
--- /dev/null
+++ b/patches/server/0499-forced-whitelist-use-configurable-kick-message.patch
@@ -0,0 +1,19 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Trigary <[email protected]>
+Date: Sat, 27 Mar 2021 09:24:23 +0100
+Subject: [PATCH] forced whitelist: use configurable kick message
+
+
+diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
+index 985f345cdf9f69140df9210be6eca477d911f8a9..42429684bd732d0094ad0db346d2a656871aabff 100644
+--- a/src/main/java/net/minecraft/server/MinecraftServer.java
++++ b/src/main/java/net/minecraft/server/MinecraftServer.java
+@@ -2368,7 +2368,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+ ServerPlayer entityplayer = (ServerPlayer) iterator.next();
+
+ if (!whitelist.isWhiteListed(entityplayer.getGameProfile()) && !this.getPlayerList().isOp(entityplayer.getGameProfile())) { // Paper - Fix kicking ops when whitelist is reloaded (MC-171420)
+- entityplayer.connection.disconnect((Component) Component.translatable("multiplayer.disconnect.not_whitelisted"));
++ entityplayer.connection.disconnect(net.kyori.adventure.text.Component.text(org.spigotmc.SpigotConfig.whitelistMessage));
+ }
+ }
+
diff --git a/patches/server/0500-Don-t-ignore-result-of-PlayerEditBookEvent.patch b/patches/server/0500-Don-t-ignore-result-of-PlayerEditBookEvent.patch
new file mode 100644
index 0000000000..1946415e72
--- /dev/null
+++ b/patches/server/0500-Don-t-ignore-result-of-PlayerEditBookEvent.patch
@@ -0,0 +1,19 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jason Penilla <[email protected]>
+Date: Mon, 5 Apr 2021 18:35:15 -0700
+Subject: [PATCH] Don't ignore result of PlayerEditBookEvent
+
+
+diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+index 628260aa644539a966e4cc5acf8da34144fcabd0..ed6c07cfa69788eb6adcaf8293b2ca2040f057a0 100644
+--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+@@ -1157,7 +1157,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
+ List<Filterable<String>> list1 = pages.stream().map(this::filterableFromOutgoing).toList();
+
+ itemstack.set(DataComponents.WRITABLE_BOOK_CONTENT, new WritableBookContent(list1));
+- CraftEventFactory.handleEditBookEvent(this.player, slotId, handItem, itemstack); // CraftBukkit
++ this.player.getInventory().setItem(slotId, CraftEventFactory.handleEditBookEvent(this.player, slotId, handItem, itemstack)); // CraftBukkit // Paper - Don't ignore result (see other callsite for handleEditBookEvent)
+ }
+ }
+
diff --git a/patches/server/0501-Expose-protocol-version.patch b/patches/server/0501-Expose-protocol-version.patch
new file mode 100644
index 0000000000..1cc14ceba2
--- /dev/null
+++ b/patches/server/0501-Expose-protocol-version.patch
@@ -0,0 +1,22 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Nassim Jahnke <[email protected]>
+Date: Fri, 26 Mar 2021 11:23:17 +0100
+Subject: [PATCH] Expose protocol version
+
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
+index 407fa9f1f3d418597eb490e2cf41aee39c715d3a..2f7cb2bd5998204d61c9d758224b3a2e3351de48 100644
+--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
+@@ -527,6 +527,11 @@ public final class CraftMagicNumbers implements UnsafeValues {
+ public String getMainLevelName() {
+ return ((net.minecraft.server.dedicated.DedicatedServer) net.minecraft.server.MinecraftServer.getServer()).getProperties().levelName;
+ }
++
++ @Override
++ public int getProtocolVersion() {
++ return net.minecraft.SharedConstants.getCurrentVersion().getProtocolVersion();
++ }
+ // Paper end
+
+ @Override
diff --git a/patches/server/0502-Enhance-console-tab-completions-for-brigadier-comman.patch b/patches/server/0502-Enhance-console-tab-completions-for-brigadier-comman.patch
new file mode 100644
index 0000000000..15098cf64c
--- /dev/null
+++ b/patches/server/0502-Enhance-console-tab-completions-for-brigadier-comman.patch
@@ -0,0 +1,445 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jason Penilla <[email protected]>
+Date: Tue, 30 Mar 2021 16:06:08 -0700
+Subject: [PATCH] Enhance console tab completions for brigadier commands
+
+Co-authored-by: Jake Potrebic <[email protected]>
+
+diff --git a/src/main/java/com/destroystokyo/paper/console/PaperConsole.java b/src/main/java/com/destroystokyo/paper/console/PaperConsole.java
+index a4070b59e261f0f1ac4beec47b11492f4724bf27..6ee39b534b8d992655bc0cef3c299d12cbae0034 100644
+--- a/src/main/java/com/destroystokyo/paper/console/PaperConsole.java
++++ b/src/main/java/com/destroystokyo/paper/console/PaperConsole.java
+@@ -1,5 +1,8 @@
+ package com.destroystokyo.paper.console;
+
++import io.papermc.paper.configuration.GlobalConfiguration;
++import io.papermc.paper.console.BrigadierCompletionMatcher;
++import io.papermc.paper.console.BrigadierConsoleParser;
+ import net.minecraft.server.dedicated.DedicatedServer;
+ import net.minecrell.terminalconsole.SimpleTerminalConsole;
+ import org.bukkit.craftbukkit.command.ConsoleCommandCompleter;
+@@ -16,11 +19,20 @@ public final class PaperConsole extends SimpleTerminalConsole {
+
+ @Override
+ protected LineReader buildReader(LineReaderBuilder builder) {
+- return super.buildReader(builder
++ builder
+ .appName("Paper")
+ .variable(LineReader.HISTORY_FILE, java.nio.file.Paths.get(".console_history"))
+ .completer(new ConsoleCommandCompleter(this.server))
+- );
++ .option(LineReader.Option.COMPLETE_IN_WORD, true);
++ if (io.papermc.paper.configuration.GlobalConfiguration.get().console.enableBrigadierHighlighting) {
++ builder.highlighter(new io.papermc.paper.console.BrigadierCommandHighlighter(this.server));
++ }
++ if (GlobalConfiguration.get().console.enableBrigadierCompletions) {
++ System.setProperty("org.jline.reader.support.parsedline", "true"); // to hide a warning message about the parser not supporting
++ builder.parser(new BrigadierConsoleParser(this.server));
++ builder.completionMatcher(new BrigadierCompletionMatcher());
++ }
++ return super.buildReader(builder);
+ }
+
+ @Override
+diff --git a/src/main/java/io/papermc/paper/console/BrigadierCommandCompleter.java b/src/main/java/io/papermc/paper/console/BrigadierCommandCompleter.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..bf7b9518c05ff8a6d4b7d7cd36187ca22257e3dc
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/console/BrigadierCommandCompleter.java
+@@ -0,0 +1,119 @@
++package io.papermc.paper.console;
++
++import com.destroystokyo.paper.event.server.AsyncTabCompleteEvent;
++import com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion;
++import com.google.common.base.Suppliers;
++import com.mojang.brigadier.CommandDispatcher;
++import com.mojang.brigadier.ParseResults;
++import com.mojang.brigadier.StringReader;
++import com.mojang.brigadier.suggestion.Suggestion;
++import io.papermc.paper.adventure.PaperAdventure;
++import java.util.ArrayList;
++import java.util.Collections;
++import java.util.List;
++import java.util.function.Supplier;
++import net.kyori.adventure.text.Component;
++import net.minecraft.commands.CommandSourceStack;
++import net.minecraft.network.chat.ComponentUtils;
++import net.minecraft.server.dedicated.DedicatedServer;
++import org.checkerframework.checker.nullness.qual.NonNull;
++import org.checkerframework.checker.nullness.qual.Nullable;
++import org.jline.reader.Candidate;
++import org.jline.reader.LineReader;
++import org.jline.reader.ParsedLine;
++
++import static com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion.completion;
++
++public final class BrigadierCommandCompleter {
++ private final Supplier<CommandSourceStack> commandSourceStack;
++ private final DedicatedServer server;
++
++ public BrigadierCommandCompleter(final @NonNull DedicatedServer server) {
++ this.server = server;
++ this.commandSourceStack = Suppliers.memoize(this.server::createCommandSourceStack);
++ }
++
++ public void complete(final @NonNull LineReader reader, final @NonNull ParsedLine line, final @NonNull List<Candidate> candidates, final @NonNull List<Completion> existing) {
++ //noinspection ConstantConditions
++ if (this.server.overworld() == null) { // check if overworld is null, as worlds haven't been loaded yet
++ return;
++ } else if (!io.papermc.paper.configuration.GlobalConfiguration.get().console.enableBrigadierCompletions) {
++ this.addCandidates(candidates, Collections.emptyList(), existing, new ParseContext(line.line(), 0));
++ return;
++ }
++ final CommandDispatcher<CommandSourceStack> dispatcher = this.server.getCommands().getDispatcher();
++ final ParseResults<CommandSourceStack> results = dispatcher.parse(new StringReader(line.line()), this.commandSourceStack.get());
++ this.addCandidates(
++ candidates,
++ dispatcher.getCompletionSuggestions(results, line.cursor()).join().getList(),
++ existing,
++ new ParseContext(line.line(), results.getContext().findSuggestionContext(line.cursor()).startPos)
++ );
++ }
++
++ private void addCandidates(
++ final @NonNull List<Candidate> candidates,
++ final @NonNull List<Suggestion> brigSuggestions,
++ final @NonNull List<Completion> existing,
++ final @NonNull ParseContext context
++ ) {
++ brigSuggestions.forEach(it -> {
++ if (it.getText().isEmpty()) return;
++ candidates.add(toCandidate(it, context));
++ });
++ for (final AsyncTabCompleteEvent.Completion completion : existing) {
++ if (completion.suggestion().isEmpty() || brigSuggestions.stream().anyMatch(it -> it.getText().equals(completion.suggestion()))) {
++ continue;
++ }
++ candidates.add(toCandidate(completion));
++ }
++ }
++
++ private static Candidate toCandidate(final Suggestion suggestion, final @NonNull ParseContext context) {
++ Component tooltip = null;
++ if (suggestion.getTooltip() != null) {
++ tooltip = PaperAdventure.asAdventure(ComponentUtils.fromMessage(suggestion.getTooltip()));
++ }
++ return toCandidate(context.line.substring(context.suggestionStart, suggestion.getRange().getStart()) + suggestion.getText(), tooltip);
++ }
++
++ private static @NonNull Candidate toCandidate(final @NonNull Completion completion) {
++ return toCandidate(completion.suggestion(), completion.tooltip());
++ }
++
++ private static @NonNull Candidate toCandidate(final @NonNull String suggestionText, final @Nullable Component tooltip) {
++ final String suggestionTooltip = PaperAdventure.ANSI_SERIALIZER.serializeOr(tooltip, null);
++ //noinspection SpellCheckingInspection
++ return new PaperCandidate(
++ suggestionText,
++ suggestionText,
++ null,
++ suggestionTooltip,
++ null,
++ null,
++ /*
++ in an ideal world, this would sometimes be true if the suggestion represented the final possible value for a word.
++ Like for `/execute alig`, pressing enter on align would add a trailing space if this value was true. But not all
++ suggestions should add spaces after, like `/execute as @`, accepting any suggestion here would be valid, but its also
++ valid to have a `[` following the selector
++ */
++ false
++ );
++ }
++
++ private static @NonNull Completion toCompletion(final @NonNull Suggestion suggestion) {
++ if (suggestion.getTooltip() == null) {
++ return completion(suggestion.getText());
++ }
++ return completion(suggestion.getText(), PaperAdventure.asAdventure(ComponentUtils.fromMessage(suggestion.getTooltip())));
++ }
++
++ private record ParseContext(String line, int suggestionStart) {
++ }
++
++ public static final class PaperCandidate extends Candidate {
++ public PaperCandidate(final String value, final String display, final String group, final String descr, final String suffix, final String key, final boolean complete) {
++ super(value, display, group, descr, suffix, key, complete);
++ }
++ }
++}
+diff --git a/src/main/java/io/papermc/paper/console/BrigadierCommandHighlighter.java b/src/main/java/io/papermc/paper/console/BrigadierCommandHighlighter.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0b21dac4473e3ea8022ef5c17f5f7d4d49d3ac0a
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/console/BrigadierCommandHighlighter.java
+@@ -0,0 +1,67 @@
++package io.papermc.paper.console;
++
++import com.google.common.base.Suppliers;
++import com.mojang.brigadier.ParseResults;
++import com.mojang.brigadier.StringReader;
++import com.mojang.brigadier.context.ParsedCommandNode;
++import com.mojang.brigadier.tree.LiteralCommandNode;
++import java.util.function.Supplier;
++import java.util.regex.Pattern;
++import net.minecraft.commands.CommandSourceStack;
++import net.minecraft.server.dedicated.DedicatedServer;
++import org.checkerframework.checker.nullness.qual.NonNull;
++import org.jline.reader.Highlighter;
++import org.jline.reader.LineReader;
++import org.jline.utils.AttributedString;
++import org.jline.utils.AttributedStringBuilder;
++import org.jline.utils.AttributedStyle;
++
++public final class BrigadierCommandHighlighter implements Highlighter {
++ private static final int[] COLORS = {AttributedStyle.CYAN, AttributedStyle.YELLOW, AttributedStyle.GREEN, AttributedStyle.MAGENTA, /* Client uses GOLD here, not BLUE, however there is no GOLD AttributedStyle. */ AttributedStyle.BLUE};
++ private final Supplier<CommandSourceStack> commandSourceStack;
++ private final DedicatedServer server;
++
++ public BrigadierCommandHighlighter(final @NonNull DedicatedServer server) {
++ this.server = server;
++ this.commandSourceStack = Suppliers.memoize(this.server::createCommandSourceStack);
++ }
++
++ @Override
++ public AttributedString highlight(final @NonNull LineReader reader, final @NonNull String buffer) {
++ //noinspection ConstantConditions
++ if (this.server.overworld() == null) { // check if overworld is null, as worlds haven't been loaded yet
++ return new AttributedString(buffer, AttributedStyle.DEFAULT.foreground(AttributedStyle.RED));
++ }
++ final AttributedStringBuilder builder = new AttributedStringBuilder();
++ final ParseResults<CommandSourceStack> results = this.server.getCommands().getDispatcher().parse(new StringReader(buffer), this.commandSourceStack.get());
++ int pos = 0;
++ int component = -1;
++ for (final ParsedCommandNode<CommandSourceStack> node : results.getContext().getLastChild().getNodes()) {
++ if (node.getRange().getStart() >= buffer.length()) {
++ break;
++ }
++ final int start = node.getRange().getStart();
++ final int end = Math.min(node.getRange().getEnd(), buffer.length());
++ builder.append(buffer.substring(pos, start), AttributedStyle.DEFAULT);
++ if (node.getNode() instanceof LiteralCommandNode) {
++ builder.append(buffer.substring(start, end), AttributedStyle.DEFAULT);
++ } else {
++ if (++component >= COLORS.length) {
++ component = 0;
++ }
++ builder.append(buffer.substring(start, end), AttributedStyle.DEFAULT.foreground(COLORS[component]));
++ }
++ pos = end;
++ }
++ if (pos < buffer.length()) {
++ builder.append((buffer.substring(pos)), AttributedStyle.DEFAULT.foreground(AttributedStyle.RED));
++ }
++ return builder.toAttributedString();
++ }
++
++ @Override
++ public void setErrorPattern(final Pattern errorPattern) {}
++
++ @Override
++ public void setErrorIndex(final int errorIndex) {}
++}
+diff --git a/src/main/java/io/papermc/paper/console/BrigadierCompletionMatcher.java b/src/main/java/io/papermc/paper/console/BrigadierCompletionMatcher.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..1e8028a43db0ff1d5b22d06ef12c1c32d992c09c
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/console/BrigadierCompletionMatcher.java
+@@ -0,0 +1,27 @@
++package io.papermc.paper.console;
++
++import com.google.common.collect.Iterables;
++import java.util.HashMap;
++import java.util.List;
++import java.util.Map;
++import org.jline.reader.Candidate;
++import org.jline.reader.CompletingParsedLine;
++import org.jline.reader.LineReader;
++import org.jline.reader.impl.CompletionMatcherImpl;
++
++public class BrigadierCompletionMatcher extends CompletionMatcherImpl {
++
++ @Override
++ protected void defaultMatchers(final Map<LineReader.Option, Boolean> options, final boolean prefix, final CompletingParsedLine line, final boolean caseInsensitive, final int errors, final String originalGroupName) {
++ super.defaultMatchers(options, prefix, line, caseInsensitive, errors, originalGroupName);
++ this.matchers.addFirst(m -> {
++ final Map<String, List<Candidate>> candidates = new HashMap<>();
++ for (final Map.Entry<String, List<Candidate>> entry : m.entrySet()) {
++ if (Iterables.all(entry.getValue(), BrigadierCommandCompleter.PaperCandidate.class::isInstance)) {
++ candidates.put(entry.getKey(), entry.getValue());
++ }
++ }
++ return candidates;
++ });
++ }
++}
+diff --git a/src/main/java/io/papermc/paper/console/BrigadierConsoleParser.java b/src/main/java/io/papermc/paper/console/BrigadierConsoleParser.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..8239a8ba57f856cbbee237a601b3cabfce20ba26
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/console/BrigadierConsoleParser.java
+@@ -0,0 +1,79 @@
++package io.papermc.paper.console;
++
++import com.mojang.brigadier.ImmutableStringReader;
++import com.mojang.brigadier.ParseResults;
++import com.mojang.brigadier.StringReader;
++import com.mojang.brigadier.context.CommandContextBuilder;
++import com.mojang.brigadier.context.ParsedCommandNode;
++import com.mojang.brigadier.context.StringRange;
++import java.util.ArrayList;
++import java.util.List;
++import net.minecraft.commands.CommandSourceStack;
++import net.minecraft.server.dedicated.DedicatedServer;
++import org.jline.reader.ParsedLine;
++import org.jline.reader.Parser;
++import org.jline.reader.SyntaxError;
++
++public class BrigadierConsoleParser implements Parser {
++
++ private final DedicatedServer server;
++
++ public BrigadierConsoleParser(DedicatedServer server) {
++ this.server = server;
++ }
++
++ @Override
++ public ParsedLine parse(final String line, final int cursor, final ParseContext context) throws SyntaxError {
++ final ParseResults<CommandSourceStack> results = this.server.getCommands().getDispatcher().parse(new StringReader(line), this.server.createCommandSourceStack());
++ final ImmutableStringReader reader = results.getReader();
++ final List<String> words = new ArrayList<>();
++ CommandContextBuilder<CommandSourceStack> currentContext = results.getContext();
++ int currentWordIdx = -1;
++ int wordIdx = -1;
++ int inWordCursor = -1;
++ if (currentContext.getRange().getLength() > 0) {
++ do {
++ for (final ParsedCommandNode<CommandSourceStack> node : currentContext.getNodes()) {
++ final StringRange nodeRange = node.getRange();
++ String current = nodeRange.get(reader);
++ words.add(current);
++ currentWordIdx++;
++ if (wordIdx == -1 && nodeRange.getStart() <= cursor && nodeRange.getEnd() >= cursor) {
++ // if cursor is in the middle of a parsed word/node
++ wordIdx = currentWordIdx;
++ inWordCursor = cursor - nodeRange.getStart();
++ }
++ }
++ currentContext = currentContext.getChild();
++ } while (currentContext != null);
++ }
++ final String leftovers = reader.getRemaining();
++ if (!leftovers.isEmpty() && leftovers.isBlank()) {
++ // if brig didn't consume the whole line, and everything else is blank, add a new empty word
++ currentWordIdx++;
++ words.add("");
++ if (wordIdx == -1) {
++ wordIdx = currentWordIdx;
++ inWordCursor = 0;
++ }
++ } else if (!leftovers.isEmpty()) {
++ // if there are unparsed leftovers, add a new word with the remaining input
++ currentWordIdx++;
++ words.add(leftovers);
++ if (wordIdx == -1) {
++ wordIdx = currentWordIdx;
++ inWordCursor = cursor - reader.getCursor();
++ }
++ }
++ if (wordIdx == -1) {
++ currentWordIdx++;
++ words.add("");
++ wordIdx = currentWordIdx;
++ inWordCursor = 0;
++ }
++ return new BrigadierParsedLine(words.get(wordIdx), inWordCursor, wordIdx, words, line, cursor);
++ }
++
++ record BrigadierParsedLine(String word, int wordCursor, int wordIndex, List<String> words, String line, int cursor) implements ParsedLine {
++ }
++}
+diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
+index e95d592b6001dd4320c58133d841359f99c2d9c4..17b15bef9ad8edddc2e1f2a617a1ab4bd5b53347 100644
+--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
+@@ -189,7 +189,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
+
+ thread.setDaemon(true);
+ thread.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(DedicatedServer.LOGGER));
+- thread.start();
++ // thread.start(); // Paper - Enhance console tab completions for brigadier commands; moved down
+ DedicatedServer.LOGGER.info("Starting minecraft server version {}", SharedConstants.getCurrentVersion().getName());
+ if (Runtime.getRuntime().maxMemory() / 1024L / 1024L < 512L) {
+ DedicatedServer.LOGGER.warn("To start the server with more ram, launch it as \"java -Xmx1024M -Xms1024M -jar minecraft_server.jar\"");
+@@ -222,6 +222,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
+ this.getPlayerList().loadAndSaveFiles(); // Must be after convertNames
+ // Paper end - fix converting txt to json file
+ org.spigotmc.WatchdogThread.doStart(org.spigotmc.SpigotConfig.timeoutTime, org.spigotmc.SpigotConfig.restartOnCrash); // Paper - start watchdog thread
++ thread.start(); // Paper - Enhance console tab completions for brigadier commands; start console thread after MinecraftServer.console & PaperConfig are initialized
+ io.papermc.paper.command.PaperCommands.registerCommands(this); // Paper - setup /paper command
+ com.destroystokyo.paper.Metrics.PaperMetrics.startMetrics(); // Paper - start metrics
+ com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // Paper - load version history now
+diff --git a/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java b/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java
+index 15bc85f4799a4b23edd2f1e93f1794de5ca3e8e3..a45e658996e483e9a21cfd8178153ddb7b87ae69 100644
+--- a/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java
++++ b/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java
+@@ -18,9 +18,11 @@ import org.bukkit.event.server.TabCompleteEvent;
+
+ public class ConsoleCommandCompleter implements Completer {
+ private final DedicatedServer server; // Paper - CraftServer -> DedicatedServer
++ private final io.papermc.paper.console.BrigadierCommandCompleter brigadierCompleter; // Paper - Enhance console tab completions for brigadier commands
+
+ public ConsoleCommandCompleter(DedicatedServer server) { // Paper - CraftServer -> DedicatedServer
+ this.server = server;
++ this.brigadierCompleter = new io.papermc.paper.console.BrigadierCommandCompleter(this.server); // Paper - Enhance console tab completions for brigadier commands
+ }
+
+ // Paper start - Change method signature for JLine update
+@@ -64,7 +66,7 @@ public class ConsoleCommandCompleter implements Completer {
+ }
+ }
+
+- if (!completions.isEmpty()) {
++ if (false && !completions.isEmpty()) {
+ for (final com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion completion : completions) {
+ if (completion.suggestion().isEmpty()) {
+ continue;
+@@ -80,6 +82,7 @@ public class ConsoleCommandCompleter implements Completer {
+ ));
+ }
+ }
++ this.addCompletions(reader, line, candidates, completions);
+ return;
+ }
+
+@@ -99,10 +102,12 @@ public class ConsoleCommandCompleter implements Completer {
+ try {
+ List<String> offers = waitable.get();
+ if (offers == null) {
++ this.addCompletions(reader, line, candidates, Collections.emptyList()); // Paper - Enhance console tab completions for brigadier commands
+ return; // Paper - Method returns void
+ }
+
+ // Paper start - JLine update
++ /*
+ for (String completion : offers) {
+ if (completion.isEmpty()) {
+ continue;
+@@ -110,6 +115,8 @@ public class ConsoleCommandCompleter implements Completer {
+
+ candidates.add(new Candidate(completion));
+ }
++ */
++ this.addCompletions(reader, line, candidates, offers.stream().map(com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion::completion).collect(java.util.stream.Collectors.toList()));
+ // Paper end
+
+ // Paper start - JLine handles cursor now
+@@ -138,5 +145,9 @@ public class ConsoleCommandCompleter implements Completer {
+ }
+ return false;
+ }
++
++ private void addCompletions(final LineReader reader, final ParsedLine line, final List<Candidate> candidates, final List<com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion> existing) {
++ this.brigadierCompleter.complete(reader, line, candidates, existing);
++ }
+ // Paper end
+ }
diff --git a/patches/server/0503-Fix-PlayerItemConsumeEvent-cancelling-properly.patch b/patches/server/0503-Fix-PlayerItemConsumeEvent-cancelling-properly.patch
new file mode 100644
index 0000000000..a1da72f499
--- /dev/null
+++ b/patches/server/0503-Fix-PlayerItemConsumeEvent-cancelling-properly.patch
@@ -0,0 +1,22 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: chickeneer <[email protected]>
+Date: Fri, 19 Mar 2021 00:33:15 -0500
+Subject: [PATCH] Fix PlayerItemConsumeEvent cancelling properly
+
+When the active item is not cleared, the item is still readied
+for use and will repeatedly trigger the PlayerItemConsumeEvent
+till their item is switched.
+This patch clears the active item when the event is cancelled
+
+diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java
+index 293490d124bc06c4a06b9f4a7f77a9c25a8d7d39..b7cc3d84c724772e3e1250c5e99bb32e01112220 100644
+--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java
++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java
+@@ -4136,6 +4136,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
+ this.level().getCraftServer().getPluginManager().callEvent(event);
+
+ if (event.isCancelled()) {
++ this.stopUsingItem(); // Paper - event is using an item, clear active item to reset its use
+ // Update client
+ Consumable consumable = this.useItem.get(DataComponents.CONSUMABLE);
+ if (consumable != null) {